package static
import (
"bufio"
"html/template"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
var readfile = ioutil.ReadFile
var create = os.Create
var mkdirall = os.MkdirAll
var parseFiles = template.ParseFiles
var walk = filepath.Walk
type executor interface {
Execute(io.Writer, interface{}) error
}
type operation func([]byte) []byte
type Markdown struct {
Book bool `json:"book,omitempty"`
Input string `json:"input,omitempty"`
Output string `json:"output,omitempty"`
Relative bool `json:"relative,omitempty"`
TemplateFile string `json:"template,omitempty"`
L logger
version string
pages []string
template executor
}
func (g *Markdown) ior(path string) string {
return strings.TrimSuffix(strings.Replace(path, g.Input, g.Output, 1), filepath.Ext(path)) + ".html"
}
func (g *Markdown) depth(path string) string {
if g.Relative {
if rel, err := filepath.Rel(filepath.Dir(path), g.Output); err == nil {
return rel + string(os.PathSeparator)
}
}
return ""
}
func (g *Markdown) walk(path string, file os.FileInfo, err error) error {
if file != nil && file.Mode().IsRegular() && file.Size() > 0 && isMarkdown(path) {
g.pages = append(g.pages, path)
}
return err
}
func (g *Markdown) multi(run operation) error {
navi := make(map[string][]navigation)
var err error
for i, _ := range g.pages {
out := g.ior(g.pages[i])
dir := filepath.Dir(g.ior(out))
nav := navigation{}
if filepath.Dir(out) != g.Output && strings.ToLower(basename(out)) == "index" {
nav.Title = basename(dir)
if g.Relative {
nav.Link = filepath.Join(strings.TrimPrefix(dir, filepath.Dir(dir)+string(os.PathSeparator)), filepath.Base(out))
} else {
nav.Link = strings.TrimPrefix(dir, g.Output) + string(os.PathSeparator)
}
dir = filepath.Dir(dir)
} else {
nav.Title = basename(out)
if g.Relative {
nav.Link = strings.TrimPrefix(out, filepath.Dir(out)+string(os.PathSeparator))
} else {
nav.Link = strings.TrimPrefix(out, g.Output)
}
}
if _, ok := navi[dir]; !ok {
navi[dir] = make([]navigation, 0)
if ok, _ := exists(dir); !ok {
if err = mkdirall(dir, 0770); err != nil {
g.L.Error("failed to create path: %s, %s", dir, err)
}
}
}
navi[dir] = append(navi[dir], nav)
}
for _, p := range g.pages {
var markdown []byte
if markdown, err = readfile(p); err != nil {
g.L.Error("failed to read file: %s, %s", p, err)
return err
}
out := g.ior(p)
dir := filepath.Dir(out)
page := page{
Name: basename(p),
Version: g.version,
Nav: navi[g.Output],
Depth: g.depth(out),
}
if dir != g.Output && strings.ToLower(basename(p)) == "index" {
toc := "\n## Table of Contents:\n\n"
for i, _ := range navi[dir] {
toc = toc + "- [" + navi[dir][i].Title + "](" + navi[dir][i].Link + ")\n"
}
g.L.Debug("table of contents for %s, %s", out, toc)
markdown = append([]byte(toc), markdown...)
}
page.Content = template.HTML(run(markdown))
var f *os.File
if f, err = create(out); err != nil {
g.L.Error("%s\n", err)
return err
}
defer f.Close()
fb := bufio.NewWriter(f)
defer fb.Flush()
if err = g.template.Execute(fb, page); err != nil {
g.L.Error("%s\n", err)
}
}
return err
}
func (g *Markdown) single(run operation) error {
content := make([]byte, 0)
toc := "\n"
previous_depth := 0
var err error
for _, p := range g.pages {
shorthand := strings.TrimPrefix(p, g.Input+string(os.PathSeparator))
depth := strings.Count(shorthand, string(os.PathSeparator))
if depth > previous_depth {
toc = toc + strings.Repeat("\t", depth-1) + "- " + basename(filepath.Dir(p)) + "\n"
}
anchor := strings.Replace(shorthand, string(os.PathSeparator), "-", -1)
toc = toc + strings.Repeat("\t", depth) + "- [" + basename(p) + "](#" + anchor + ")\n"
var markdown []byte
if markdown, err = readfile(p); err != nil {
g.L.Error("failed to read file: %s (%s)", p, err)
continue
}
markdown = append([]byte("\n\n\n"), markdown...)
markdown = append(markdown, []byte("\n[back to top](#top)\n\n")...)
content = append(content, markdown...)
previous_depth = depth
}
content = append([]byte(toc), content...)
if ok, _ := exists(g.Output); !ok {
if err = mkdirall(g.Output, 0770); err != nil {
g.L.Error("failed to create path: %s (%s)", g.Output, err)
return err
}
}
page := page{
Version: g.version,
Content: template.HTML(run(content)),
}
out := filepath.Join(g.Output, "index.html")
var f *os.File
if f, err = create(out); err != nil {
g.L.Error("%s\n", err)
return err
}
defer f.Close()
fb := bufio.NewWriter(f)
defer fb.Flush()
if err = g.template.Execute(fb, page); err != nil {
g.L.Error("%s\n", err)
}
return err
}
func (g *Markdown) Generate(run operation) error {
var err error
if g.template, err = parseFiles(g.TemplateFile); err != nil {
g.L.Error("%s\n", err)
return err
}
g.version = version(g.Input)
g.Input, _ = filepath.Abs(g.Input)
g.Output, _ = filepath.Abs(g.Output)
g.Input = filepath.Clean(g.Input)
g.Output = filepath.Clean(g.Output)
if err := walk(g.Input, g.walk); err != nil {
g.L.Error("%s\n", err)
return err
}
g.L.Debug("Markdown state: %+v", g)
if g.Book {
return g.single(run)
}
return g.multi(run)
}