caddy/middleware/markdown/page.go
Zac Bergquist e158cda057 Fix test failures on Windows.
Most of the Windows test failures are due to the path separator not being "/".  The general approach I took here was to keep paths in "URL form" (ie using "/" separators) as much as possible, and only convert to native paths when we attempt to open a file.  This will allow the most consistency between different host OS.  For example, data structures that store paths still store them with "/" delimiters.  Functions that accepted paths as input and return them as outputs still use "/".

There are still a few test failures that need to be sorted out.

- config/setup/TestRoot (I hear this has already been fixed by someone else)
- middleware/basicauth/TestBrowseTemplate and middleware/templates/Test (a line endings issue that I'm still working through)
2015-10-13 19:49:53 -04:00

168 lines
3.3 KiB
Go

package markdown
import (
"bytes"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
"github.com/russross/blackfriday"
)
const (
// Date format YYYY-MM-DD HH:MM:SS
timeLayout = `2006-01-02 15:04:05`
// Maximum length of page summary.
summaryLen = 500
)
// PageLink represents a statically generated markdown page.
type PageLink struct {
Title string
Summary string
Date time.Time
URL string
}
// byDate sorts PageLink by newest date to oldest.
type byDate []PageLink
func (p byDate) Len() int { return len(p) }
func (p byDate) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p byDate) Less(i, j int) bool { return p[i].Date.After(p[j].Date) }
type linkGen struct {
generating bool
waiters int
lastErr error
sync.RWMutex
sync.WaitGroup
}
func (l *linkGen) addWaiter() {
l.WaitGroup.Add(1)
l.waiters++
}
func (l *linkGen) discardWaiters() {
l.Lock()
defer l.Unlock()
for i := 0; i < l.waiters; i++ {
l.Done()
}
}
func (l *linkGen) started() bool {
l.RLock()
defer l.RUnlock()
return l.generating
}
// generateLinks generate links to markdown files if there are file changes.
// It returns true when generation is done and false otherwise.
func (l *linkGen) generateLinks(md Markdown, cfg *Config) bool {
l.Lock()
l.generating = true
l.Unlock()
fp := filepath.Join(md.Root, cfg.PathScope) // path to scan for .md files
// If the file path to scan for Markdown files (fp) does
// not exist, there are no markdown files to scan for.
if _, err := os.Stat(fp); os.IsNotExist(err) {
l.Lock()
l.lastErr = err
l.generating = false
l.Unlock()
return false
}
hash, err := computeDirHash(md, cfg)
// same hash, return.
if err == nil && hash == cfg.linksHash {
l.Lock()
l.generating = false
l.Unlock()
return false
} else if err != nil {
log.Println("Hash error (markdown):", err)
}
cfg.Links = []PageLink{}
cfg.Lock()
l.lastErr = filepath.Walk(fp, func(path string, info os.FileInfo, err error) error {
for _, ext := range cfg.Extensions {
if info.IsDir() || !strings.HasSuffix(info.Name(), ext) {
continue
}
// Load the file
body, err := ioutil.ReadFile(path)
if err != nil {
return err
}
// Get the relative path as if it were a HTTP request,
// then prepend with "/" (like a real HTTP request)
reqPath, err := filepath.Rel(md.Root, path)
if err != nil {
return err
}
reqPath = "/" + filepath.ToSlash(reqPath)
parser := findParser(body)
if parser == nil {
// no metadata, ignore.
continue
}
summary, err := parser.Parse(body)
if err != nil {
return err
}
// truncate summary to maximum length
if len(summary) > summaryLen {
summary = summary[:summaryLen]
// trim to nearest word
lastSpace := bytes.LastIndex(summary, []byte(" "))
if lastSpace != -1 {
summary = summary[:lastSpace]
}
}
metadata := parser.Metadata()
cfg.Links = append(cfg.Links, PageLink{
Title: metadata.Title,
URL: reqPath,
Date: metadata.Date,
Summary: string(blackfriday.Markdown(summary, SummaryRenderer{}, 0)),
})
break // don't try other file extensions
}
return nil
})
// sort by newest date
sort.Sort(byDate(cfg.Links))
cfg.linksHash = hash
cfg.Unlock()
l.Lock()
l.generating = false
l.Unlock()
return true
}