wombat/blog/blog.go

299 lines
7.7 KiB
Go

package blog
import (
"bytes"
"fmt"
"math"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"text/template"
"time"
goutil "git.gutmet.org/goutil.git/misc"
. "git.gutmet.org/wombat.git/templatestructures"
)
const postsPerPage int = 5
var blogname string
var folder string
var style string
var script string
var t *template.Template
var rssTemplate *template.Template
var categories map[string][]Post
func linkToPost(filename string) string {
filename = filepath.Base(filename)
return fmt.Sprintf("/%s/%s", folder, ConvASCII(filename))
}
func linkToCategory(name string) string {
return fmt.Sprintf("/%s/categories.html#%s", folder, ConvASCII(strings.ToLower(name)))
}
func addToCategory(category string, post Post) {
c := strings.ToLower(category)
if len(c) > 0 {
categories[c] = append(categories[c], post)
}
}
func keys(m map[string][]Post) []string {
keys := make([]string, 0)
for k := range m {
keys = append(keys, k)
}
return keys
}
func removeFromStage2(filename string) {
filename = strings.TrimPrefix(strings.TrimPrefix(filename, "stage1/"), "stage1\\")
os.Remove(filepath.Join("stage2", filename))
}
func emitPostPage(post Post) string {
link := post.Link
removeFromStage2(post.Filename)
base := path.Base(link)
folders := strings.TrimSuffix("stage2"+link, base)
os.MkdirAll(folders, 0755)
page := Page{}
page.Blogname = blogname
page.Blogdir = folder
page.Title = post.Meta.Title
page.Style = style
page.Script = script
page.Content = post.Content
page.Time = post.Meta.Date.Format(TimeFormat)
page.Blogpost = true
for _, categoryName := range post.Meta.Categories {
page.Categories = append(page.Categories, Category{Name: categoryName, Path: linkToCategory(categoryName)})
related := CategoryListing{Name: categoryName, ASCIIName: ConvASCII(categoryName), Posts: categories[categoryName]}
page.CategoryListings = append(page.CategoryListings, related)
}
buf := new(bytes.Buffer)
t.Execute(buf, page)
goutil.WriteFile(filepath.Join("stage2", link), buf.String())
return link
}
func indexFilename(i int) string {
return filepath.Join("stage2", folder, fmt.Sprintf("%d.html", i))
}
func indexPageExists(i int, total int) bool {
max := int(math.Ceil(float64(total)/float64(postsPerPage))) - 1
return (i >= 0) && (i <= max)
}
type byDateDesc []Post
func (p byDateDesc) Len() int { return len(p) }
func (p byDateDesc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p byDateDesc) Less(i, j int) bool { return p[i].Meta.Date.After(p[j].Meta.Date) }
func emitIndexAndPostPages(i int, posts []Post, total int) {
file := indexFilename(i)
postCollection := make([]Blogpost, 0)
sort.Sort(byDateDesc(posts))
for _, post := range posts {
link := emitPostPage(post)
p := Blogpost{}
p.Title = post.Meta.Title
p.Link = link
p.Content = post.Content
postCollection = append(postCollection, p)
}
buf := new(bytes.Buffer)
page := Page{}
page.Title = blogname
page.Style = style
page.Script = script
page.Blogdir = folder
page.Blogname = blogname
page.Blogpost = false
page.PostCollection = postCollection
if indexPageExists(i-1, total) {
page.Previous = strconv.Itoa(i-1) + ".html"
}
if indexPageExists(i+1, total) {
page.Later = strconv.Itoa(i+1) + ".html"
}
t.Execute(buf, page)
goutil.WriteFile(file, buf.String())
}
func getMetadata(file string) (Metadata, error) {
metaAll, err := goutil.ReadFile(goutil.TrimExt(file) + ".desc")
ret := Metadata{}
if err == nil {
lines := strings.Split(metaAll, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "title:") {
ret.Title = strings.Trim(strings.TrimPrefix(line, "title:"), "\" ")
} else if strings.HasPrefix(line, "date:") {
date, err2 := time.Parse(TimeFormat, strings.Trim(strings.TrimPrefix(line, "date:"), "\" "))
if err2 == nil {
ret.Date = date
} else {
fmt.Printf("Unrecognized date format in %s", file)
}
} else if strings.HasPrefix(line, "categories:") {
categories := strings.Trim(strings.TrimPrefix(line, "categories:"), "\"[] ")
splitcategories := strings.Split(categories, ",")
for _, category := range splitcategories {
c := strings.TrimSpace(category)
if len(c) > 0 {
ret.Categories = append(ret.Categories, c)
}
}
}
}
return ret, nil
} else {
return ret, err
}
}
func getPost(file string) (Post, error) {
tmp, err := goutil.ReadFile(file)
if err == nil {
meta, err2 := getMetadata(file)
if err2 == nil {
p := Post{}
p.Content = tmp
p.Meta = meta
p.Filename = file
filename := strings.TrimPrefix(file, "stage1")
p.Link = linkToPost(filename)
return p, nil
} else {
return Post{}, err2
}
} else {
return Post{}, err
}
}
func makeCategories(posts []Post) {
sort.Sort(byDate(posts))
for _, post := range posts {
for _, category := range post.Meta.Categories {
addToCategory(category, post)
}
}
page := Page{}
page.Title = blogname + ": Categories"
page.Style = style
page.Script = script
keySet := keys(categories)
sort.Strings(keySet)
for _, k := range keySet {
listing := CategoryListing{}
listing.Name = k
listing.ASCIIName = ConvASCII(k)
sort.Sort(byDate(categories[k]))
listing.Posts = categories[k]
page.CategoryListings = append(page.CategoryListings, listing)
}
buf := new(bytes.Buffer)
t.Execute(buf, page)
goutil.WriteFile(filepath.Join("stage2", folder, "categories.html"), buf.String())
}
func makeTimeline(posts []Post) {
sort.Sort(byDateDesc(posts))
page := Page{}
page.Title = blogname + ": Timeline"
page.Style = style
page.Script = script
page.TimelineListing = posts
buf := new(bytes.Buffer)
t.Execute(buf, page)
goutil.WriteFile(filepath.Join("stage2", folder, "timeline.html"), buf.String())
}
func makeRSS(posts []Post) {
sort.Sort(byDateDesc(posts))
buf := new(bytes.Buffer)
rssTemplate.Execute(buf, posts)
goutil.WriteFile(filepath.Join("stage2", folder, "feed.rss"), buf.String())
}
type byDate []Post
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].Meta.Date.Before(p[j].Meta.Date) }
func getPosts() []Post {
files := goutil.ListFilesExt(filepath.Join("stage1", folder), ".html")
posts := make([]Post, 0)
for _, file := range files {
tmp, err := getPost(file)
if err == nil {
isFuture := tmp.Meta.Date != time.Time{} && tmp.Meta.Date.After(time.Now())
if isFuture {
removeFromStage2(file)
} else {
posts = append(posts, tmp)
}
}
}
return posts
}
func makeBlog() {
posts := getPosts()
makeCategories(posts)
makeTimeline(posts)
makeRSS(posts)
sort.Sort(byDate(posts))
total := len(posts)
s := make([]Post, 0)
n := 0
for i, tmp := range posts {
if (i / postsPerPage) != n {
emitIndexAndPostPages(n, s, total)
n = i / postsPerPage
s = make([]Post, 0)
}
s = append(s, tmp)
}
if len(s) != 0 {
emitIndexAndPostPages(n, s, total)
}
os.Link(indexFilename(n), filepath.Join("stage2", folder, "index.html"))
}
func Generate(dir string) {
folder = dir
blogname, _ = goutil.ReadFile(filepath.Join("stage0", folder, ".name"))
if blogname == "" {
blogname = "blog"
}
blogname = strings.TrimSpace(blogname)
categories = make(map[string][]Post)
var err error
var err2 error
var err3 error
style, err = goutil.ReadFile("style.css")
script, err2 = goutil.ReadFile("script.js")
templateString, err3 := goutil.ReadFile("template")
t = template.Must(template.New("page").Parse(templateString))
rssTemplateString, _ := goutil.ReadFile("rssTemplate")
rssTemplate = template.Must(template.New("rss").Parse(rssTemplateString))
if err == nil && err2 == nil && err3 == nil {
makeBlog()
} else {
fmt.Println(err)
fmt.Println(err2)
fmt.Println(err3)
}
}