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:
parent
b14c8c5b44
commit
58d3e66604
16
build.go
16
build.go
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
helpers.go
25
helpers.go
@ -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 {
|
||||||
|
@ -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
15
main.go
@ -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
70
page.go
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
77
site.go
@ -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
5
test/_collection/c1.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
a: b
|
||||||
|
---
|
||||||
|
|
||||||
|
A collection page.
|
4
test/_config.yml
Normal file
4
test/_config.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
collections:
|
||||||
|
collection:
|
||||||
|
output: true
|
||||||
|
permalink: /:collection/:path
|
Loading…
Reference in New Issue
Block a user