|
@@ -1,16 +1,15 @@
|
|
|
package main
|
|
|
|
|
|
import (
|
|
|
+ "bufio"
|
|
|
"html/template"
|
|
|
+ "io/ioutil"
|
|
|
"os"
|
|
|
"path/filepath"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
|
|
|
- // "bufio"
|
|
|
- // "io/ioutil"
|
|
|
- // "path"
|
|
|
-
|
|
|
- // "github.com/russross/blackfriday"
|
|
|
+ "github.com/russross/blackfriday"
|
|
|
|
|
|
"github.com/cdelorme/go-log"
|
|
|
)
|
|
@@ -24,137 +23,152 @@ type Staticmd struct {
|
|
|
Relative bool
|
|
|
MaxParallelism int
|
|
|
Version string
|
|
|
- Navigation []string
|
|
|
Pages []string
|
|
|
- Subdirectories map[string][]string
|
|
|
- Indexes map[string][]string
|
|
|
}
|
|
|
|
|
|
-// search for readme or fallback to index
|
|
|
-func (staticmd *Staticmd) index(pages []string) string {
|
|
|
- for _, page := range pages {
|
|
|
- if n := basename(page); n == "readme" {
|
|
|
- return n
|
|
|
+// convert markdown input path to html output path
|
|
|
+func (staticmd *Staticmd) ior(path string) string {
|
|
|
+ return strings.TrimSuffix(strings.Replace(path, staticmd.Input, staticmd.Output, 1), filepath.Ext(path)) + ".html"
|
|
|
+}
|
|
|
+
|
|
|
+// for relative rendering generate relative depth string, or fallback to empty
|
|
|
+func (staticmd *Staticmd) depth(path string) string {
|
|
|
+ if staticmd.Relative {
|
|
|
+ if rel, err := filepath.Rel(filepath.Dir(path), staticmd.Output); err == nil {
|
|
|
+ return rel
|
|
|
}
|
|
|
}
|
|
|
- return "index"
|
|
|
+ return ""
|
|
|
}
|
|
|
|
|
|
-func (staticmd *Staticmd) Walk(path string, file os.FileInfo, err error) error {
|
|
|
- if file == nil {
|
|
|
- } else if file.IsDir() {
|
|
|
-
|
|
|
- // prepare path to create for matching output
|
|
|
- mkd := filepath.Join(staticmd.Output, strings.TrimPrefix(path, staticmd.Input))
|
|
|
-
|
|
|
- // include parent output path
|
|
|
- if path == staticmd.Input {
|
|
|
- mkd = staticmd.Output
|
|
|
- } else {
|
|
|
-
|
|
|
- // note subdirectory for indexing
|
|
|
- if _, ok := staticmd.Subdirectories[filepath.Dir(path)]; !ok {
|
|
|
- staticmd.Subdirectories[filepath.Dir(path)] = make([]string, 0)
|
|
|
- }
|
|
|
- staticmd.Subdirectories[filepath.Dir(path)] = append(staticmd.Subdirectories[filepath.Dir(path)], path)
|
|
|
- }
|
|
|
+// get link to file, with support for relative path linking
|
|
|
+func (staticmd *Staticmd) link(path string) string {
|
|
|
+ if staticmd.Relative {
|
|
|
+ return strings.TrimPrefix(path, filepath.Dir(path))
|
|
|
+ }
|
|
|
+ return strings.TrimPrefix(path, staticmd.Output)
|
|
|
+}
|
|
|
|
|
|
- // create all matching output paths
|
|
|
- staticmd.Logger.Debug("creating matching output path: %s", mkd)
|
|
|
- if err := os.MkdirAll(mkd, 0770); err != nil {
|
|
|
- staticmd.Logger.Error("failed to create matching output path: %s, %s", path, err)
|
|
|
- }
|
|
|
+// walk the directories and build a list of pages
|
|
|
+func (staticmd *Staticmd) Walk(path string, file os.FileInfo, err error) error {
|
|
|
|
|
|
+ // only pay attention to files with a size greater than 0
|
|
|
+ if file == nil {
|
|
|
} else if file.Mode().IsRegular() && file.Size() > 0 {
|
|
|
|
|
|
- // append all markdown files to our pages list
|
|
|
+ // only add markdown files to our pages array (.md, .mkd, .markdown)
|
|
|
if strings.HasSuffix(path, ".md") || strings.HasSuffix(path, ".mkd") || strings.HasSuffix(path, ".markdown") {
|
|
|
staticmd.Pages = append(staticmd.Pages, path)
|
|
|
-
|
|
|
- dir := filepath.Dir(path)
|
|
|
-
|
|
|
- // append to navigation, index
|
|
|
- if dir == staticmd.Input {
|
|
|
- staticmd.Navigation = append(staticmd.Navigation, path)
|
|
|
- } else {
|
|
|
-
|
|
|
- // prepare index container
|
|
|
- if _, ok := staticmd.Indexes[dir]; !ok {
|
|
|
- staticmd.Indexes[dir] = make([]string, 0)
|
|
|
- }
|
|
|
- staticmd.Indexes[dir] = append(staticmd.Indexes[dir], dir+basename(strings.TrimPrefix(path, staticmd.Input))+".html")
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
+// build output concurrently to many pages
|
|
|
func (staticmd *Staticmd) Multi() {
|
|
|
|
|
|
- // test filepath.Rel()
|
|
|
-
|
|
|
- // iterate all subdirectories to find index/readme and append to
|
|
|
- // for i, _ := range staticmd.Subdirectories {
|
|
|
- // for _, d := range staticmd.Subdirectories[i] {
|
|
|
-
|
|
|
- // // can only append to indexes that exist
|
|
|
- // if _, ok := staticmd.Indexes[filepath.Dir(staticmd.Subdirectories[i][d])]; !ok {
|
|
|
- // continue
|
|
|
- // }
|
|
|
-
|
|
|
- // // determine whether to append relative path or just the folder
|
|
|
- // if staticmd.Relative {
|
|
|
-
|
|
|
- // // trim input path, and append to indexes as .html
|
|
|
- // idx := staticmd.index(staticmd.Subdirectories[i][d])
|
|
|
- // staticmd.Subdirectories[i][d]+idx+".html"
|
|
|
-
|
|
|
- // // determine whether index or readme exists, assume index
|
|
|
- // } else {
|
|
|
- // // simply append `subfolder/`
|
|
|
- // }
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // rebuild navigation pathing
|
|
|
- // for i, page := range staticmd.Navigation {
|
|
|
- // if staticmd.Relative {
|
|
|
- // staticmd.Navigation[i] = strings.TrimPrefix(strings.TrimPrefix(page, staticmd.Input), "/")
|
|
|
- // } else if basename(page) == "index" {
|
|
|
- // staticmd.Navigation[i] = "/"
|
|
|
- // } else {
|
|
|
- // staticmd.Navigation[i] = strings.TrimPrefix(page, staticmd.Input)
|
|
|
- // }
|
|
|
- // }
|
|
|
+ // prepare navigation storage
|
|
|
+ navigation := make(map[string][]Navigation)
|
|
|
|
|
|
- // debug output
|
|
|
- staticmd.Logger.Debug("Navigation: %+v", staticmd.Navigation)
|
|
|
- staticmd.Logger.Debug("Indexes: %+v", staticmd.Indexes)
|
|
|
+ // loop pages to build table of contents
|
|
|
+ for i, _ := range staticmd.Pages {
|
|
|
|
|
|
- // concurrently build pages
|
|
|
+ // build output directory
|
|
|
+ out := staticmd.ior(staticmd.Pages[i])
|
|
|
+ dir := filepath.Dir(staticmd.ior(out))
|
|
|
|
|
|
-}
|
|
|
+ // build indexes first-match
|
|
|
+ if _, ok := navigation[dir]; !ok {
|
|
|
+ navigation[dir] = make([]Navigation, 0)
|
|
|
|
|
|
-func (staticmd *Staticmd) Single() {
|
|
|
+ // create output directory for when we create files
|
|
|
+ if ok, _ := exists(dir); !ok {
|
|
|
+ if err := os.MkdirAll(dir, 0770); err != nil {
|
|
|
+ staticmd.Logger.Error("Failed to create path: %s, %s", dir, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // open single index.html for building
|
|
|
- file := staticmd.Output
|
|
|
+ // create a new navigation object
|
|
|
+ nav := Navigation{
|
|
|
+ Name: basename(out),
|
|
|
+ Link: staticmd.link(out),
|
|
|
+ }
|
|
|
|
|
|
- // prepare index
|
|
|
- index := make([]string, 0)
|
|
|
+ // append files to their respective directories
|
|
|
+ navigation[dir] = append(navigation[dir], nav)
|
|
|
+ }
|
|
|
|
|
|
- // prepare markdown thingy?
|
|
|
- // content := template.HTML(blackfriday.MarkdownCommon(markdown))
|
|
|
+ // @todo second cycle to clarify whether index or readme for table-of-contents
|
|
|
|
|
|
- // synchronously build one output file
|
|
|
- for i, _ := range staticmd.Pages {
|
|
|
+ // debug output
|
|
|
+ staticmd.Logger.Debug("Navigation: %+v", navigation)
|
|
|
+
|
|
|
+ // prepare waitgroup, bufferer channel, and add number of async handlers to wg
|
|
|
+ var wg sync.WaitGroup
|
|
|
+ pages := make(chan string, staticmd.MaxParallelism)
|
|
|
+ wg.Add(staticmd.MaxParallelism)
|
|
|
+
|
|
|
+ // prepare workers
|
|
|
+ for i := 0; i < staticmd.MaxParallelism; i++ {
|
|
|
+ go func() {
|
|
|
+ defer wg.Done()
|
|
|
+
|
|
|
+ // iterate supplied pages
|
|
|
+ for p := range pages {
|
|
|
+
|
|
|
+ // acquire output filepath
|
|
|
+ out := staticmd.ior(p)
|
|
|
+ // dir := filepath.Dir(out)
|
|
|
+
|
|
|
+ // prepare a new page object for our template to render
|
|
|
+ page := Page{
|
|
|
+ Name: basename(p),
|
|
|
+ Version: staticmd.Version,
|
|
|
+ Nav: navigation[staticmd.Output],
|
|
|
+ Depth: staticmd.depth(p),
|
|
|
+ }
|
|
|
+
|
|
|
+ // read in page text
|
|
|
+ if markdown, err := ioutil.ReadFile(p); err == nil {
|
|
|
|
|
|
- // create index record
|
|
|
+ // conditionally prepend table of contents?
|
|
|
|
|
|
- // staticmd.Pages[i]
|
|
|
- // append each file to content
|
|
|
+ page.Content = template.HTML(blackfriday.MarkdownCommon(markdown))
|
|
|
+ } else {
|
|
|
+ staticmd.Logger.Error("failed to read file: %s, %s", p, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // translate input path to output path & create a write context
|
|
|
+ if f, err := os.Create(out); err == nil {
|
|
|
+ fb := bufio.NewWriter(f)
|
|
|
+ defer fb.Flush()
|
|
|
+ defer f.Close()
|
|
|
+
|
|
|
+ // attempt to use template to write output with page context
|
|
|
+ if e := staticmd.Template.Execute(fb, page); e != nil {
|
|
|
+ staticmd.Logger.Error("Failed to write template: %s, %s", out, e)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ staticmd.Logger.Error("failed to create new file: %s, %s", out, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ }
|
|
|
+
|
|
|
+ // send pages to workers for async rendering
|
|
|
+ for _, page := range staticmd.Pages {
|
|
|
+ pages <- page
|
|
|
}
|
|
|
|
|
|
- // append index
|
|
|
+ // close channel and wait for async to finish before continuing
|
|
|
+ close(pages)
|
|
|
+ wg.Wait()
|
|
|
+}
|
|
|
+
|
|
|
+// build output synchronously to a single page
|
|
|
+func (staticmd *Staticmd) Single() {
|
|
|
+
|
|
|
+ // still determining strategy
|
|
|
|
|
|
}
|