package blog import ( "bytes" "fmt" goutil "git.gutmet.org/goutil.git/misc" . "git.gutmet.org/wombat.git/templatestructures" "math" "os" "path" "path/filepath" "sort" "strconv" "strings" "text/template" "time" ) const postsPerPage int = 5 var blogname string var folder string var style string var t *template.Template var rssTemplate *template.Template var categories map[string][]Post func linkToPost(filename string) string { filename = filepath.Base(filename) split := strings.Split(filename, "-") if len(split) < 4 { return "" } else { year := split[0] month := split[1] day := split[2] name := ConvASCII(strings.Join(split[3:], "-")) return fmt.Sprintf("/%s/%s/%s/%s/%s", folder, year, month, day, name) } } 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 emitPostPage(post Post) string { filename := strings.TrimPrefix(strings.TrimPrefix(post.Filename, "stage1/"), "stage1\\") link := post.Link os.Remove(filepath.Join("stage2", 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.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{categoryName, linkToCategory(categoryName)}) addToCategory(categoryName, post) } 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 emitIndexPage(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.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() { page := Page{} page.Title = blogname + ": Categories" page.Style = style 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.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 { posts = append(posts, tmp) } } return posts } func makeBlog() { posts := getPosts() sort.Sort(byDate(posts)) total := len(posts) s := make([]Post, 0) n := 0 for i, tmp := range posts { if (i / postsPerPage) != n { emitIndexPage(n, s, total) n = i / postsPerPage s = make([]Post, 0) } s = append(s, tmp) } if len(s) != 0 { emitIndexPage(n, s, total) } os.Link(indexFilename(n), filepath.Join("stage2", folder, "index.html")) makeCategories() makeTimeline(posts) makeRSS(posts) } 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 style, err = goutil.ReadFile("style.css") templateString, err2 := 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 { makeBlog() } else { fmt.Println(err) fmt.Println(err2) } }