9 Star 45 Fork 10

卧雪Sirk/gosk

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
render.go 16.55 KB
一键复制 编辑 原始数据 按行查看 历史
scottkiss 提交于 2014-06-15 18:12 . fix xml code bug
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
package gosk
import (
"bufio"
"fmt"
"github.com/scottkiss/blackfriday"
"github.com/scottkiss/go-gypsy/yaml"
"html"
"html/template"
"io"
"io/ioutil"
"log"
"os"
"sort"
"strconv"
"strings"
"time"
"regexp"
)
type RenderFactory struct{}
const (
INDEX_TPL = "index"
TAG_TPL = "tag"
POSTS_TPL = "posts"
PAGES_TPL = "pages"
RSS_TPL = "rss"
CATEGORY_TPL = "category"
ARCHIVE_TPL = "archive"
)
const (
POST_DIR = "posts"
PUBLICSH_DIR = "publish"
)
const (
COMMON_HEADER_FILE = "header.tpl"
COMMON_FOOTER_FILE = "footer.tpl"
)
var (
articles Artic
articleListSize int = 5000
rss []RssConfig
rssListSize int = 10
navBarList []NavConfig
allTags map[string]Tag
categories map[string]Category
pages []*CustomPage
archives map[string]*YearArchive
allArchive YearArchives
NEWLY_ARTICLES_COUNT = 6
INDEX_ARTICLES_SHOW_COUNT = 15
)
func parseTemplate(root, tpl string, cfg *yaml.File) *template.Template {
//get theme template
themeFold, errt := cfg.Get("theme")
if errt != nil {
log.Println("get theme error!check config.yml")
os.Exit(1)
}
file := root + "templates/" + themeFold + "/" + tpl + ".tpl"
if !isExists(file) {
log.Println(file + " can not be found!")
os.Exit(1)
}
t := template.New(tpl + ".tpl")
t.Funcs(template.FuncMap{"get": cfg.Get})
t.Funcs(template.FuncMap{"unescaped": unescaped})
headerTpl := root + "templates/" + themeFold + "/common/" + COMMON_HEADER_FILE
footerTpl := root + "templates/" + themeFold + "/common/" + COMMON_FOOTER_FILE
if !isExists(headerTpl) {
log.Println(headerTpl + " can not be found!")
os.Exit(1)
}
if !isExists(footerTpl) {
log.Println(footerTpl + " can not be found!")
os.Exit(1)
}
t, err := t.ParseFiles(file, headerTpl, footerTpl)
if err != nil {
log.Println("parse " + tpl + " Template error!" + err.Error())
os.Exit(1)
}
log.Println("parse " + tpl + " Template complete!")
return t
}
func parseXMLTemplate(root, tpl string, cfg *yaml.File) *template.Template {
//get theme template
themeFold, errt := cfg.Get("theme")
if errt != nil {
log.Println("get theme error!check config.yml")
os.Exit(1)
}
file := root + "/templates/" + themeFold + "/" + tpl + ".tpl"
if !isExists(file) {
log.Println(file + " can not be found!")
os.Exit(1)
}
t := template.New(tpl + ".tpl")
t.Funcs(template.FuncMap{"get": cfg.Get})
t.Funcs(template.FuncMap{"unescaped": unescaped})
t.Funcs(template.FuncMap{"xmlheader": xmlHeader})
t, err := t.ParseFiles(file)
if err != nil {
log.Println("parse " + tpl + " Template error!" + err.Error())
os.Exit(1)
}
log.Println("parse " + tpl + " Template complete!")
return t
}
func isExists(file string) bool {
_, err := os.Stat(file)
if err == nil {
return true
}
return os.IsExist(err)
}
func (self *RenderFactory) RenderIndex(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
t := parseTemplate(root, INDEX_TPL, cfg)
targetFile := PUBLICSH_DIR + "/index.html"
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
if len(articles)<INDEX_ARTICLES_SHOW_COUNT{
INDEX_ARTICLES_SHOW_COUNT = len(articles)
}
if len(articles)<NEWLY_ARTICLES_COUNT{
NEWLY_ARTICLES_COUNT = len(articles)
}
m := map[string]interface{}{"ar": articles[:INDEX_ARTICLES_SHOW_COUNT], "nav": navBarList,"cats": categories,"newly":articles[:NEWLY_ARTICLES_COUNT]}
exErr := t.Execute(fout, m)
return exErr
}
//render tags
func (self *RenderFactory) RenderTag(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
t := parseTemplate(root, TAG_TPL, cfg)
targetFile := PUBLICSH_DIR + "/tag.html"
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
//log.Println(allTags)
m := map[string]interface{}{"tag": allTags, "nav": navBarList,"cats": categories,"newly":articles[:NEWLY_ARTICLES_COUNT-1]}
exErr := t.Execute(fout, m)
return exErr
}
//render categories
func (self *RenderFactory) RenderCategories(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
t := parseTemplate(root, CATEGORY_TPL, cfg)
targetFile := PUBLICSH_DIR + "/category.html"
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
//log.Println(categories)
m := map[string]interface{}{"cats": categories, "nav": navBarList,"newly":articles[:NEWLY_ARTICLES_COUNT-1]}
exErr := t.Execute(fout, m)
return exErr
}
//render rss page
func (self *RenderFactory) RenderRss(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
t := parseXMLTemplate(root, RSS_TPL, cfg)
targetFile := PUBLICSH_DIR + "/rss.xml"
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
rssCount, err := cfg.Get("rss.max")
if err != nil {
log.Println(err)
}
rssListSize, errconv := strconv.Atoi(rssCount)
if errconv != nil {
log.Println("rss max in config.yml is not a number!" + errconv.Error())
}
if len(articles) < rssListSize {
rssListSize = len(articles)
}
ars := articles[:rssListSize]
for _, ar := range ars {
rss = append(rss, RssConfig{ar.Title, ar.Link, ar.Author,
ar.Date, ar.Content})
}
r := Rss{time.Now().Format(time.RFC1123), rss}
exErr := t.Execute(fout, r)
return exErr
}
//pre process posts pages
func (self *RenderFactory) PreProcessPosts(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
articles = make([]*ArticleConfig, 0, articleListSize)
fileInfos, err := ioutil.ReadDir(root + POST_DIR)
if err != nil {
log.Println(err)
}
for _, fileInfo := range fileInfos {
if !fileInfo.IsDir() {
log.Println("begin process article -- " + fileInfo.Name())
fileName := fileInfo.Name()
mardownStr, fi, err := processArticleFile(root+POST_DIR+"/"+fileName, fileName)
//create post html file
if err != nil {
log.Println("preprocess article file error!")
os.Exit(1)
}
trName := strings.TrimSuffix(fileName, ".md")
p := processArticleUrl(fi)
//deal markdown
htmlByte := blackfriday.MarkdownCommon([]byte(mardownStr))
//init other article infos
htmlStr := html.UnescapeString(string(htmlByte))
re := regexp.MustCompile(`<pre><code>([\s\S]*?)</code></pre>`)
htmlStr = re.ReplaceAllString(htmlStr, `<pre class="prettyprint linenums">${1}</pre>`)
fi.Content = htmlStr
fi.Link = p + trName + ".html"
//if abstract is empty,auto gen it
if fi.Abstract == "" {
var limit int = 1000
rs := []rune(htmlStr)
if len(rs) < 1000 {
limit = len(rs)
}
abstract := subStr(htmlStr, 0, limit)
fi.Abstract = trimHTML(abstract)
}
if fi.Author == "" {
author, cerr := cfg.Get("meta.author")
if cerr != nil {
log.Println(cerr)
}
fi.Author = author
}
//sort by date
addAndSortArticles(fi)
}
}
generateCategories()
generateTags()
generateNavBar(yamls)
return nil
}
//render posts pages
func (self *RenderFactory) RenderPosts(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
//log.Println(cfg.Get("title"))
t := parseTemplate(root, POSTS_TPL, cfg)
for _,fileInfo := range articles {
//create dir /yyyy/MM/dd
p := processArticleUrl(*fileInfo)
if !isExists(PUBLICSH_DIR + "/articles/" + p) {
os.MkdirAll(PUBLICSH_DIR+"/articles/"+p, 0777)
}
targetFile := PUBLICSH_DIR + "/articles/" + fileInfo.Link
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
m := map[string]interface{}{"fi": fileInfo,"nav": navBarList, "cats": categories,"newly":articles[:NEWLY_ARTICLES_COUNT-1]}
t.Execute(fout, m)
}
return nil
}
func processArticleUrl(ar ArticleConfig) string {
y := strconv.Itoa(ar.Time.Year())
m := strconv.Itoa(int(ar.Time.Month()))
d := strconv.Itoa(ar.Time.Day())
return y + "/" + m + "/" + d + "/"
}
//render custom pages
func (self *RenderFactory) RenderPages(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
generatePages(yamls)
t := parseTemplate(root, PAGES_TPL, cfg)
for _, p := range pages {
p.Id = strings.TrimSuffix(p.Id, " ")
filePath := root + "pages/" + p.Id + ".md"
if !isExists(filePath) {
log.Println(filePath + " is not found!")
os.Exit(1)
}
f, err := os.Open(filePath)
if err != nil {
log.Println(err)
}
defer f.Close()
rd := bufio.NewReader(f)
var markdownStr string
for {
buf, _, err := rd.ReadLine()
if err == io.EOF {
break
} else {
content := string(buf)
markdownStr += content + "\n"
}
}
//deal markdown
htmlByte := blackfriday.MarkdownCommon([]byte(markdownStr))
//init other article infos
htmlStr := html.UnescapeString(string(htmlByte))
htmlStr = strings.Replace(htmlStr, "<pre><code", `<pre class="prettyprint linenums"`, -1)
htmlStr = strings.Replace(htmlStr, `</code>`, "", -1)
p.Content = htmlStr
if !isExists(PUBLICSH_DIR + "/pages/") {
os.MkdirAll(PUBLICSH_DIR+"/pages/", 0777)
}
targetFile := PUBLICSH_DIR + "/pages/" + p.Id + ".html"
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
m := map[string]interface{}{"p": p, "nav": navBarList,"cats": categories,"newly":articles[:NEWLY_ARTICLES_COUNT-1]}
t.Execute(fout, m)
}
return nil
}
//render archive
func (self *RenderFactory) RenderArchives(root string, yamls map[string]interface{}) error {
if !strings.HasSuffix(root, "/") {
root += "/"
}
yCfg := yamls["config.yml"]
var cfg = yCfg.(*yaml.File)
t := parseTemplate(root, ARCHIVE_TPL, cfg)
targetFile := PUBLICSH_DIR + "/archive.html"
fout, err := os.Create(targetFile)
if err != nil {
log.Println("create file " + targetFile + " error!")
os.Exit(1)
}
defer fout.Close()
generateArchive()
//log.Println(allArchive)
m := map[string]interface{}{"archives": allArchive, "nav": navBarList,"cats": categories,"newly":articles[:NEWLY_ARTICLES_COUNT-1]}
exErr := t.Execute(fout, m)
return exErr
}
func generateArchive() {
archives = make(map[string]*YearArchive)
for _, ar := range articles {
y, m, _ := ar.Time.Date()
year := fmt.Sprintf("%v", y)
month := m.String()
yArchive := archives[year]
if yArchive == nil {
yArchive = &YearArchive{year, make([]*MonthArchive, 0), make(map[string]*MonthArchive)}
archives[year] = yArchive
}
mArchive := yArchive.months[month]
if mArchive == nil {
mArchive = &MonthArchive{month, m, make([]*ArticleBase, 0)}
yArchive.months[month] = mArchive
}
mArchive.Articles = append(mArchive.Articles, &ArticleBase{ar.Link, ar.Title})
}
allArchive = make(YearArchives, 0)
//sort by time
for _, yArchive := range archives {
monthCollect := make(MonthArchives, 0)
for _, mArchive := range yArchive.months {
monthCollect = append(monthCollect, mArchive)
}
sort.Sort(monthCollect)
yArchive.months = nil
yArchive.Months = monthCollect
allArchive = append(allArchive, yArchive)
}
sort.Sort(allArchive)
}
func generateTags() {
allTags = make(map[string]Tag)
for _, ar := range articles {
for _, tg := range ar.Tags {
//log.Println(tg)
t, ok := allTags[tg.Name]
if ok {
t.Articles = append(t.Articles, ArticleBase{ar.Link, ar.Title})
t.Length = len(t.Articles)
allTags[tg.Name] = t
} else {
art := ArticleBase{ar.Link, ar.Title}
arts := make([]ArticleBase, 0)
arts = append(arts, art)
allTags[tg.Name] = Tag{tg.Name, arts, 1}
}
}
}
}
func generateCategories() {
categories = make(map[string]Category)
for _, ar := range articles {
c, ok := categories[ar.Category]
if ok {
c.Articles = append(c.Articles, ArticleBase{ar.Link, ar.Title})
c.Length = len(c.Articles)
categories[ar.Category] = c
} else {
art := ArticleBase{ar.Link, ar.Title}
arts := make([]ArticleBase, 0)
arts = append(arts, art)
categories[ar.Category] = Category{ar.Category, arts, 1}
}
}
}
//process posts,get article title,post date
func processArticleFile(filePath, fileName string) (string, ArticleConfig, error) {
f, err := os.Open(filePath)
if err != nil {
log.Println(err)
}
defer f.Close()
rd := bufio.NewReader(f)
var ct int = 0
var yamlStr, markdownStr string
for {
buf, _, err := rd.ReadLine()
if err == io.EOF {
break
} else {
content := string(buf)
if content == "---" {
ct++
}
if ct == 2 {
if content != "---" {
markdownStr += content + "\n"
}
} else {
yamlStr += content + "\n"
}
}
}
config := yaml.Config(strings.Replace(yamlStr, "---\n", "", -1))
title, err := config.Get("title")
date, err := config.Get("date")
tagCount, err := config.Count("tags")
if err != nil {
log.Println(err)
}
var tags []TagConfig
trName := strings.TrimSuffix(fileName, ".md")
for i := 0; i < tagCount; i++ {
tagName, err := config.Get("tags[" + strconv.Itoa(i) + "]")
if err != nil {
log.Println("generate Tags error " + err.Error())
}
tags = append(tags, TagConfig{tagName, title, trName + ".html"})
}
cat, err := config.Get("categories[0]")
abstract, err := config.Get("abstract")
author, err := config.Get("author")
t, terr := time.Parse("2006-01-02 15:04:05", date)
if terr != nil {
log.Println(terr)
}
//log.Println(t)
shortDate := t.UTC().Format("Jan 2, 2006")
arInfo := ArticleConfig{title, date,shortDate, cat, tags, abstract, author, t, "", "", navBarList}
//log.Println(markdownStr)
return markdownStr, arInfo, nil
}
//sort articles by date
func addAndSortArticles(arInfo ArticleConfig) {
//log.Println(len(articles))
artLen := len(articles)
if artLen < articleListSize {
articles = append(articles, &arInfo)
}
sort.Sort(ByDate{articles})
//log.Println(len(articles))
}
func unescaped(str string) interface{} {
re := regexp.MustCompile(`<pre class="prettyprint linenums">([\s\S]*?)</pre>`)
str = re.ReplaceAllStringFunc(str,xmlEscapString)
return template.HTML(str)
}
func xmlHeader(blank string) string {
return blank + `<?xml version="1.0" encoding="utf-8"?>`
}
func xmlEscapString(str string) string{
str = strings.Replace(str,`<pre class="prettyprint linenums">`,"@@PRE_BEGIN",-1)
str = strings.Replace(str,`</pre>`,"@@PRE_END",-1)
str = template.HTMLEscapeString(str)
str = strings.Replace(str,"@@PRE_BEGIN",`<pre class="prettyprint linenums">`,-1)
str = strings.Replace(str,"@@PRE_END",`</pre>`,-1)
return str
}
func generateNavBar(yamls map[string]interface{}) {
yCfg := yamls["nav.yml"]
var cfg = yCfg.(*yaml.File)
ct, err := cfg.Count("")
if err != nil {
log.Println(err)
}
for i := 0; i < ct; i++ {
name, errn := cfg.Get("[" + strconv.Itoa(i) + "].label")
if nil != errn {
log.Println(errn)
}
href, errh := cfg.Get("[" + strconv.Itoa(i) + "].href")
if nil != errh {
log.Println(errh)
}
target, errt := cfg.Get("[" + strconv.Itoa(i) + "].target")
if nil != errt {
log.Println(errt)
}
nav := NavConfig{name, href, target}
navBarList = append(navBarList, nav)
}
//log.Println(navBarList)
}
//generate custom pages
func generatePages(yamls map[string]interface{}) {
yCfg := yamls["pages.yml"]
var cfg = yCfg.(*yaml.File)
ct, err := cfg.Count("")
if err != nil {
log.Println(err)
}
for i := 0; i < ct; i++ {
id, erri := cfg.Get("[" + strconv.Itoa(i) + "].id")
if nil != erri {
log.Println(erri)
}
title, errt := cfg.Get("[" + strconv.Itoa(i) + "].title")
if nil != errt {
log.Println(errt)
}
page := CustomPage{id, title, ""}
pages = append(pages, &page)
}
}
func (self *RenderFactory) Render(root string) {
yp := new(YamlParser)
yamlData := yp.parse(root)
self.PreProcessPosts(root,yamlData)
self.RenderPosts(root, yamlData)
self.RenderCategories(root, yamlData)
self.RenderIndex(root, yamlData)
self.RenderRss(root, yamlData)
self.RenderTag(root, yamlData)
self.RenderArchives(root, yamlData)
self.RenderPages(root, yamlData)
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/scottkiss/gosk.git
git@gitee.com:scottkiss/gosk.git
scottkiss
gosk
gosk
master

搜索帮助