1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-11-30 08:08:59 +01:00

Implement sass

This commit is contained in:
Oliver Steele 2017-06-16 13:47:11 -04:00
parent 6a4b048a09
commit 1d9fa428bc
7 changed files with 162 additions and 56 deletions

View File

@ -50,7 +50,7 @@ func (s *Site) Build() (count int, err error) {
// WritePage writes a page to the destination directory.
func (s *Site) WritePage(page Page) error {
src := filepath.Join(s.Source, page.Path())
dst := filepath.Join(s.Destination, page.Path())
dst := filepath.Join(s.Destination, page.Permalink())
if !page.Static() && filepath.Ext(dst) == "" {
dst = filepath.Join(dst, "/index.html")
}

View File

@ -17,20 +17,35 @@ var nonAlphanumericSequenceMatcher = regexp.MustCompile(`[^[:alnum:]]+`)
// copyFile copies from file src to dst. It's not atomic and doesn't copy permissions or metadata.
// This is sufficient for its use within this package.
func copyFile(dst, src string, perm os.FileMode) error {
inf, err := os.Open(src)
in, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close() // nolint: errcheck, gas
outf, err := os.Create(dst)
defer in.Close() // nolint: errcheck, gas
out, err := os.Create(dst)
if err != nil {
return err
}
if _, err = io.Copy(outf, inf); err != nil {
if _, err = io.Copy(out, in); err != nil {
_ = os.Remove(dst) // nolint: gas
return err
}
return outf.Close()
return out.Close()
}
// ReadFileMagic returns the first four bytes of the file, with final '\r' replaced by '\n'.
func ReadFileMagic(p string) (data []byte, err error) {
f, err := os.Open(p)
if err != nil {
return
}
defer f.Close()
data = make([]byte, 4)
_, err = f.Read(data)
if data[3] == '\r' {
data[3] = '\n'
}
return
}
// Bool returns m[k] if it's a bool; else defaultValue.

View File

@ -49,7 +49,10 @@ func XMLEscapeFactory(parameters []core.Value) core.Filter {
panic("The xml_escape filter doesn't accept parameters")
}
return func(input interface{}, data map[string]interface{}) interface{} {
s, err := xml.Marshal(input) // FIXME can't handle maps
s, err := xml.Marshal(input)
// TODO can't handle maps
// eval https://github.com/clbanning/mxj
// adapt https://stackoverflow.com/questions/30928770/marshall-map-to-xml-in-go
if err != nil {
panic(err)
}

103
page.go
View File

@ -61,30 +61,28 @@ func (p *pageFields) setPermalink(permalink string) {
}
// ReadPage reads a Page from a file, using defaults as the default front matter.
func ReadPage(path string, defaults VariableMap) (p Page, err error) {
// TODO only read the first four bytes unless it's dynamic
source, err := ioutil.ReadFile(filepath.Join(site.Source, path))
func ReadPage(rel string, defaults VariableMap) (p Page, err error) {
magic, err := ReadFileMagic(filepath.Join(site.Source, rel))
if err != nil {
return nil, err
return
}
data := pageFields{
path: path,
fields := pageFields{
path: rel,
frontMatter: defaults,
}
if string(source[:4]) == "---\n" {
p, err = makeDynamicPage(&data, source)
if string(magic) == "---\n" {
p, err = readDynamicPage(&fields, rel)
} else {
p = &StaticPage{data}
p = &StaticPage{fields}
}
if p != nil {
// Compute this after creating the page, to pick up the the front matter.
pattern := data.frontMatter.String("permalink", ":path:output_ext")
permalink, err := expandPermalinkPattern(pattern, data.path, data.frontMatter)
// Compute this after creating the page, in order to pick up the front matter.
pattern := fields.frontMatter.String("permalink", ":path:output_ext")
permalink, err := expandPermalinkPattern(pattern, rel, fields.frontMatter)
if err != nil {
return nil, err
}
println(path, pattern, permalink)
p.setPermalink(permalink)
}
return
@ -150,17 +148,22 @@ type DynamicPage struct {
// Static returns a bool indicating that the page is a not static page.
func (p *DynamicPage) Static() bool { return false }
func makeDynamicPage(data *pageFields, source []byte) (*DynamicPage, error) {
frontMatter, err := readFrontMatter(&source)
func readDynamicPage(fields *pageFields, rel string) (p *DynamicPage, err error) {
data, err := ioutil.ReadFile(filepath.Join(site.Source, rel))
if err != nil {
return nil, err
return
}
data.frontMatter = mergeVariableMaps(data.frontMatter, frontMatter)
p := &DynamicPage{
pageFields: *data,
Content: source,
data = bytes.Replace(data, []byte("\r"), []byte("\n"), -1)
frontMatter, err := readFrontMatter(&data)
if err != nil {
return
}
return p, nil
fields.frontMatter = mergeVariableMaps(fields.frontMatter, frontMatter)
return &DynamicPage{
pageFields: *fields,
Content: data,
}, nil
}
func readFrontMatter(sourcePtr *[]byte) (frontMatter VariableMap, err error) {
@ -273,39 +276,53 @@ func parseAndApplyTemplate(bs []byte, variables VariableMap) ([]byte, error) {
}
// Write applies Liquid and Markdown, as appropriate.
func (p *DynamicPage) Write(w io.Writer) error {
func (p *DynamicPage) Write(w io.Writer) (err error) {
body, err := parseAndApplyTemplate(p.Content, p.TemplateVariables())
if err != nil {
err := &os.PathError{Op: "Liquid Error", Path: p.Source(), Err: err}
return err
err = &os.PathError{Op: "Liquid Error", Path: p.Source(), Err: err}
return
}
if isMarkdown(p.path) {
body = blackfriday.MarkdownCommon(body)
body, err = p.applyLayout(p.frontMatter, body)
if err != nil {
return
}
}
layoutFrontMatter := p.frontMatter
for {
layoutName := layoutFrontMatter.String("layout", "")
if layoutName == "" {
break
}
template, err := site.FindLayout(layoutName, &layoutFrontMatter)
if err != nil {
return err
}
vars := mergeVariableMaps(p.TemplateVariables(), VariableMap{
"content": body,
"layout": layoutFrontMatter,
})
body, err = renderTemplate(template, vars)
if err != nil {
return nil
}
if isSassPath(p.path) {
return p.writeSass(w, body)
}
_, err = w.Write(body)
return err
return
}
func (p *DynamicPage) applyLayout(frontMatter VariableMap, body []byte) ([]byte, error) {
for {
layoutName := frontMatter.String("layout", "")
if layoutName == "" {
break
}
template, err := site.FindLayout(layoutName, &frontMatter)
if err != nil {
return nil, err
}
vars := mergeVariableMaps(p.TemplateVariables(), VariableMap{
"content": body,
"layout": frontMatter,
})
body, err = renderTemplate(template, vars)
if err != nil {
return nil, err
}
}
return body, nil
}
func isSassPath(path string) bool {
return strings.HasSuffix(path, ".sass") || strings.HasSuffix(path, ".scss")
}
func isMarkdown(path string) bool {

View File

@ -37,7 +37,6 @@ var templateVariableMatcher = regexp.MustCompile(`:\w+\b`)
func permalinkTemplateVariables(path string, frontMatter VariableMap) map[string]string {
var (
collectionName string
localPath = path
ext = filepath.Ext(path)
outputExt = ext
root = path[:len(path)-len(ext)]
@ -47,16 +46,16 @@ func permalinkTemplateVariables(path string, frontMatter VariableMap) map[string
switch {
case isMarkdown(path):
outputExt = ".html"
case ext == ".scss":
outputExt = ".html"
case isSassPath(path):
outputExt = ".css"
}
if val, found := frontMatter["collection"]; found {
collectionName = val.(string)
prefix := "_" + collectionName + "/"
if !strings.HasPrefix(localPath, prefix) {
panic(fmt.Errorf("Expected %s to start with %s", localPath, prefix))
if !strings.HasPrefix(path, prefix) {
panic(fmt.Errorf("Expected %s to start with %s", path, prefix))
}
localPath = localPath[len(prefix):]
path = path[len(prefix):]
root = root[len(prefix):]
}
vs := map[string]string{

71
sass.go Normal file
View File

@ -0,0 +1,71 @@
package main
import (
"bytes"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
libsass "github.com/wellington/go-libsass"
)
func (p *DynamicPage) writeSass(w io.Writer, data []byte) error {
comp, err := libsass.New(w, bytes.NewBuffer(data))
if err != nil {
return err
}
err = comp.Option(libsass.IncludePaths(site.SassIncludePaths()))
if err != nil {
log.Fatal(err)
}
return comp.Run()
}
// CopySassFileIncludes copies sass partials into a temporary directory,
// removing initial underscores.
// TODO delete the temp directory when done
func (s *Site) CopySassFileIncludes() {
// TODO use libsass.ImportsOption instead?
if site.sassTempDir == "" {
d, err := ioutil.TempDir(os.TempDir(), "_sass")
if err != nil {
panic(err)
}
site.sassTempDir = d
}
src := filepath.Join(s.Source, "_sass")
dst := site.sassTempDir
err := filepath.Walk(src, func(from string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return err
}
rel, err := filepath.Rel(src, from)
if err != nil {
panic(err)
}
if strings.HasPrefix(rel, "_") {
rel = rel[1:]
}
to := filepath.Join(dst, rel)
if err := copyFile(to, from, 0644); err != nil {
return err
}
return nil
})
if err != nil {
panic(err)
}
}
// SassIncludePaths returns an array of sass include directories.
func (s *Site) SassIncludePaths() []string {
if site.sassTempDir == "" {
site.CopySassFileIncludes()
}
s.CopySassFileIncludes()
return []string{site.sassTempDir}
}

View File

@ -24,6 +24,7 @@ type Site struct {
Paths map[string]Page // URL path -> Page
config SiteConfig
sassTempDir string
}
// For now (and maybe always?), there's just one site.