From 60ec92b2822a74c66f9868f6820f3e5d3bc4e865 Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Thu, 31 Aug 2017 10:10:33 -0700 Subject: [PATCH] Initial commit --- .gitignore | 1 + Makefile | 6 + Readme.md | 13 + cmd/static-docs/main.go | 57 ++++ docs/docs.go | 182 ++++++++++++ docs/themes/apex/apex.go | 3 + docs/themes/apex/bindata.go | 310 ++++++++++++++++++++ docs/themes/apex/doc.go | 3 + docs/themes/apex/files/css/index.css | 280 +++++++++++++++++++ docs/themes/apex/files/js/index.js | 68 +++++ docs/themes/apex/files/js/smoothscroll.js | 326 ++++++++++++++++++++++ docs/themes/apex/files/views/index.html | 40 +++ frontmatter.go | 32 +++ frontmatter_test.go | 43 +++ markdown.go | 26 ++ markdown_test.go | 34 +++ 16 files changed, 1424 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 Readme.md create mode 100644 cmd/static-docs/main.go create mode 100644 docs/docs.go create mode 100644 docs/themes/apex/apex.go create mode 100644 docs/themes/apex/bindata.go create mode 100644 docs/themes/apex/doc.go create mode 100644 docs/themes/apex/files/css/index.css create mode 100644 docs/themes/apex/files/js/index.js create mode 100644 docs/themes/apex/files/js/smoothscroll.js create mode 100644 docs/themes/apex/files/views/index.html create mode 100644 frontmatter.go create mode 100644 frontmatter_test.go create mode 100644 markdown.go create mode 100644 markdown_test.go 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. + +--- + +[![GoDoc](https://godoc.org/github.com/apex/static?status.svg)](https://godoc.org/github.com/apex/static) +![](https://img.shields.io/badge/license-MIT-blue.svg) +![](https://img.shields.io/badge/status-stable-green.svg) + + 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 @@ + + + + + {{.Title}} | {{.Subtitle}} + + + +
+
+ + +
+
+
+ {{.Title}} + {{.Subtitle}} +
+
+ {{range .Pages}} +
+

{{.Title}}

+ {{.Content}} +
+ {{end}} + + + +
+
+ + diff --git a/frontmatter.go b/frontmatter.go new file mode 100644 index 0000000..426bbfa --- /dev/null +++ b/frontmatter.go @@ -0,0 +1,32 @@ +package static + +import ( + "io" + "io/ioutil" + + "github.com/tj/front" +) + +// Frontmatter returns a reader of contents and unmarshals frontmatter to v. +func Frontmatter(r io.Reader, v interface{}) io.ReadCloser { + pr, pw := io.Pipe() + + go func() { + b, err := ioutil.ReadAll(r) + if err != nil { + pw.CloseWithError(err) + return + } + + s, err := front.Unmarshal(b, v) + if err != nil { + pw.CloseWithError(err) + return + } + + pw.Write(s) + pw.Close() + }() + + return pr +} diff --git a/frontmatter_test.go b/frontmatter_test.go new file mode 100644 index 0000000..4fea511 --- /dev/null +++ b/frontmatter_test.go @@ -0,0 +1,43 @@ +package static + +import ( + "io/ioutil" + "strings" + "testing" + + "github.com/tj/assert" +) + +func TestFrontmatter(t *testing.T) { + t.Run("valid", func(t *testing.T) { + in := strings.NewReader(`--- +title: Something +--- + +Hello __World__. + `) + + var meta struct { + Title string `yml:"title"` + } + + out := Markdown(Frontmatter(in, &meta)) + 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{} + + 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`) + }) +}