generator.go 3.23 KB
Newer Older
1 2 3
package markdown

import (
4
	"crypto/md5"
5 6 7 8 9 10 11 12 13 14 15
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/mholt/caddy/middleware"
)

16 17 18
// GenerateStatic generate static files and link index from markdowns.
// It only generates static files if it is enabled (cfg.StaticDir
// must be set).
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
func GenerateStatic(md Markdown, cfg *Config) error {
	generated, err := generateLinks(md, cfg)
	if err != nil {
		return err
	}

	// No new file changes, return.
	if !generated {
		return nil
	}

	// If static site generation is enabled.
	if cfg.StaticDir != "" {
		if err := generateStaticHTML(md, cfg); err != nil {
			return err
		}
	}
	return nil
}

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.
// It returns if generation is done and any error that occurred.
func generateLinks(md Markdown, cfg *Config) (bool, 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()
			// another goroutine has done the generation.
			return false, g.lastErr
		}
	}

	g := &linkGen{}
	generator.gens[cfg] = g
	generator.Unlock()

	generated := g.generateLinks(md, cfg)
	g.discardWaiters()
	return generated, g.lastErr
}

// generateStaticFiles generates static html files from markdowns.
func generateStaticHTML(md Markdown, cfg *Config) error {
	// If generated site already exists, clear it out
	_, err := os.Stat(cfg.StaticDir)
	if err == nil {
		err := os.RemoveAll(cfg.StaticDir)
		if err != nil {
			return err
		}
	}

	fp := filepath.Join(md.Root, cfg.PathScope)

	return 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

				// Generate the static file
				ctx := middleware.Context{Root: md.FileSys}
105
				_, err = md.Process(cfg, reqPath, body, ctx)
106 107 108 109 110 111 112 113 114 115 116 117
				if err != nil {
					return err
				}

				break // don't try other file extensions
			}
		}
		return nil
	})
}

// computeDirHash computes an hash on static directory of c.
118
func computeDirHash(md Markdown, c *Config) (string, error) {
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
	dir := filepath.Join(md.Root, c.PathScope)
	if _, err := os.Stat(dir); err != nil {
		return "", err
	}

	hashString := ""
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() && c.IsValidExt(filepath.Ext(path)) {
			hashString += fmt.Sprintf("%v%v%v%v", info.ModTime(), info.Name(), info.Size(), path)
		}
		return nil
	})
	if err != nil {
		return "", err
	}

135
	sum := md5.Sum([]byte(hashString))
136 137
	return hex.EncodeToString(sum[:]), nil
}