mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
ff28bc8b0a
Also, summary truncated at nearest space instead of middle of word, and code spans become part of summary.
169 lines
3.4 KiB
Go
169 lines
3.4 KiB
Go
package markdown
|
|
|
|
import (
|
|
"bytes"
|
|
"io/ioutil"
|
|
"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
|
|
}
|
|
|
|
func (l *linkGen) generateLinks(md Markdown, cfg *Config) {
|
|
l.Lock()
|
|
l.generating = true
|
|
l.Unlock()
|
|
|
|
fp := filepath.Join(md.Root, cfg.PathScope)
|
|
|
|
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) {
|
|
// 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 = "/" + 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, PlaintextRenderer{}, 0)),
|
|
})
|
|
|
|
break // don't try other file extensions
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
// sort by newest date
|
|
sort.Sort(byDate(cfg.Links))
|
|
cfg.Unlock()
|
|
|
|
l.Lock()
|
|
l.generating = false
|
|
l.Unlock()
|
|
}
|
|
|
|
type linkGenerator struct {
|
|
gens map[*Config]*linkGen
|
|
sync.Mutex
|
|
}
|
|
|
|
var generator = linkGenerator{gens: make(map[*Config]*linkGen)}
|
|
|
|
// GenerateLinks generates links to all markdown files ordered by newest date.
|
|
// This blocks until link generation is done. When called by multiple goroutines,
|
|
// the first caller starts the generation and others only wait.
|
|
func GenerateLinks(md Markdown, cfg *Config) error {
|
|
generator.Lock()
|
|
|
|
// if link generator exists for config and running, wait.
|
|
if g, ok := generator.gens[cfg]; ok {
|
|
if g.started() {
|
|
g.addWaiter()
|
|
generator.Unlock()
|
|
g.Wait()
|
|
return g.lastErr
|
|
}
|
|
}
|
|
|
|
g := &linkGen{}
|
|
generator.gens[cfg] = g
|
|
generator.Unlock()
|
|
|
|
g.generateLinks(md, cfg)
|
|
g.discardWaiters()
|
|
return g.lastErr
|
|
}
|