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

Collection and permalink wip

This commit is contained in:
Oliver Steele 2017-06-10 17:51:46 -04:00
parent b14c8c5b44
commit 58d3e66604
9 changed files with 157 additions and 71 deletions

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -10,15 +9,13 @@ import (
func cleanDirectory() error { func cleanDirectory() error {
removeFiles := func(path string, info os.FileInfo, err error) error { removeFiles := func(path string, info os.FileInfo, err error) error {
stat, err := os.Stat(path) if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return nil
} }
if err != nil {
return err return err
} }
if info.IsDir() {
if stat.IsDir() {
return nil return nil
} }
// TODO check for inclusion in KeepFiles // TODO check for inclusion in KeepFiles
@ -26,9 +23,10 @@ func cleanDirectory() error {
return err return err
} }
err := filepath.Walk(siteConfig.DestinationDir, removeFiles) err := filepath.Walk(siteConfig.DestinationDir, removeFiles)
if err == nil { if err != nil {
err = removeEmptyDirectories(siteConfig.DestinationDir) return err
} }
err = removeEmptyDirectories(siteConfig.DestinationDir)
return err return err
} }
@ -39,7 +37,7 @@ func build() error {
} }
for _, page := range siteMap { for _, page := range siteMap {
if !page.Static { if !page.Static {
page, err = readFile(page.Path, true) page, err = readFile(page.Path, siteData, true)
} }
if err != nil { if err != nil {
return err return err
@ -54,7 +52,7 @@ func build() error {
if page.Static { if page.Static {
os.Link(filepath.Join(siteConfig.SourceDir, page.Path), destPath) os.Link(filepath.Join(siteConfig.SourceDir, page.Path), destPath)
} else { } else {
fmt.Println("render", filepath.Join(siteConfig.SourceDir, page.Path), "->", destPath) // fmt.Println("render", filepath.Join(siteConfig.SourceDir, page.Path), "->", destPath)
ioutil.WriteFile(destPath, page.Body, 0644) ioutil.WriteFile(destPath, page.Body, 0644)
} }
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -15,6 +16,30 @@ func leftPad(s string, n int) string {
return string(ws) + s return string(ws) + s
} }
func mergeMaps(a map[interface{}]interface{}, b map[interface{}]interface{}) map[interface{}]interface{} {
result := map[interface{}]interface{}{}
for k, v := range a {
result[k] = v
}
for k, v := range b {
result[k] = v
}
return result
}
func stringMap(m map[interface{}]interface{}) map[string]interface{} {
result := map[string]interface{}{}
for k, v := range m {
stringer, ok := k.(fmt.Stringer)
if ok {
result[stringer.String()] = v
} else {
result[fmt.Sprintf("%v", k)] = v
}
}
return result
}
func postfixWalk(path string, walkFn filepath.WalkFunc) error { func postfixWalk(path string, walkFn filepath.WalkFunc) error {
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(path)
if err != nil { if err != nil {

View File

@ -15,7 +15,7 @@ func LinkFactory(p *core.Parser, config *core.Configuration) (core.Tag, error) {
end := p.Position - 2 end := p.Position - 2
path := strings.Trim(string(p.Data[start:end]), " ") path := strings.Trim(string(p.Data[start:end]), " ")
permalink, ok := getFilePermalink(path) permalink, ok := getFileURL(path)
if !ok { if !ok {
return nil, p.Error(fmt.Sprintf("%s not found", path)) return nil, p.Error(fmt.Sprintf("%s not found", path))
} }

15
main.go
View File

@ -33,10 +33,10 @@ func main() {
flag.StringVar(&siteConfig.SourceDir, "source", siteConfig.SourceDir, "Source directory") flag.StringVar(&siteConfig.SourceDir, "source", siteConfig.SourceDir, "Source directory")
flag.Parse() flag.Parse()
configPath := filepath.Join(siteConfig.SourceDir, "_siteConfig.yml") configPath := filepath.Join(siteConfig.SourceDir, "_config.yml")
// TODO error if file is e.g. unreadable // TODO error if file is e.g. unreadable
if _, err := os.Stat(configPath); err == nil { if _, err := os.Stat(configPath); err == nil {
err := siteConfig.readFromDirectory(siteConfig.SourceDir) err := siteConfig.read(configPath)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
@ -48,7 +48,7 @@ func main() {
printPathSetting("Source:", siteConfig.SourceDir) printPathSetting("Source:", siteConfig.SourceDir)
printPathSetting("Destination:", siteConfig.DestinationDir) printPathSetting("Destination:", siteConfig.DestinationDir)
fileMap, err := buildFileMap() fileMap, err := buildSiteMap()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
@ -66,11 +66,12 @@ func main() {
printSetting("", fmt.Sprintf("done in %.2fs.", elapsed.Seconds())) printSetting("", fmt.Sprintf("done in %.2fs.", elapsed.Seconds()))
case "routes": case "routes":
fmt.Printf("\nRoutes:\n") fmt.Printf("\nRoutes:\n")
for urlPath, p := range siteMap { for url, p := range siteMap {
fmt.Printf(" %s -> %s\n", urlPath, p.Path) fmt.Printf(" %s -> %s\n", url, p.Path)
} }
case "build1": case "render":
page, err2 := readFile("index.md", true) // build a single page, and print it to stdout; for testing
page, err2 := readFile("index.md", siteData, true)
if err2 != nil { if err2 != nil {
err = err2 err = err2
break break

70
page.go
View File

@ -13,7 +13,9 @@ import (
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
var frontmatterRe = regexp.MustCompile(`(?s)^---\n(.+?)\n---\n`) var frontmatterMatcher = regexp.MustCompile(`(?s)^---\n(.+?\n)---\n`)
var templateVariableMatcher = regexp.MustCompile(`:(?:collection|path|name|title)\b`)
var nonAlphanumericSequenceMatcher = regexp.MustCompile(`[^[:alnum:]]+`)
// A Page represents an HTML page. // A Page represents an HTML page.
type Page struct { type Page struct {
@ -25,10 +27,11 @@ type Page struct {
} }
func (p Page) String() string { func (p Page) String() string {
return fmt.Sprintf("Page{Path=%v, Permalink=%v}", p.Path, p.Permalink) return fmt.Sprintf("Page{Path=%v, Permalink=%v, Static=%v}",
p.Path, p.Permalink, p.Static)
} }
func readFile(path string, expand bool) (*Page, error) { func readFile(path string, defaults map[interface{}]interface{}, expand bool) (*Page, error) {
// TODO don't read, parse binary files // TODO don't read, parse binary files
source, err := ioutil.ReadFile(filepath.Join(siteConfig.SourceDir, path)) source, err := ioutil.ReadFile(filepath.Join(siteConfig.SourceDir, path))
@ -37,48 +40,51 @@ func readFile(path string, expand bool) (*Page, error) {
} }
static := true static := true
data := map[string]interface{}{} data := defaults
body := source body := source
fmMatchIndex := frontmatterRe.FindSubmatchIndex(source) if match := frontmatterMatcher.FindSubmatchIndex(source); match != nil {
if fmMatchIndex != nil {
static = false static = false
body = source[fmMatchIndex[1]:] body = source[match[1]:]
fmBytes := source[fmMatchIndex[2]:fmMatchIndex[3]] fm := map[interface{}]interface{}{}
var fmMap interface{} err = yaml.Unmarshal(source[match[2]:match[3]], &fm)
err = yaml.Unmarshal(fmBytes, &fmMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fmStringMap, ok := fmMap.(map[interface{}]interface{})
if !ok { data = mergeMaps(data, fm)
return nil, errors.New("YAML frontmatter is not a map")
}
for k, v := range fmStringMap {
stringer, ok := k.(fmt.Stringer)
if ok {
data[stringer.String()] = v
} else {
data[fmt.Sprintf("%v", k)] = v
}
}
} }
ext := filepath.Ext(path) ext := filepath.Ext(path)
var title string // var title string
if val, ok := data["permalink"]; ok { // if val, ok := data["permalink"]; ok {
title = fmt.Sprintf("%v", val) // title = fmt.Sprintf("%v", val)
} else { // } else {
title = filepath.Base(path) // title = filepath.Base(path)
title = title[:len(title)-len(ext)] // title = title[:len(title)-len(ext)]
} // }
// TODO use site, collection default; expand components
permalink := "/" + path[:len(path)-len(ext)] permalink := "/" + path[:len(path)-len(ext)]
if val, ok := data["permalink"]; ok { if val, ok := data["permalink"]; ok {
permalink = val.(string) // TODO what if it's not a string? permalink, ok = val.(string)
if !ok {
return nil, errors.New("Required string value for permalink")
} }
}
templateVariables := map[string]string{}
templateVariables["output_ext"] = ".html"
templateVariables["path"] = regexp.MustCompile(`\.md$`).ReplaceAllLiteralString(path, "")
templateVariables["name"] = nonAlphanumericSequenceMatcher.ReplaceAllString(filepath.Base(path), "-")
if val, found := data["collection"]; found {
collectionName := val.(string)
collectionPath := "_" + collectionName + "/"
templateVariables["collection"] = collectionName
templateVariables["path"] = templateVariables["path"][len(collectionPath):]
}
permalink = templateVariableMatcher.ReplaceAllStringFunc(permalink, func(m string) string {
return templateVariables[m[1:]]
})
if expand && ext == ".md" { if expand && ext == ".md" {
template, err := liquid.Parse(body, nil) template, err := liquid.Parse(body, nil)
@ -86,7 +92,7 @@ func readFile(path string, expand bool) (*Page, error) {
return nil, err return nil, err
} }
writer := new(bytes.Buffer) writer := new(bytes.Buffer)
template.Render(writer, data) template.Render(writer, stringMap(data))
body = blackfriday.MarkdownBasic(writer.Bytes()) body = blackfriday.MarkdownBasic(writer.Bytes())
} }

View File

@ -33,7 +33,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
return return
} }
p, err := readFile(p.Path, true) p, err := readFile(p.Path, siteData, true)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
fmt.Printf("Error expanding %s\n%s", p.Path, err) fmt.Printf("Error expanding %s\n%s", p.Path, err)

77
site.go
View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -14,7 +15,7 @@ type SiteConfig struct {
Permalink string Permalink string
SourceDir string SourceDir string
DestinationDir string DestinationDir string
// Safe bool Safe bool
Exclude []string Exclude []string
Include []string Include []string
// KeepFiles []string // KeepFiles []string
@ -33,7 +34,9 @@ var siteConfig = SiteConfig{
// A map from URL path -> *Page // A map from URL path -> *Page
var siteMap map[string]*Page var siteMap map[string]*Page
func (config *SiteConfig) readFromDirectory(path string) error { var siteData = map[interface{}]interface{}{}
func (config *SiteConfig) read(path string) error {
configBytes, err := ioutil.ReadFile(path) configBytes, err := ioutil.ReadFile(path)
if err == nil { if err == nil {
err = yaml.Unmarshal(configBytes, &config) err = yaml.Unmarshal(configBytes, &config)
@ -43,7 +46,7 @@ func (config *SiteConfig) readFromDirectory(path string) error {
return err return err
} }
func buildFileMap() (map[string]*Page, error) { func buildSiteMap() (map[string]*Page, error) {
basePath := siteConfig.SourceDir basePath := siteConfig.SourceDir
fileMap := map[string]*Page{} fileMap := map[string]*Page{}
exclusionMap := stringArrayToMap(siteConfig.Exclude) exclusionMap := stringArrayToMap(siteConfig.Exclude)
@ -52,14 +55,9 @@ func buildFileMap() (map[string]*Page, error) {
if err != nil { if err != nil {
return err return err
} }
if path == siteConfig.SourceDir { if path == basePath {
return nil return nil
} }
// TODO replace by info.IsDir
stat, err := os.Stat(path)
if err != nil {
return err
}
relPath, err := filepath.Rel(basePath, path) relPath, err := filepath.Rel(basePath, path)
if err != nil { if err != nil {
@ -70,25 +68,74 @@ func buildFileMap() (map[string]*Page, error) {
_, exclude := exclusionMap[relPath] _, exclude := exclusionMap[relPath]
exclude = exclude || strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") exclude = exclude || strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_")
if exclude { if exclude {
if stat.IsDir() { if info.IsDir() {
return filepath.SkipDir return filepath.SkipDir
} }
return nil return nil
} }
if !stat.IsDir() { if info.IsDir() {
page, err := readFile(relPath, false) return nil
}
p, err := readFile(relPath, siteData, false)
if err != nil { if err != nil {
return err return err
} }
fileMap[page.Permalink] = page fileMap[p.Permalink] = p
}
return nil return nil
} }
err := filepath.Walk(basePath, walkFn) err := filepath.Walk(basePath, walkFn)
if err != nil {
return nil, err
}
for name, colVal := range siteConfig.Collections {
data := colVal.(map[interface{}]interface{})
output := false
if val, found := data["output"]; found {
output = val.(bool)
}
if output {
err = addCollectionFiles(fileMap, name, data)
if err != nil {
return nil, err
}
}
}
return fileMap, err return fileMap, err
} }
func getFilePermalink(path string) (string, bool) { func addCollectionFiles(fileMap map[string]*Page, name string, data map[interface{}]interface{}) error {
basePath := siteConfig.SourceDir
collData := mergeMaps(siteData, data)
collData["collection"] = name
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(basePath, path)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
p, err := readFile(relPath, collData, false)
if err != nil {
return err
}
if p.Static {
fmt.Printf("skipping static file inside collection: %s\n", path)
} else {
fileMap[p.Permalink] = p
}
return nil
}
err := filepath.Walk(filepath.Join(basePath, "_"+name), walkFn)
return err
}
func getFileURL(path string) (string, bool) {
for _, v := range siteMap { for _, v := range siteMap {
if v.Path == path { if v.Path == path {
return v.Permalink, true return v.Permalink, true

5
test/_collection/c1.md Normal file
View File

@ -0,0 +1,5 @@
---
a: b
---
A collection page.

4
test/_config.yml Normal file
View File

@ -0,0 +1,4 @@
collections:
collection:
output: true
permalink: /:collection/:path