diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..567609b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4afae41
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+include github.com/tj/make/golang
+
+# Generate themes.
+generate:
+ @go generate ./...
+.PHONY: generate
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..8c8c7e5
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,13 @@
+
+
+The goal of this project is to build a common toolkit for building static sites for a variety of domains. Each program is tailored specifically to one domain, such as a blog, documentation site, or others, the programs are named to reflect this, for example `static-docs` or `static-blog`. Focusing the UX on each domain as necessary, making for a smoother experience, but still sharing a common library, making it easy to create your own.
+
+I don't have much time for this project right now, so it only has what I need, but hopefully long-term it'll turn into something real :D.
+
+---
+
+[](https://godoc.org/github.com/apex/static)
+
+
+
+
diff --git a/cmd/static-docs/main.go b/cmd/static-docs/main.go
new file mode 100644
index 0000000..93522ff
--- /dev/null
+++ b/cmd/static-docs/main.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "flag"
+ "time"
+
+ "github.com/apex/log"
+ "github.com/apex/log/handlers/cli"
+ "github.com/tj/go/flag/usage"
+
+ "github.com/apex/static/docs"
+)
+
+func init() {
+ log.SetHandler(cli.Default)
+}
+
+func main() {
+ flag.Usage = usage.Output(&usage.Config{
+ Examples: []usage.Example{
+ {
+ Help: "Generate site in ./build from ./docs/*.md.",
+ Command: "static-docs -title Up -in ./docs",
+ },
+ {
+ Help: "Generate a site in ./ from ../up/docs/*.md",
+ Command: "static-docs -title Up -in ../up/docs -out .",
+ },
+ },
+ })
+
+ title := flag.String("title", "", "Site title.")
+ subtitle := flag.String("subtitle", "", "Site subtitle or slogan.")
+ theme := flag.String("theme", "apex", "Theme name.")
+ src := flag.String("in", ".", "Source directory for markdown files.")
+ dst := flag.String("out", "build", "Output directory for the static site.")
+ flag.Parse()
+
+ println()
+ defer println()
+
+ start := time.Now()
+
+ c := &docs.Config{
+ Src: *src,
+ Dst: *dst,
+ Title: *title,
+ Subtitle: *subtitle,
+ Theme: *theme,
+ }
+
+ if err := docs.Compile(c); err != nil {
+ log.Fatalf("error: %s", err)
+ }
+
+ log.Infof("compiled in %s\n", time.Since(start).Round(time.Millisecond))
+}
diff --git a/docs/docs.go b/docs/docs.go
new file mode 100644
index 0000000..71c2a56
--- /dev/null
+++ b/docs/docs.go
@@ -0,0 +1,182 @@
+package docs
+
+import (
+ "html/template"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/apex/log"
+ "github.com/pkg/errors"
+ "github.com/segmentio/go-snakecase"
+
+ "github.com/apex/static"
+ "github.com/apex/static/docs/themes/apex"
+)
+
+// Config options.
+type Config struct {
+ // Src dir of markdown documentation files.
+ Src string
+
+ // Dst dir of the static site.
+ Dst string
+
+ // Title of the site.
+ Title string
+
+ // Subtitle of the site.
+ Subtitle string
+
+ // Theme name.
+ Theme string
+}
+
+// Page model.
+type Page struct {
+ // Title of the page.
+ Title string `yml:"title"`
+
+ // Slug of the page.
+ Slug string `yml:"slug"`
+
+ // Skip the page.
+ Skip bool `yml:"skip"`
+
+ // Content of the page.
+ Content template.HTML
+}
+
+// Compile docs site.
+func Compile(c *Config) error {
+ log.Infof("compiling %s to %s", c.Src, c.Dst)
+
+ if err := os.MkdirAll(c.Dst, 0755); err != nil {
+ return errors.Wrap(err, "mkdir")
+ }
+
+ if err := initTheme(c); err != nil {
+ return errors.Wrap(err, "initializing theme")
+ }
+
+ files, err := ioutil.ReadDir(c.Src)
+ if err != nil {
+ return errors.Wrap(err, "reading dir")
+ }
+
+ var pages []*Page
+
+ for _, f := range files {
+ path := filepath.Join(c.Src, f.Name())
+
+ log.Infof("compiling %q", path)
+ p, err := compile(c, path)
+ if err != nil {
+ return errors.Wrapf(err, "compiling %q", path)
+ }
+
+ if p == nil {
+ log.Infof("skipping %q", path)
+ continue
+ }
+
+ pages = append(pages, p)
+ }
+
+ out := filepath.Join(c.Dst, "index.html")
+ f, err := os.Create(out)
+ if err != nil {
+ return errors.Wrap(err, "opening")
+ }
+
+ if err := render(f, c, pages); err != nil {
+ return errors.Wrap(err, "rendering")
+ }
+
+ if err := f.Close(); err != nil {
+ return errors.Wrap(err, "closing")
+ }
+
+ return nil
+}
+
+// render to writer w.
+func render(w io.Writer, c *Config, pages []*Page) error {
+ path := filepath.Join(c.Dst, "theme", c.Theme, "views", "*.html")
+
+ views, err := template.ParseGlob(path)
+ if err != nil {
+ return errors.Wrap(err, "parsing templates")
+ }
+
+ err = views.ExecuteTemplate(w, "index.html", struct {
+ *Config
+ Pages []*Page
+ }{
+ Config: c,
+ Pages: pages,
+ })
+
+ if err != nil {
+ return errors.Wrap(err, "rendering")
+ }
+
+ return nil
+}
+
+// compile file.
+func compile(c *Config, path string) (*Page, error) {
+ // open
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, errors.Wrap(err, "open")
+ }
+ defer f.Close()
+
+ // meta-data
+ var page Page
+ rc := static.Markdown(static.Frontmatter(f, &page))
+
+ // contents
+ b, err := ioutil.ReadAll(rc)
+ if err != nil {
+ rc.Close()
+ return nil, errors.Wrap(err, "reading")
+ }
+
+ if err := rc.Close(); err != nil {
+ return nil, errors.Wrap(err, "closing")
+ }
+
+ // populate
+ if page.Slug == "" {
+ page.Slug = snakecase.Snakecase(page.Title)
+ }
+ page.Content = template.HTML(b)
+
+ // skip
+ if page.Skip {
+ return nil, nil
+ }
+
+ return &page, nil
+}
+
+// initTheme populates the theme directory unless present.
+func initTheme(c *Config) error {
+ dir := filepath.Join(c.Dst, "theme", c.Theme)
+ _, err := os.Stat(dir)
+
+ if os.IsNotExist(err) {
+ return apex.RestoreAssets(dir, "")
+ }
+
+ return nil
+}
+
+// stripExt returns the path sans-extname.
+func stripExt(s string) string {
+ return strings.Replace(s, filepath.Ext(s), "", 1)
+}
diff --git a/docs/themes/apex/apex.go b/docs/themes/apex/apex.go
new file mode 100644
index 0000000..9d721a9
--- /dev/null
+++ b/docs/themes/apex/apex.go
@@ -0,0 +1,3 @@
+//go:generate go-bindata -modtime 0 -pkg apex -prefix files files/...
+
+package apex
diff --git a/docs/themes/apex/bindata.go b/docs/themes/apex/bindata.go
new file mode 100644
index 0000000..126dcee
--- /dev/null
+++ b/docs/themes/apex/bindata.go
@@ -0,0 +1,310 @@
+// Code generated by go-bindata.
+// sources:
+// files/css/index.css
+// files/js/index.js
+// files/js/smoothscroll.js
+// files/views/index.html
+// DO NOT EDIT!
+
+package apex
+
+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 _cssIndexCss = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x94\x57\x5f\x6f\xdb\x36\x10\x7f\xcf\xa7\x20\x1c\x14\x48\x5a\x4b\x95\x9c\xd8\x75\x15\xa0\xc0\xd6\x26\xe8\x1e\xba\x15\xcb\x8a\xed\x95\x92\x68\x9b\x0b\x25\x0a\x14\x95\xc4\x1d\xf6\xdd\x77\xe4\x51\x12\x69\xcb\xed\x8a\x20\xb0\x74\xe4\xfd\xfb\xdd\x8f\x77\x54\xa6\xa4\xd4\xe4\x9f\x33\x42\xa2\x88\xd1\x96\x65\xa4\xe8\x72\x5e\x44\x39\xfb\xca\x99\xba\x88\xd7\x8b\x39\x49\xe6\x24\x4e\xe1\x37\xbd\xbc\xb1\xfb\x9e\x78\xa9\x77\x19\x59\x27\x49\xf3\x8c\x92\x1d\xa3\x25\x53\xf0\xc3\xb7\x3b\x9d\x91\x6b\x5c\xb1\x4b\x5a\xd1\xe2\x81\xd7\xdb\x8c\x24\x71\xb2\x54\xac\xba\x09\xc4\x51\xc5\x4a\xde\x55\x66\x75\x62\x51\x50\xb5\x65\x66\x6d\x6d\xd7\x60\xf1\xf5\xcb\x28\x2a\xa9\x7a\xc8\xc8\xf9\x22\x5d\x2c\x17\x6f\x6f\x5e\xbe\xb6\x3a\x4e\x98\x24\x09\xda\xc8\x45\x07\x9a\xe7\x57\x57\x3f\xbd\xbd\xbb\x43\x91\x30\xd1\x45\x5b\x45\xf7\xb0\xb0\xa1\xe6\xcf\x05\x99\x6f\x8d\x64\xb3\xc1\x7d\x1b\xf3\xb6\x5e\xad\x6f\xdf\xae\x7a\xc1\x81\x4f\xd4\x6a\x99\x60\x85\xe6\xb2\xb6\xfa\x8f\x54\x5d\xa0\x5b\x07\xd3\xb8\x6c\x0c\x3e\xed\xb8\x66\x37\x67\xff\x9e\x9d\xbd\xb4\x70\xe7\xf2\x39\x6a\xf9\x57\x8b\x4c\x2e\x95\x81\x0f\x44\x76\xc3\x4e\x57\x62\x0e\xc2\x72\x6f\x77\x6e\x64\xad\xa3\x0d\xad\xb8\x80\xb8\x23\xda\x34\x82\x45\xed\xbe\xd5\xac\x9a\x93\x9f\x05\xaf\x1f\x3e\xd1\xe2\xde\xbe\xdf\xc1\xce\x39\x68\x10\x32\xbb\x67\x5b\xc9\xc8\x97\x5f\x66\x73\x32\xfb\x5d\xe6\x52\x4b\xf3\xf4\xdb\xf3\x7e\xcb\xea\x99\xdb\xf3\x25\xef\x6a\xdd\x19\xf9\x7b\x5a\x6b\xaa\x98\x10\xe6\xe5\x8e\x2b\x4a\xee\x69\xdd\xf6\xfb\x3e\x28\xc9\x4b\x27\x21\xb3\x8f\x4c\x3c\x32\xcd\x0b\x4a\x7e\x65\x1d\x03\x49\x0b\x0b\x90\xac\xe2\x08\xdf\x13\xcb\x1f\x38\x04\x6c\xa2\x6e\x2b\x60\xd7\xce\xe6\x08\x1e\x38\x15\x1c\x28\x56\xde\xf4\x49\x41\xfe\x50\xa3\x74\x85\x34\xb2\xa2\x27\x47\xa1\x2b\xac\x63\x0e\x44\xd8\x2a\xd9\xd5\xe5\x00\xf0\xd6\xc2\x5b\x48\x21\x55\x2f\x73\xf5\xb1\x0b\x00\x08\x1b\x88\x98\x1a\x49\x05\x24\xe2\x35\xb0\xc8\xbc\x34\xb4\x2c\x91\x8d\x16\xea\x2c\x1b\xaa\x84\x55\x39\xf2\xe7\x17\xf9\xd8\xb3\x5f\xe3\x4b\x2c\x5e\x3a\x27\x3b\x38\x2c\xbb\x2b\xf8\xbf\xb6\x46\x31\x80\x48\xcb\x26\x23\x6f\x96\x98\xac\x93\x41\x61\xb4\xac\x5c\x6c\x3e\x26\xf1\xc2\x9d\x86\x00\x95\x15\xa2\x12\xe6\x38\x1c\x9c\x13\x98\xd8\xa0\x8e\x02\x39\xf6\x88\x66\x70\xf7\x2b\xd2\x8c\xdc\x0b\x37\x1c\xb9\x5f\x59\x25\x6a\xf7\xbb\x10\x78\xbd\x03\x3e\x68\xb3\x59\xb3\x67\x1d\x95\xac\x90\x8a\x1a\x9c\x32\x52\xcb\x1a\xcf\x41\xe3\xc5\x94\x91\x05\x00\x43\xa6\xb2\x5b\x4d\xa5\x66\xdf\xf1\x40\x63\x86\x82\x93\x56\x2b\x59\x6f\xe7\x60\x17\x9f\xfc\x80\x8e\x79\x12\xe0\xba\x4c\x92\xde\x0a\x35\x06\xe8\x0f\xe8\x5e\x27\x3e\xb1\x86\x8a\x5e\x61\x9d\x87\xa3\x8d\xd2\x14\x92\x2c\xe1\x99\x95\xe4\xbc\x2c\xcb\xc1\x69\xb6\x93\x8f\x4c\x59\xd7\xf8\x78\x1c\xc0\xd0\x59\x0e\x4c\x7a\x78\xfa\xba\xc1\xae\x28\xb0\x64\x5f\x10\xb6\x4e\x04\x45\x58\x26\xa6\x08\xf8\x73\xe5\xba\xfb\xc1\x89\x01\x8d\x03\xa5\x14\x95\xd2\x51\x09\xb7\x41\x5e\x81\xed\xbe\xbe\xdf\xae\xe5\x31\xbb\x1d\x2b\xd1\xe2\x37\x4a\xdb\xa7\x75\xa2\xb6\x8d\x62\x27\x8e\xf8\x38\x17\x8e\x0f\xf8\x50\xf3\x01\x86\x1e\x17\x07\xb0\xa2\x30\xbf\x5a\xe0\x2f\x4a\x0d\xfe\x1b\x21\x9f\xa2\xe7\x8c\xb4\x85\x92\x42\xf4\x01\x65\xd0\x94\x65\xa7\x0a\x46\xde\xcb\x92\x91\xcf\xca\x74\xe4\x4f\xac\x16\x72\x4e\x2a\x59\xcb\xb6\xa1\x05\x3b\x38\x94\xf1\x7a\xf2\xc4\xf5\x90\x00\x1e\xef\x20\xdc\x92\x19\xe2\xe0\x93\x57\x7b\x64\x5b\x2b\x05\x34\xef\xf3\x0f\xb7\xb7\x8b\xdb\xd5\x81\xf9\x24\x7e\xd3\x9f\xe9\x31\x3d\xd0\x49\xa7\x53\x74\x94\xb6\x93\x2c\xb2\xe1\x1a\xf6\x3d\x29\xda\x9c\x6a\x54\xc1\xe8\x1a\x9a\x02\x44\x5e\x32\x4d\xb9\x68\x21\xe8\xb6\xab\x80\x21\x38\xe9\x8a\x4e\xb5\x06\xfa\x46\xf2\x5a\x33\x65\xf1\xec\xb4\x49\xbe\xa7\x39\x21\x1d\x4c\x1a\xd7\x76\x3d\xee\x8f\xf6\x1a\x9f\xfd\x82\x6d\x34\xa6\x84\x30\x4c\xd7\xd4\x6d\x4b\x97\x8e\xbb\xf1\x9f\x90\x52\xe3\xce\x51\xc9\xdb\x46\x98\x1b\xc3\x46\x30\x9b\xfe\xdf\x5d\xab\xf9\x66\x0f\x74\x83\x18\x4d\x59\x0b\x86\xc1\x1a\xcd\xf7\x20\xa4\x10\x2f\xea\xba\x8b\x12\x7a\xb5\x2f\xd6\xed\x81\x49\xa3\x77\xcf\x4b\x96\x53\xd4\x32\x52\x88\x06\xfe\x68\xa7\xe5\x68\x17\xdc\xf8\x56\xdf\x2c\x5f\xe0\x1a\x70\xa8\xb3\x0b\x8d\x6c\x39\xf6\xd8\x7e\x08\x43\xa4\xc5\xc3\xfe\x26\x58\x1b\x65\x76\x0e\x2c\x93\x60\x20\x59\x59\x41\x45\x71\x81\x51\x07\x37\xbb\x4b\x98\x0b\x86\x1b\xdf\x9a\xc0\x43\x44\xef\x48\x0c\x3c\xa9\x30\xb2\x83\xe6\x38\x42\x1d\x6c\x7d\xe7\x1a\xef\x18\x2c\xdc\x49\x60\x6c\x3c\x9e\xaa\xfc\x74\x2b\x86\xeb\x63\xdd\x1b\xb0\x51\x92\x45\x92\x54\xed\x44\xd0\x53\xf1\x42\x10\x31\x2d\x8c\xd3\x71\x02\x06\xbd\xe4\xff\xa7\x0e\xa6\xb2\x9c\x6d\xa4\xeb\x3c\x03\x63\x66\xb3\xb0\x24\x34\x07\x7a\x76\xda\x26\xe4\xaa\x9b\x26\xc9\x0b\xf3\x3a\x1c\xfa\xfe\x44\x22\x80\x91\xbb\x46\x20\x73\x0f\xae\x4a\xd1\xc9\xc1\xf5\xc8\x5b\x9e\x73\xc1\x35\x90\x6f\xc7\xcb\x92\xd5\x03\x5e\x10\x65\x65\x5a\x16\x15\xec\xaf\x8b\xe4\x32\x90\x47\x52\x71\xdb\xc3\x8d\xbb\x81\xef\x21\xd0\x54\x08\x98\xe1\x00\xb3\x73\x6b\x3e\x26\x4e\x61\x72\x62\xc2\x7d\x07\x49\xab\xe5\xe3\xe9\x67\x63\x9f\x05\x9b\x4e\x27\x75\x26\x3f\x5a\x32\xff\xe0\xb1\x26\x90\x1a\xdf\xd6\x91\x89\xa3\xf5\xc5\x7d\x69\xa6\x0e\x0a\xfa\xfb\x4c\xb7\xec\xe8\xe2\x95\x26\xe1\x60\x8d\xbc\x63\xe8\x05\xf9\x8a\x4c\xab\x27\x47\xaa\x38\xde\xe2\x3f\xb8\x16\x6c\x7a\xe2\x06\xe3\x63\x11\x2f\xdc\xf8\x40\x95\x18\x33\xf2\x1d\x21\xab\xb0\xfb\x0c\x42\x85\xea\xbd\xd4\xde\xeb\x2c\x32\x3e\x26\x15\x7d\xee\x3f\x0f\x97\xc9\x90\x12\xfa\x41\x3b\xbe\x9f\x9e\xcd\xeb\x70\xa7\x99\x0a\x0d\xad\xc3\x3a\xe5\x42\x16\x0f\xfe\xa6\xb8\xed\x72\x13\xc4\xe9\xbb\x80\x77\xa7\x08\x86\xde\xda\xcd\x3c\x9b\x82\xc7\x96\xce\x34\xfd\x02\x78\x1b\x34\xe9\x61\xc6\xf4\x7e\x07\xa7\x82\xc1\x45\x4e\xd9\x49\x68\x47\x27\x7a\xef\x3f\x5e\x2f\xbf\xeb\x22\x68\x2d\xb9\x14\xd3\x1f\x46\x23\x80\x6d\x65\x0e\xd9\xe8\xdf\xdf\x7a\x8d\x9c\x3a\xf4\xd7\xb7\xc9\xc3\x50\x6b\x58\xa5\xe2\x98\x1b\xfd\x27\x40\x7c\x07\x1f\x6e\x8e\x14\x43\x0b\xea\x0b\xfa\x5f\x00\x00\x00\xff\xff\xf5\x75\x54\x7e\x34\x10\x00\x00")
+
+func cssIndexCssBytes() ([]byte, error) {
+ return bindataRead(
+ _cssIndexCss,
+ "css/index.css",
+ )
+}
+
+func cssIndexCss() (*asset, error) {
+ bytes, err := cssIndexCssBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "css/index.css", size: 4148, mode: os.FileMode(420), modTime: time.Unix(1504144113, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _jsIndexJs = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\x53\x4d\x6f\xda\x40\x10\xbd\xfb\x57\x4c\xd5\x83\x6d\x52\x2d\x9c\xdb\x12\x89\x44\x39\x44\x6a\x7b\x28\x52\x2f\x08\x89\x65\x3d\xe0\x15\xeb\x1d\xba\x5e\x83\x50\x95\xff\xde\xd9\x0f\x42\x83\xa8\x82\x64\xb4\xb6\xdf\xbc\x79\xef\xcd\x78\x3c\x1a\x15\x30\x82\x39\xfa\x61\x2f\xf8\x34\x2e\x0a\x45\xb6\xf7\xa0\x3d\x76\x3d\x4c\xa1\x21\x35\x74\x68\xbd\xf8\x3d\xa0\x3b\xcd\xd1\xa0\xf2\xe4\x66\xc6\x54\xe5\x42\x37\xcb\xb2\xce\x78\xa3\xed\xee\x1d\xbc\xf8\x8e\x76\x00\xc9\x25\x45\x6e\xfb\xd8\xa2\xda\x81\xde\xc0\x0a\xcd\x0a\x74\x0f\x34\xf8\x74\x6d\xe0\xa0\xf1\x98\x15\x6d\x06\xab\xbc\x26\xcb\x88\x07\x34\x74\x9c\x2b\x47\x4c\x88\xa6\x86\x3f\x05\x80\x63\xf1\xce\x02\x1a\xb1\x45\xff\x40\x83\x6d\xb4\xdd\x3e\x1a\xcd\x2a\x7e\x72\xf7\xaa\x16\x6b\xf2\x9e\x3a\xb8\x87\x49\xf1\xf2\xda\x7c\xc6\x9c\x07\xe9\x31\x5a\x85\x95\x5e\x5d\x77\x93\x19\xf0\xcc\xef\x2b\x9d\x7a\x45\x9b\x62\x43\xee\x49\xaa\xb6\x42\x98\xde\x03\x0a\x65\x64\xdf\x7f\xd3\xbd\x17\x0e\x3b\x3a\x60\x55\xc6\x52\x2c\xeb\xfa\x5c\xb2\xd0\xcb\x7f\x60\xb2\x69\x2e\x98\x5b\x92\x7c\x8b\xa0\xc8\x39\x96\x0f\x5d\x48\x2d\x6a\xe4\xb6\xe1\x4d\x40\x72\xe8\x9e\xfd\xf5\xa0\x6d\x04\x87\xb4\xf6\xe4\xfc\xff\x3c\x54\x59\x3e\xf2\x64\x79\x4a\x93\x82\x6f\x02\x5d\xf5\x85\xef\xbf\xa6\x69\x0b\x83\x76\xeb\x5b\x7e\x72\x77\x97\xe0\x10\x66\x53\xbd\x4d\x3d\x42\xd9\x4e\x7d\x86\x00\xac\x1d\xca\x5d\x3c\xbf\x14\xe1\xe2\xbf\xab\xec\x6e\x79\xec\x23\x1f\xf4\xfb\x13\x3b\xe0\x89\x9d\xb2\xf6\xa3\xb6\x0d\x1d\x43\x46\x4f\x07\xb6\x18\x02\x43\x8b\xae\x2a\x53\x41\xf9\x09\x62\xec\x17\x67\x97\x7d\x9a\x35\x0d\xf4\x1d\x91\x6f\x33\x3b\xd3\xbe\xc7\xaa\x8c\x56\xbb\x33\x69\x70\x94\xf6\x19\x0d\xc7\x84\xc2\x4b\xc7\x4b\x15\x1c\x8d\xc7\x79\xc7\xc9\x9a\x53\x91\x92\xe1\x95\xb3\xd4\xe0\x0f\xd9\x21\x7c\x98\x42\x39\x2b\xeb\xbc\x8d\xb9\x62\x70\xe6\x95\x91\xcf\x81\x32\x6e\xe9\xcc\x7b\xa7\xd7\x03\xab\x2f\x5b\x87\x9b\xf0\x49\x44\xbc\xb4\xaa\x25\xf7\xa6\x07\x97\x2d\x26\xcb\x48\xff\xf1\x9a\x3e\x99\xcc\x3f\x7e\x74\xfb\xe3\x0b\x14\xb5\x48\xd8\x67\xeb\xe9\x17\xef\x4a\x95\x66\xb7\xc6\x56\x1e\x34\xb9\xcf\x50\xa6\xdc\xca\x30\x40\x1e\x57\x5d\xfc\x0d\x00\x00\xff\xff\x79\x82\x0e\x63\x1a\x04\x00\x00")
+
+func jsIndexJsBytes() ([]byte, error) {
+ return bindataRead(
+ _jsIndexJs,
+ "js/index.js",
+ )
+}
+
+func jsIndexJs() (*asset, error) {
+ bytes, err := jsIndexJsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "js/index.js", size: 1050, mode: os.FileMode(420), modTime: time.Unix(1504144547, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _jsSmoothscrollJs = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x5a\x5f\x73\xdb\xb8\x11\x7f\xf7\xa7\xd8\xbc\x9c\xa4\x9c\x42\xd9\x6d\xef\xa6\x23\x55\x6d\x4f\x89\x5a\xbb\xb5\xa3\x1b\x4b\x93\xda\xd3\xb9\xe9\xd0\x22\x24\xe1\x42\x11\x3a\x12\xd4\x9f\x89\x7d\x9f\xbd\x8b\xbf\x04\x40\xd2\x39\x4f\x33\x93\x97\xe6\xc5\x04\x76\xb1\xd8\x5d\xfc\x76\xb1\x58\x65\xf0\xfa\x0c\x5e\x43\xb1\x65\x8c\x6f\x8a\x65\xce\xd2\x14\x76\x2c\x3d\xad\x28\x7e\xbc\x81\xfd\x79\xf4\xfb\xe8\x3b\xc1\xb1\xe1\x7c\x57\x0c\x07\x03\x1a\x6f\x93\xb2\xe0\x71\x16\xad\x29\xdf\x94\x0f\x11\x65\x03\x77\xb5\xe0\xfd\xdd\xf9\xc5\xf7\xd0\x5d\xf6\xe0\x9d\xe4\x84\x7f\xc6\x05\x27\x59\x1f\xfe\x41\x72\xb2\xa5\x71\x01\x37\x24\xa3\xcb\x0d\x49\x53\x8a\x7b\xdc\x5c\x2d\xe0\x9a\x2e\x49\x56\x10\x5c\x3c\x38\x3b\xeb\xae\xca\x6c\xc9\x29\xcb\xba\x87\x3e\x24\x7d\x28\xb3\x84\xac\x68\x46\x92\x1e\x7c\x3a\x03\xe8\x94\x05\x81\x82\xe7\x74\xc9\x3b\xa3\x33\x9c\x10\x26\x00\x6e\x1b\xa7\x28\x9b\x14\x6a\x70\x18\xc2\x81\x66\x09\x3b\xc0\x3a\x65\x0f\x71\x0a\xec\xe1\x67\xb2\xe4\x8a\x98\x0c\x21\x61\xcb\x72\x4b\x32\x3d\x61\xb7\x18\x56\x9f\x92\x32\x90\x1b\x0c\xac\x4f\x70\x64\xb4\xb3\x73\x5d\xa5\x97\xe4\xcb\x09\x2f\xf3\x0c\x0e\x1b\x92\x81\x72\xc8\x84\x6c\xe2\x3d\x65\x39\xd0\x8c\x93\x7c\x15\x2f\x09\xd0\x02\x8a\x72\xb7\x63\x39\x57\xbb\x00\x5d\x41\xb7\xe3\xb3\x77\x90\x1f\x92\xc8\xa8\x39\x4d\x89\xf8\x13\x15\xfc\x94\x12\xb3\x1f\xe8\xed\x46\x72\xf4\x74\xa6\x94\x78\xad\x68\xaf\xb5\xe1\x85\x1e\x0e\xe4\xdf\x7d\x9c\x83\x96\x05\x63\x38\x44\x97\x8b\x9b\x6b\x33\x7e\x7c\xc4\x09\x3d\x18\x59\xee\xf9\xdb\xdb\xd9\xf5\xf5\x7f\x16\x57\x37\x53\x5c\xf1\x87\xef\xff\x38\x0a\xf7\x51\x9e\x85\x75\xcc\x37\x24\xa7\xd9\x1a\x58\x4e\xd7\x34\x43\x9f\x6b\x40\x6d\x09\xdf\xb0\xa4\xae\x88\xe5\x1b\x5b\x83\xd4\x0a\x3c\xbb\x48\xaf\x95\x4a\xa9\xef\x05\xeb\x7b\x5c\x93\x53\xc5\x37\x39\x19\x1a\x49\xe7\x5a\x86\xf1\xd9\x2e\x67\x9c\xf1\xd3\x8e\x38\x32\xd5\x97\xe6\xf0\xc5\x5e\x65\x9c\x7d\xa0\xe4\xd0\x2e\xc0\x70\x28\xb7\xd7\xfc\xa1\xf0\x03\x9c\x6e\x85\x33\x94\xf1\x35\xdb\x33\x44\xa6\x38\x80\x1d\x62\x82\xe5\xdb\x38\x43\x5c\x7c\xf3\x8d\x3f\x11\x21\x93\x56\xed\x2f\x75\x4a\xf4\x80\xf8\xee\x7a\xd3\x3d\x18\xc2\xbb\x98\x4b\xb2\x55\xcb\xea\xb5\xdc\xc4\xd9\x9a\x14\x60\xc3\xbc\xa0\x12\xc7\x34\x2b\x68\x42\x00\x03\x95\x28\x83\xcd\x82\xbf\x2a\xdd\x7d\x67\x59\xe2\x2e\xce\xe3\x2d\x7c\x7a\x5f\x6e\x1f\x48\xfe\x04\xc7\x36\xc2\xc9\xb3\xdd\x46\x8f\x27\xb4\x7b\xec\xc3\xa9\x82\x35\xdf\xd0\x42\xfb\xfa\x9a\xac\x04\x52\x8f\xa3\x3a\x69\xc1\x76\x48\x39\x05\xe8\xb7\xe6\xaa\xd8\x28\xf0\x6f\x51\xa6\x1c\xd8\x0a\xe2\xdd\x2e\x3d\x89\x33\x21\x98\x27\x60\x8b\x78\xad\xd4\xe1\x0c\x62\xc8\xa4\xca\xa1\xf9\x82\xbb\xcd\xb8\x8f\x96\x60\xb6\x33\xa4\x66\xab\x85\xac\xee\xc7\x30\x80\xe1\x3c\xfa\x0e\x65\x74\x2f\x44\x3a\x44\xb5\xa2\x25\x2b\xba\xf2\xe3\xc7\x2b\x9c\xff\xd8\xeb\xb5\x19\x89\x10\xa0\x4b\x3c\xf1\x42\xe4\x90\x58\xe7\x71\x78\x30\x39\xa7\xd8\xb0\x32\x4d\x70\x2c\x6d\xa7\x24\xa9\x1d\xad\x64\x98\xc4\x34\x9d\x95\x2d\x47\xfb\x38\x93\xf1\xed\x9e\xb0\xb5\x75\xc2\x58\x4a\xe2\xac\xc5\x58\x4f\x78\xf7\x58\x59\x2d\xf2\x9d\x88\x27\x3c\x93\x23\xbc\x1a\x8f\xa1\xa3\x72\x48\x47\xd3\xd5\x3f\x0c\xd3\x23\x8c\x91\x9a\x95\x32\xf1\xfa\x94\xc8\x1a\x29\x58\xbc\xac\xfd\x0c\x5f\x27\x2e\x39\xab\x6f\x13\x30\x61\x44\xe0\xbd\xc5\x3b\x95\xc6\x32\xb9\xaf\x68\x5e\x70\x88\xf3\x35\x46\x2f\x17\xf1\xa2\xb4\x1e\x78\xfa\x21\x1f\x8a\xb1\xf2\x30\xd7\x8b\x2d\xfb\xa0\x65\x0a\x62\x5d\x59\x0d\x03\x9e\x97\xc4\x00\x5d\x9f\x75\xe0\xac\x71\x9b\xb3\x30\x77\x84\x56\x28\x30\xb4\x1a\x21\xef\x15\xa9\x9f\x31\x04\xbf\x12\x4f\x73\x25\x21\xd4\x72\x85\x97\x4a\x5d\x4d\x14\xcd\x37\x39\x66\x35\x92\xe7\xb8\x5a\xde\x80\xae\x2c\xe1\x32\xff\xd6\x03\xbd\x20\x23\x07\x58\xa0\x81\x53\xb1\xb0\xdb\xb1\x8b\xc4\x8a\x3d\x5e\xea\x49\xa7\x15\xfe\xe8\xc5\xc4\x24\xb4\xf8\x21\x25\x80\xc8\x15\x56\x89\x60\x6f\x4d\x67\x62\xd1\xdc\x2e\xf9\x51\xae\xa8\x41\x9f\x25\xe4\x09\x05\x34\x84\xb7\x4f\x09\x20\xdf\x24\xbb\x4b\xd2\xea\x0c\x44\xee\xa7\xc5\x84\x25\xa7\x91\x33\xb3\x89\x8b\x6a\xd5\x7c\x87\x45\x42\x40\xfd\x40\x0b\x8a\xa4\xd9\x1e\x93\x7d\x6a\x93\x3b\x60\x19\xe3\x9c\x2e\x11\x37\x29\x49\x23\xe5\x04\xa1\xa8\xe5\x93\xe7\x53\x10\x0e\x4b\x86\x19\x43\xaa\x8a\x92\xa9\xd8\xae\xb0\x2c\x4a\x2f\x29\x43\x22\x28\x89\x1e\x1c\x3d\xa1\x41\x4b\x18\x3b\x20\xc4\x9d\x97\x98\x63\x32\x7e\x49\xe8\x7a\xc3\xe1\x4f\x62\x46\x9d\x8d\x9e\x79\x7c\x6c\x62\xff\x17\x4d\x30\x67\x39\xdc\x72\xc2\xdb\x36\x30\xdf\xdb\xf6\x10\xad\x09\x7f\xcb\xb6\xbb\x12\x81\x35\x17\xf5\x11\x3a\xbc\x2f\x93\x46\x2f\x62\x76\x85\x88\x88\xbd\x12\xd3\xb1\xe0\x45\x98\x52\x44\x4d\xf7\x95\x36\x1d\xa3\xe8\x55\xb7\xc1\x4c\x31\x5f\x57\x43\xa4\xe5\xb3\xc0\x75\x4d\x3e\x6a\xb4\x40\x2a\x68\xd7\xeb\xd0\x22\x69\x1b\xd2\x0b\x92\xae\x30\x87\xec\xd9\x47\x92\x38\xb7\xd6\x26\xe6\x7d\x58\xd3\x3d\x06\x5b\x2c\xce\x96\x93\x23\x4e\x60\xb9\xbd\x2b\x64\x74\x95\xeb\x8d\x0e\x0f\xbc\xf7\x6a\xc9\x1f\xd9\x42\xe0\x9b\x64\xaf\x65\xb5\x24\x76\x5c\xd8\xd5\x1c\x3e\xb4\xb1\xe8\x11\x06\x63\xfd\xd1\xed\xb9\x10\xc6\x38\x2e\x3d\x4c\x2f\xcb\x5c\x80\xf4\xae\x61\xee\xde\x9d\x23\x69\xbc\x2b\xd0\xe4\x31\x26\x41\x21\xfb\x8d\xd1\x0c\x4b\xe1\x38\xe7\x0b\x9c\xeb\xc1\xc0\xad\x52\x47\x4e\x46\x8a\xf7\x8c\x26\x56\x86\x10\x50\xc0\x06\xa1\x48\x72\xe1\x3a\xcc\x7b\x19\xb1\x65\xa3\xd9\xc7\x7c\xfd\x19\x2e\xb0\xec\xba\xc0\x92\x4a\xcf\x78\x82\x45\x29\x21\x6e\x73\x51\x4e\x60\xed\xe0\x6e\x61\xb5\x47\x93\x85\x3c\x71\xe5\x6b\x7a\x05\x18\x63\x3f\x32\x78\x06\xdd\xc1\xb7\x60\x5c\x1b\x1d\x43\x73\xef\x7a\x78\x54\x9e\x2f\x8d\xcf\x42\x39\xf7\xae\x9c\x53\x28\xe7\xde\x91\x63\x04\x69\xba\xc2\x46\xb4\x8c\xf1\x61\x63\xd7\x58\x44\xf7\xad\xe2\xf6\xeb\xbe\xe7\x3a\xc6\x94\xfc\x2c\x27\xe2\xe6\x3a\x10\x04\xff\x9e\xc8\x5c\x9e\x93\x18\x1f\x7d\x09\xb0\x32\xc7\x1a\xb9\xe0\x58\xfa\x0b\x34\x39\xd7\x9c\x75\x8a\xa8\x09\x2a\x27\xe0\x0d\x6d\xcd\x74\x29\x27\xf7\x6a\x3b\x44\x39\xf9\xa5\x44\xb1\x3f\x64\x74\x2b\x05\xff\x0d\x01\x4d\xba\x02\xab\xba\x54\xee\x9b\x95\xbd\x5e\x75\x7d\xb5\x84\x9b\x34\xa3\x30\x6f\xc8\x03\xbe\x75\xeb\xe5\x55\x2d\x9e\x24\x79\xae\xdf\xc1\x4d\x71\xf5\x58\xbb\x57\xfe\xd7\x2a\xda\xd9\x52\xa6\x3d\xbf\x90\x16\x11\x54\x1d\x9e\x1b\x57\x0a\x4e\xb5\x19\x2f\xf6\x94\x59\x35\x9e\x85\x17\xe3\xd5\xd1\xeb\x77\x8f\x46\x80\x97\x40\xe4\xe9\x7a\xb7\x8a\x7b\x74\xce\xfd\x8d\x4f\xa2\x2a\xf1\xeb\x80\x18\xdb\x57\xde\x9d\x7a\x0e\xee\xe2\x35\xb9\x9b\xad\x56\x78\x9f\x05\xcc\xf7\x0e\xf3\x7d\xc5\x7c\x1f\x32\xeb\x03\x1b\xdb\x27\xa8\x5e\x53\x5d\x0c\x04\x6b\x9c\x36\x15\x49\xda\xa0\xa3\xbd\xbd\xc4\x8b\xa5\x41\x2d\x4b\xc7\x67\x4b\x83\x22\xde\x73\xa8\xa9\xb8\xd2\x6e\x4d\x19\xdb\xc9\x37\x36\x5e\x24\x88\xc8\x95\xc0\xb8\x79\xbe\x8a\xac\xdc\xa4\xf2\xd0\xf9\xee\x07\x5b\x0f\xf5\xdf\xbe\xaf\xb1\x38\xe3\x61\xf5\x19\x50\xef\x34\xe9\x2e\x98\xbf\xd7\xf3\xf7\xd5\xfc\x71\x88\x90\xb4\x23\x7c\xb1\x9f\x8c\x6d\x61\x51\x67\x90\x3f\xbb\xbd\xfa\xfb\xd5\xfb\x1f\xae\xe1\x66\xba\xb8\x9c\xbd\x9b\xc3\xec\xc3\xf4\xf6\xf6\xea\xdd\x74\x6e\x43\xe0\x4c\xfb\xc4\x76\x09\x44\xe1\x5a\xb5\x09\x24\xd9\xd2\xc6\x0e\x05\x07\xb6\xb9\x54\x21\xd0\x5e\x14\xe1\xf3\x09\x51\xab\x12\xd7\x2f\x25\xcd\x6d\xd5\x2a\xb0\xec\x3f\x6d\x4c\x35\x5d\xfc\xfb\xfc\xa7\x9e\x8b\xec\x00\x5e\x2a\xab\xba\xb5\x4b\xdf\x19\xb8\x52\xa2\x54\xbc\x7b\x11\xc0\xee\x64\x2b\x33\xc7\x97\xb0\xc7\x7b\xf1\x93\x65\xed\x8d\x82\xfa\xbd\x09\x5c\xd7\xd3\x05\x2c\x2e\xa7\x30\xbf\x99\xcd\x16\x97\xef\xa7\xf3\x39\x4c\xa6\x78\x0a\xaf\x0c\xb4\x9c\x34\x13\x18\xe1\x98\xa0\x22\xbb\x1a\xff\xfa\x6b\xcd\xa4\x56\x22\x9a\x70\xe6\x29\x6c\xfb\x2a\x03\xa7\xc9\xe3\x9d\xec\xe4\xf4\x95\x8f\x73\x72\xfa\xff\x81\x8a\x32\xa3\x3d\x3d\xf7\x9e\x3b\x6f\x77\x69\x3d\x59\xf7\x5a\xe1\xd0\xda\xd7\x13\x59\xa0\x8d\xa8\xb3\x42\xeb\xda\xf1\x33\x2b\xbf\x1a\xcc\x4c\x2f\xb3\x06\x33\xd5\x07\xeb\x7b\x33\x2f\x42\xdb\x17\xc5\x9b\xa8\x0c\x52\xd5\xa7\xab\x29\xe1\x56\x0f\x5c\x36\xec\xc2\x8d\x47\x5f\x06\xb6\x75\x97\xd4\xc6\xaa\x89\xa2\x34\x15\xcf\x41\xd5\xee\xeb\x60\x95\x2f\xe7\x86\x61\xdf\xb1\x61\xb1\x34\xc1\x5f\x2b\xa6\x86\x41\x5f\xf2\xc5\xd8\xd5\xa9\xad\x9d\xdc\x8c\x41\xe1\x55\xf4\xe7\x79\xe0\xd6\x51\x43\xef\x48\xb1\x39\xed\x23\x17\x71\x8e\xf6\x4e\x05\x01\xd2\x2f\x43\xb9\xd4\x04\xfb\x73\x2e\x62\x3b\xcd\xab\x82\xdb\x77\x89\xcb\x69\x42\x45\xb3\xfb\xf5\x34\x54\xb5\x41\x43\x21\xe6\x6a\x1a\x9c\xb5\xdb\x31\xfe\x56\x4a\x0e\xd1\xe0\x34\x8e\xbf\xfd\x0c\xd8\x9f\x7e\xeb\xc9\x79\x3f\x06\x7c\x8e\xe9\x2b\x5f\x58\x46\x8d\xcf\x86\x8e\x2b\xd0\xef\xaf\x0a\xc0\xe7\xf8\xae\x1d\x7a\x3c\x5f\xf6\x72\xf2\x5f\x2d\xaa\x89\x26\x3c\xd7\xd4\x5b\x13\x9a\x7b\x9d\x06\xd5\xfc\xba\x45\x84\x17\xb6\xac\xae\xf8\x45\xaf\x68\xc2\xd0\x1a\xac\xa1\xdf\xca\xee\x93\xe0\xf4\x7b\x15\x4b\x3b\x2f\x24\x48\xdc\xb4\xae\x72\x8f\x24\xd4\xf8\x55\xe3\x63\x47\xfe\x48\xb8\x27\x71\x6a\xda\x93\xe6\xf7\x97\x9d\xd3\x88\x84\x17\xa6\xba\x70\xef\xe7\x68\x7e\x98\x38\xc6\xaa\x10\x7f\xe3\x7a\x30\xa8\xd9\x5a\x85\xa9\x88\x72\x65\x89\x14\xe0\x8b\xaa\x2a\x3c\x0f\x26\x95\x3f\x74\xef\x96\x66\xb0\x47\x8c\x8a\x46\x71\x55\x93\xd8\x3c\xd8\x90\x9e\x9e\xd3\x57\xe6\xa4\x40\x8b\xe6\x4c\x64\xda\xe5\xbf\x25\x09\x35\x9d\xe1\x0b\x95\x0e\xfd\x5e\x53\x3a\x70\xe6\xcb\x95\x36\x09\x4c\xc7\x9e\x73\x15\x90\xa3\xd0\xb3\x68\xbc\x0d\xd0\xb4\x25\xdb\x6e\x59\xf6\xb3\xea\x0b\x6f\x59\x52\xa6\x24\xb2\x4b\xe0\x93\xfd\xdd\x7b\x58\xfd\x4f\x01\xb5\x8d\xeb\x26\x94\xa3\x7e\x7f\x96\xa3\xea\xa7\x72\xa5\xcf\x53\xaf\xab\x3a\x2b\x7d\xfb\x43\x3c\x52\xfe\x1b\x00\x00\xff\xff\x13\x45\xe8\xd6\x8c\x20\x00\x00")
+
+func jsSmoothscrollJsBytes() ([]byte, error) {
+ return bindataRead(
+ _jsSmoothscrollJs,
+ "js/smoothscroll.js",
+ )
+}
+
+func jsSmoothscrollJs() (*asset, error) {
+ bytes, err := jsSmoothscrollJsBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "js/smoothscroll.js", size: 8332, mode: os.FileMode(420), modTime: time.Unix(1504142380, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _viewsIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x54\x4d\x6f\xe3\x20\x10\xbd\xef\xaf\x60\xd9\xf3\x1a\xed\x6d\x0f\x38\x97\xb4\x55\x2f\x55\x23\x25\x52\xd5\x23\xb1\x27\x81\x14\x63\x0b\xc6\x55\xa2\xd4\xff\xbd\xf8\x23\x15\xe0\x34\xea\x29\x33\x79\xef\xc1\x9b\x07\x98\xff\xbe\x7b\x5e\x6e\x5e\x57\xf7\x44\x62\xa5\x17\xbf\xf8\xf8\x43\x08\x97\x20\xca\xbe\xf0\x65\x05\x28\x48\x21\x85\x75\x80\x39\x6d\x71\xf7\xf7\x3f\x9d\x20\x54\xa8\x61\x71\x3e\x67\x9b\xbe\xe8\x3a\xf2\x41\x7c\xb3\x6e\xb7\x38\xf6\x9c\x8d\x8c\x91\xad\x95\x79\x23\x16\x74\x4e\x1d\x9e\x34\x38\x09\x80\x94\x48\x0b\xbb\x9c\xa2\x84\x0a\x98\x68\xe0\xc8\x0a\xe7\x98\x32\x25\x1c\x33\x5f\x0d\x3b\x71\x76\xb1\xc3\xb7\x75\x79\x9a\x96\x2b\xd5\x3b\x29\xb4\x70\x2e\xa7\x2f\x56\x34\x0d\xd8\xc9\x56\x8c\x2d\x6b\x83\x42\x99\x00\x8d\xf1\xb5\x2a\x61\x2b\x42\x34\xc6\x9f\xc0\xb4\x11\x48\xfc\x8c\x56\x98\x3d\x90\x6c\x25\xf6\xe0\xba\x2e\x02\x63\xb5\x42\xa8\x12\xf5\x40\x11\xd3\xdc\x7f\xfa\xbc\x74\xbb\xef\x3a\x1a\xe4\xc8\x99\x48\x35\x9c\xf9\x55\x53\x1b\x60\xca\x68\xf7\x84\x34\xb5\x57\xc7\xee\x63\x01\x83\xdf\x8e\xfd\xe8\x13\x07\x9b\x58\x0f\x09\x83\x55\x52\xf8\x35\x66\x34\x4f\x74\x8d\x30\x17\x26\xc2\x11\xe3\xe9\x7a\xf4\xa6\xc4\xf9\x2b\x74\x51\x85\xd7\x69\x2e\x9c\xe5\x32\xfb\xe3\xe6\x69\x85\x13\xf5\x38\x25\xaa\xcc\x69\x70\x28\xa9\x4b\xf9\x2f\x9a\xc4\xb7\x09\xc3\xa3\x53\xb6\xe9\x56\x73\x63\xb3\xf3\x0b\xdc\x3c\xd4\xf5\x90\xec\x7c\x40\x57\x58\xd5\x20\x71\xb6\x88\xde\xcd\xc1\x31\x57\x79\x91\xf4\x78\xad\x75\x76\x70\xbd\x78\x24\xff\x50\x3f\x3e\xbb\x6b\xc2\xc0\xc5\x57\xc9\xd9\xf8\x1a\x7d\x0a\xc3\x67\xe3\x33\x00\x00\xff\xff\x2b\xb5\x57\x66\x4e\x04\x00\x00")
+
+func viewsIndexHtmlBytes() ([]byte, error) {
+ return bindataRead(
+ _viewsIndexHtml,
+ "views/index.html",
+ )
+}
+
+func viewsIndexHtml() (*asset, error) {
+ bytes, err := viewsIndexHtmlBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "views/index.html", size: 1102, mode: os.FileMode(420), modTime: time.Unix(1504143916, 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){
+ "css/index.css": cssIndexCss,
+ "js/index.js": jsIndexJs,
+ "js/smoothscroll.js": jsSmoothscrollJs,
+ "views/index.html": viewsIndexHtml,
+}
+
+// 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{
+ "css": &bintree{nil, map[string]*bintree{
+ "index.css": &bintree{cssIndexCss, map[string]*bintree{}},
+ }},
+ "js": &bintree{nil, map[string]*bintree{
+ "index.js": &bintree{jsIndexJs, map[string]*bintree{}},
+ "smoothscroll.js": &bintree{jsSmoothscrollJs, map[string]*bintree{}},
+ }},
+ "views": &bintree{nil, map[string]*bintree{
+ "index.html": &bintree{viewsIndexHtml, 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/docs/themes/apex/doc.go b/docs/themes/apex/doc.go
new file mode 100644
index 0000000..9d721a9
--- /dev/null
+++ b/docs/themes/apex/doc.go
@@ -0,0 +1,3 @@
+//go:generate go-bindata -modtime 0 -pkg apex -prefix files files/...
+
+package apex
diff --git a/docs/themes/apex/files/css/index.css b/docs/themes/apex/files/css/index.css
new file mode 100644
index 0000000..4c2e3b4
--- /dev/null
+++ b/docs/themes/apex/files/css/index.css
@@ -0,0 +1,280 @@
+:root {
+ --ease: cubic-bezier(.82, 0, .12, 1);
+ --width: 800px;
+ --header-height: 400px;
+
+ --tracking: 0.05rem;
+ --tracking-medium: 0.5rem;
+ --tracking-large: 0.8rem;
+
+ /*--dark: #212529;*/
+ --dark: #000;
+ --blue: #33A9FF;
+ --light-gray: #fafafa;
+
+ --bg: #fff;
+ --fg: #868E96;
+ --fg-dark: #212529;
+
+ --selection-bg: var(--blue);
+ --selection-fg: white;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html, body {
+ font-family: -apple-system, BlinkMacSystemFont,
+ "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans",
+ "Droid Sans", "Helvetica Neue", sans-serif;
+ -webkit-font-smoothing: antialiased;
+ font-size: 16px;
+ font-weight: 300;
+ background: var(--bg);
+ color: var(--fg-dark);
+ line-height: 1;
+ margin: 0;
+ padding: 0;
+}
+
+::selection {
+ background: var(--selection-bg);
+ color: var(--selection-fg);
+}
+
+h1, h2, h3, h4 {
+ margin-top: 75px;
+ margin-bottom: 0;
+ font-size: 1.2rem;
+ font-weight: 600;
+ line-height: 1.5rem;
+ color: var(--fg-dark);
+}
+
+h1 {
+ margin-top: 0;
+ font-size: 1.5rem;
+}
+
+h1 + p {
+ font-size: 1.5rem;
+ line-height: 1.6;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+p {
+ margin: 25px 0;
+ line-height: 1.6;
+ color: var(--fg-color-light);
+}
+
+li strong,
+p strong {
+ color: var(--fg-dark);
+ font-weight: 500;
+}
+
+li a,
+p a {
+ color: var(--fg-dark);
+ font-weight: 400;
+ padding-bottom: 3px;
+ border-bottom: 1px dotted #ddd;
+}
+
+li a:hover,
+p a:hover {
+ color: var(--blue);
+ border-bottom: none;
+}
+
+p a:hover {
+ border-bottom-color: var(--color);
+}
+
+ul {
+ margin: 50px 0 50px 30px;
+ padding: 0;
+}
+
+ul ul {
+ margin: 10px 0 10px 30px;
+}
+
+ul li {
+ margin: 5px 0;
+ color: var(--fg-color-light);
+ line-height: 1.5em;
+}
+
+ul li strong {
+ color: var(--fg-color);
+ font-weight: 500;
+}
+
+pre {
+ background: var(--light-gray);
+ color: var(--dark);
+ padding: 30px;
+ border-radius: 2px;
+ overflow-x: scroll;
+ font: "Source Code Pro", Menlo, monospace;
+ font-size: .8em;
+ line-height: 1.5em;
+}
+
+li > code,
+p > code {
+ border: 1px solid #DEE2E6;
+ font-size: 0.75rem;
+ padding: 3px 10px;
+ border-radius: 3px;
+ white-space: nowrap;
+ font-weight: 600;
+ font-family: inherit;
+}
+
+details > summary {
+ cursor: pointer;
+ outline: none;
+ user-select: none;
+}
+
+details > p {
+ border-left: 3px solid var(--dark);
+ padding-left: 15px;
+}
+
+.Wrapper {
+ display: flex;
+ justify-content: center;
+}
+
+.Container {
+ width: var(--width);
+ display: flex;
+}
+
+.Sidebar {
+ flex: 1 1 auto;
+}
+
+.Content {
+ width: 75%;
+}
+
+.Menu {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 50px;
+ margin-top: calc(var(--header-height) + 10px);
+ color: var(--fg-dark);
+}
+
+.Menu > .item {
+ padding-bottom: 15px;
+}
+
+.Menu > .item > a {
+ position: relative;
+ user-select: none;
+ font-weight: 400;
+ transition: color 200ms;
+ color: var(--fg);
+}
+
+.Menu > .item > a.active {
+ font-weight: 500;
+ color: var(--fg-dark);
+}
+
+.Menu > .item > a:before {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 1px;
+ bottom: -5px;
+ left: 0;
+ background-color: var(--fg-dark);
+ visibility: hidden;
+ transform: scaleX(0);
+ transform-origin: left center;
+ transition: all 250ms var(--ease);
+}
+
+.Menu > .item > a:hover {
+ color: var(--fg-dark);
+}
+
+.Menu > .item > a:hover:before {
+ visibility: visible;
+ transform: scaleX(1);
+}
+
+.Header {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: var(--header-height);
+}
+
+.Page {
+ margin-top: 100px;
+ padding-top: 50px;
+}
+
+.Header + .Page {
+ margin-top: 0;
+ padding-top: 0;
+}
+
+.Title {
+ margin: 5px 0;
+ line-height: 2.2em;
+}
+
+.Title.center {
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+ max-width: 500px;
+}
+
+.Title.margin {
+ margin-bottom: 80px;
+}
+
+.Title > span {
+ display: block;
+}
+
+.Title .subtext {
+ color: var(--fg-color-light);
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ display: none;
+}
+
+.Title .text {
+ letter-spacing: var(--tracking);
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.Title.small .text {
+ font-size: 14px;
+ text-transform: none;
+ letter-spacing: normal;
+ line-height: 2rem;
+}
+
+.Footer {
+ height: 100px;
+}
diff --git a/docs/themes/apex/files/js/index.js b/docs/themes/apex/files/js/index.js
new file mode 100644
index 0000000..c82544b
--- /dev/null
+++ b/docs/themes/apex/files/js/index.js
@@ -0,0 +1,68 @@
+/**
+ * Setup.
+ */
+
+const items = document.querySelectorAll('[id]')
+const links = document.querySelectorAll('.Menu a')
+
+/**
+ * Check if `el` is out out of view.
+ */
+
+function isBelowScroll(el) {
+ return el.getBoundingClientRect().bottom > 0
+}
+
+/**
+ * Activate item `i`.
+ */
+
+function activateItem(i) {
+ links.forEach(e => e.classList.remove('active'))
+ links[i].classList.add('active')
+}
+
+/**
+ * Activate the correct menu item for the
+ * contents in the viewport.
+ */
+
+function activate() {
+ let i = 0
+
+ for (; i < items.length; i++) {
+ if (isBelowScroll(items[i])) {
+ break
+ }
+ }
+
+ activateItem(i)
+}
+
+/**
+ * Activate scroll spy thingy.
+ */
+
+window.addEventListener('scroll', e => activate())
+
+/**
+ * Add smooth scrolling.
+ */
+
+window.addEventListener('click', e => {
+ const el = e.target
+
+ // links only
+ if (el.nodeName != 'A') return
+
+ // url
+ const url = el.getAttribute('href')
+
+ // anchors only
+ if (url[0] != '#') return
+
+ // scrolllllllll
+ document.querySelector(url).scrollIntoView({
+ behavior: 'smooth'
+ })
+})
diff --git a/docs/themes/apex/files/js/smoothscroll.js b/docs/themes/apex/files/js/smoothscroll.js
new file mode 100644
index 0000000..66b7f02
--- /dev/null
+++ b/docs/themes/apex/files/js/smoothscroll.js
@@ -0,0 +1,326 @@
+/*
+ * smoothscroll polyfill - v0.3.5
+ * https://iamdustan.github.io/smoothscroll
+ * 2016 (c) Dustan Kasten, Jeremias Menichelli - MIT License
+ */
+
+(function(w, d, undefined) {
+ 'use strict';
+
+ /*
+ * aliases
+ * w: window global object
+ * d: document
+ * undefined: undefined
+ */
+
+ // polyfill
+ function polyfill() {
+ // return when scrollBehavior interface is supported
+ if ('scrollBehavior' in d.documentElement.style) {
+ return;
+ }
+
+ /*
+ * globals
+ */
+ var Element = w.HTMLElement || w.Element;
+ var SCROLL_TIME = 468;
+
+ /*
+ * object gathering original scroll methods
+ */
+ var original = {
+ scroll: w.scroll || w.scrollTo,
+ scrollBy: w.scrollBy,
+ elScroll: Element.prototype.scroll || scrollElement,
+ scrollIntoView: Element.prototype.scrollIntoView
+ };
+
+ /*
+ * define timing method
+ */
+ var now = w.performance && w.performance.now
+ ? w.performance.now.bind(w.performance) : Date.now;
+
+ /**
+ * changes scroll position inside an element
+ * @method scrollElement
+ * @param {Number} x
+ * @param {Number} y
+ */
+ function scrollElement(x, y) {
+ this.scrollLeft = x;
+ this.scrollTop = y;
+ }
+
+ /**
+ * returns result of applying ease math function to a number
+ * @method ease
+ * @param {Number} k
+ * @returns {Number}
+ */
+ function ease(k) {
+ return 0.5 * (1 - Math.cos(Math.PI * k));
+ }
+
+ /**
+ * indicates if a smooth behavior should be applied
+ * @method shouldBailOut
+ * @param {Number|Object} x
+ * @returns {Boolean}
+ */
+ function shouldBailOut(x) {
+ if (typeof x !== 'object'
+ || x === null
+ || x.behavior === undefined
+ || x.behavior === 'auto'
+ || x.behavior === 'instant') {
+ // first arg not an object/null
+ // or behavior is auto, instant or undefined
+ return true;
+ }
+
+ if (typeof x === 'object'
+ && x.behavior === 'smooth') {
+ // first argument is an object and behavior is smooth
+ return false;
+ }
+
+ // throw error when behavior is not supported
+ throw new TypeError('behavior not valid');
+ }
+
+ /**
+ * finds scrollable parent of an element
+ * @method findScrollableParent
+ * @param {Node} el
+ * @returns {Node} el
+ */
+ function findScrollableParent(el) {
+ var isBody;
+ var hasScrollableSpace;
+ var hasVisibleOverflow;
+
+ do {
+ el = el.parentNode;
+
+ // set condition variables
+ isBody = el === d.body;
+ hasScrollableSpace =
+ el.clientHeight < el.scrollHeight ||
+ el.clientWidth < el.scrollWidth;
+ hasVisibleOverflow =
+ w.getComputedStyle(el, null).overflow === 'visible';
+ } while (!isBody && !(hasScrollableSpace && !hasVisibleOverflow));
+
+ isBody = hasScrollableSpace = hasVisibleOverflow = null;
+
+ return el;
+ }
+
+ /**
+ * self invoked function that, given a context, steps through scrolling
+ * @method step
+ * @param {Object} context
+ */
+ function step(context) {
+ var time = now();
+ var value;
+ var currentX;
+ var currentY;
+ var elapsed = (time - context.startTime) / SCROLL_TIME;
+
+ // avoid elapsed times higher than one
+ elapsed = elapsed > 1 ? 1 : elapsed;
+
+ // apply easing to elapsed time
+ value = ease(elapsed);
+
+ currentX = context.startX + (context.x - context.startX) * value;
+ currentY = context.startY + (context.y - context.startY) * value;
+
+ context.method.call(context.scrollable, currentX, currentY);
+
+ // scroll more if we have not reached our destination
+ if (currentX !== context.x || currentY !== context.y) {
+ w.requestAnimationFrame(step.bind(w, context));
+ }
+ }
+
+ /**
+ * scrolls window with a smooth behavior
+ * @method smoothScroll
+ * @param {Object|Node} el
+ * @param {Number} x
+ * @param {Number} y
+ */
+ function smoothScroll(el, x, y) {
+ var scrollable;
+ var startX;
+ var startY;
+ var method;
+ var startTime = now();
+
+ // define scroll context
+ if (el === d.body) {
+ scrollable = w;
+ startX = w.scrollX || w.pageXOffset;
+ startY = w.scrollY || w.pageYOffset;
+ method = original.scroll;
+ } else {
+ scrollable = el;
+ startX = el.scrollLeft;
+ startY = el.scrollTop;
+ method = scrollElement;
+ }
+
+ // scroll looping over a frame
+ step({
+ scrollable: scrollable,
+ method: method,
+ startTime: startTime,
+ startX: startX,
+ startY: startY,
+ x: x,
+ y: y
+ });
+ }
+
+ /*
+ * ORIGINAL METHODS OVERRIDES
+ */
+
+ // w.scroll and w.scrollTo
+ w.scroll = w.scrollTo = function() {
+ // avoid smooth behavior if not required
+ if (shouldBailOut(arguments[0])) {
+ original.scroll.call(
+ w,
+ arguments[0].left || arguments[0],
+ arguments[0].top || arguments[1]
+ );
+ return;
+ }
+
+ // LET THE SMOOTHNESS BEGIN!
+ smoothScroll.call(
+ w,
+ d.body,
+ ~~arguments[0].left,
+ ~~arguments[0].top
+ );
+ };
+
+ // w.scrollBy
+ w.scrollBy = function() {
+ // avoid smooth behavior if not required
+ if (shouldBailOut(arguments[0])) {
+ original.scrollBy.call(
+ w,
+ arguments[0].left || arguments[0],
+ arguments[0].top || arguments[1]
+ );
+ return;
+ }
+
+ // LET THE SMOOTHNESS BEGIN!
+ smoothScroll.call(
+ w,
+ d.body,
+ ~~arguments[0].left + (w.scrollX || w.pageXOffset),
+ ~~arguments[0].top + (w.scrollY || w.pageYOffset)
+ );
+ };
+
+ // Element.prototype.scroll and Element.prototype.scrollTo
+ Element.prototype.scroll = Element.prototype.scrollTo = function() {
+ // avoid smooth behavior if not required
+ if (shouldBailOut(arguments[0])) {
+ original.elScroll.call(
+ this,
+ arguments[0].left || arguments[0],
+ arguments[0].top || arguments[1]
+ );
+ return;
+ }
+
+ var left = arguments[0].left;
+ var top = arguments[0].top;
+
+ // LET THE SMOOTHNESS BEGIN!
+ smoothScroll.call(
+ this,
+ this,
+ typeof left === 'number' ? left : this.scrollLeft,
+ typeof top === 'number' ? top : this.scrollTop
+ );
+ };
+
+ // Element.prototype.scrollBy
+ Element.prototype.scrollBy = function() {
+ var arg0 = arguments[0];
+
+ if (typeof arg0 === 'object') {
+ this.scroll({
+ left: arg0.left + this.scrollLeft,
+ top: arg0.top + this.scrollTop,
+ behavior: arg0.behavior
+ });
+ } else {
+ this.scroll(
+ this.scrollLeft + arg0,
+ this.scrollTop + arguments[1]
+ );
+ }
+ };
+
+ // Element.prototype.scrollIntoView
+ Element.prototype.scrollIntoView = function() {
+ // avoid smooth behavior if not required
+ if (shouldBailOut(arguments[0])) {
+ original.scrollIntoView.call(
+ this,
+ arguments[0] === undefined ? true : arguments[0]
+ );
+ return;
+ }
+
+ // LET THE SMOOTHNESS BEGIN!
+ var scrollableParent = findScrollableParent(this);
+ var parentRects = scrollableParent.getBoundingClientRect();
+ var clientRects = this.getBoundingClientRect();
+
+ if (scrollableParent !== d.body) {
+ // reveal element inside parent
+ smoothScroll.call(
+ this,
+ scrollableParent,
+ scrollableParent.scrollLeft + clientRects.left - parentRects.left,
+ scrollableParent.scrollTop + clientRects.top - parentRects.top
+ );
+ // reveal parent in viewport
+ w.scrollBy({
+ left: parentRects.left,
+ top: parentRects.top,
+ behavior: 'smooth'
+ });
+ } else {
+ // reveal element in viewport
+ w.scrollBy({
+ left: clientRects.left,
+ top: clientRects.top,
+ behavior: 'smooth'
+ });
+ }
+ };
+ }
+
+ if (typeof exports === 'object') {
+ // commonjs
+ module.exports = { polyfill: polyfill };
+ } else {
+ // global
+ polyfill();
+ }
+})(window, document);
diff --git a/docs/themes/apex/files/views/index.html b/docs/themes/apex/files/views/index.html
new file mode 100644
index 0000000..a46a3d3
--- /dev/null
+++ b/docs/themes/apex/files/views/index.html
@@ -0,0 +1,40 @@
+
+
+
Hello World.
\n", string(b)) + }) + + t.Run("read error", func(t *testing.T) { + in := &errorReader{} + + var meta struct { + Title string `yml:"title"` + } + + out := Markdown(Frontmatter(in, &meta)) + + _, err := ioutil.ReadAll(out) + assert.EqualError(t, err, `boom`) + }) +} diff --git a/markdown.go b/markdown.go new file mode 100644 index 0000000..6a1c45f --- /dev/null +++ b/markdown.go @@ -0,0 +1,26 @@ +package static + +import ( + "io" + "io/ioutil" + + "github.com/russross/blackfriday" +) + +// Markdown returns a reader transformed to markdown. +func Markdown(r io.Reader) io.ReadCloser { + pr, pw := io.Pipe() + + go func() { + b, err := ioutil.ReadAll(r) + if err != nil { + pw.CloseWithError(err) + return + } + + pw.Write(blackfriday.MarkdownCommon(b)) + pw.Close() + }() + + return pr +} diff --git a/markdown_test.go b/markdown_test.go new file mode 100644 index 0000000..23031b1 --- /dev/null +++ b/markdown_test.go @@ -0,0 +1,34 @@ +package static + +import ( + "errors" + "io/ioutil" + "strings" + "testing" + + "github.com/tj/assert" +) + +type errorReader struct{} + +func (r *errorReader) Read(b []byte) (int, error) { + return 0, errors.New("boom") +} + +func TestMarkdown(t *testing.T) { + t.Run("valid", func(t *testing.T) { + in := strings.NewReader(`Hello __World__.`) + out := Markdown(in) + b, err := ioutil.ReadAll(out) + assert.NoError(t, err, "reading") + assert.NoError(t, out.Close()) + assert.Equal(t, "Hello World.
\n", string(b)) + }) + + t.Run("read error", func(t *testing.T) { + in := &errorReader{} + out := Markdown(in) + _, err := ioutil.ReadAll(out) + assert.EqualError(t, err, `boom`) + }) +}