mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	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)
		
			
				
	
	
		
			168 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | 
						|
}
 |