Browse Source

completed concurrent file rendering for multiple outputs

finished creating top-level navigation

still working on per-folder indexes

currently debugging issue with top levle file creation failing
Casey DeLorme 10 năm trước cách đây
mục cha
commit
2e8dcd8a3e
3 tập tin đã thay đổi với 138 bổ sung105 xóa
  1. 6 0
      navigation.go
  2. 13 0
      page.go
  3. 119 105
      staticmd.go

+ 6 - 0
navigation.go

@@ -0,0 +1,6 @@
+package main
+
+type Navigation struct {
+	Link string
+	Name string
+}

+ 13 - 0
page.go

@@ -0,0 +1,13 @@
+package main
+
+import (
+	"html/template"
+)
+
+type Page struct {
+	Name    string
+	Version string
+	Nav     []Navigation
+	Depth   string
+	Content template.HTML
+}

+ 119 - 105
staticmd.go

@@ -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
 
 }