钱文翔的博客

Gugo!我自己写的简易静态博客生成器

我自己使用Go写了一个简易的静态博客生成器。可以将使用markdown写的博客文章转换成html博客页面。写这篇文章的时候,已经实现了博客主页文章列表,博客文章展示,tag列表和分类列表。

我的github项目–gugo

缘起

看完翻译writing%20a%20Static%20Blog%20Generator%20in%20Go/)了这篇文章之后,我就有了自己写一个静态博客的想法。

在此之前,我的博客使用的是hexo,看了不少hexo的document来学习使用hexo。为了使我的博客更加赏心悦目,我一直致力于更换hexo的theme,并乐此不疲。

偶然间从github的trending上看到hugo,第一反应是–“哎哟,不错哟”。我对其这么有好感的主要原因是hugo是用Go写的。然后我就去官方网站看hugo的–啧啧,Hugo的官网真是丑到爆!我直接下拉看themes,嗯,真丑。(我写这篇博客的时候,hugo提供的theme样例已经很多了,看起来还很不错)

所以,我虽然觉得hugo不错,但因为官方提供的theme样例丑的原因,而并没有使用hugo生成我的静态博客。虽然hugo只是一个静态博客生成器,和网站的theme并无太大关系,但是我一个theme的美观足以促使我使用这个项目。hugo没有代替hexo在我心中的地位,但是她也给了我很深的印象。

当看到《Writing a Static Blog Generator in Go》这篇文章的时候,我就表现出了极大的兴趣。这篇博客写的非常好,我迫不及待的翻译了这篇文章。让我受益匪浅。

原理

静态博客原理很简单,其核心要素是:1. 解析yaml数据 2. 将markdown文件转成HTML文件 3. 模板渲染 4. 路由

解析yaml数据

每篇文章都必须有Title,Date,Tags,Category数据,我们将这些数据称之为metadata。在每篇markdown的文章开头都加上yaml格式的metadata,以便我们获取这些信息。

解析markdown文件的内容时候,截取metadata数据并Unmarshal。
在解析文件的时候,顺便检测more标签,当这个标签单独出现在一行的时候,就表示more之前的内容是概览内容。在首页展示的就是more之前的内容(概览)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
func (p *Post) ParseMetaData() (err error) {
buf := bufio.NewReader(bytes.NewReader(p.sourceData))
metaData := []byte{}
overview := []byte{}
content := []byte{}
metaStart := false
metaFinished := false
overviewFinished := false
// get the data between the line only have "---"
for true {
line, isPrefix, lineErr := buf.ReadLine()
if lineErr != nil {
break
}
for _, s := range []string{"---", "--- ", " --- ", " ---", "----"} {
if string(line) == s {
if metaStart == true {
metaFinished = true
}
metaStart = true
}
}
for _, s := range []string{"<!-- more -->", "<!-- more-->", "<!--more -->"} {
if strings.Contains(string(line), s) {
overviewFinished = true
}
}

if !isPrefix {
line = append(line, []byte("\n")...)
}
if metaStart && !metaFinished {
metaData = append(metaData, line...)
}
if metaFinished && !overviewFinished {
overview = append(overview, line...)
}
if metaFinished {
content = append(content, line...)
}
}
if !metaFinished {
return fmt.Errorf("Cannot find the metadata of the post!")
}
err = yaml.Unmarshal(metaData, p.Meta)
if err != nil {
return
}
if p.Meta.Title == "" {
return fmt.Errorf("The title of the post is empty!")
}
if p.Meta.Date == "" {
return fmt.Errorf("The Date of the post is empty!")
}
p.sourceData = content
p.overviewData = overview
return
}

将markdown文件转成HTML文件

几乎每个主流的现代语言(nodejs,python,go)都有markdown转成HTML的包,直接调用包就可以将Markdown文件转成HTML文件了。我使用的是Go语言,使用blackfriday 包将Markdown转成html数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (p *Post) Convert() (err error) {
p.htmlData = blackfriday.MarkdownCommon(p.sourceData)
p.htmlData, err = p.fixHtmlData(p.htmlData)
if err != nil {
return err
}
p.overviewHtml = blackfriday.MarkdownCommon(p.overviewData)
p.overviewHtml, err = p.fixHtmlData(p.overviewHtml)
if err != nil {
return err
}

return
}

为了使Markdown文件中的Code数据能够高亮,使用syntaxhighligh将代码的html高亮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (p *Post) fixHtmlData(data []byte) (newData []byte, err error) {
reader := bytes.NewReader(data)
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
return newData, fmt.Errorf("Creating NewDocumentFromReader Err:%v", err)
}
doc.Find("code[class*=\"language-\"]").Each(func(i int, s *goquery.Selection) {
oldCode := s.Text()
formatted, _ := syntaxhighlight.AsHTML([]byte(oldCode))
s.Empty()
s.AppendHtml(string(formatted))
})
newDoc, err := doc.Html()
if err != nil {
return newData, fmt.Errorf("Generating new html err: %v", err)
}
// replace the html tags that we donnot need
for _, tag := range []string{"<html>", "</html>", "<head>", "</head>", "<body>", "</body>"} {
newDoc = strings.Replace(newDoc, tag, "", 1)
}
return []byte(newDoc), err
}

模板渲染

Markdown文件的数据只是页面展示里的文章内容。页面的布局,样式和页面的其他内容在模板中。我们需要做的就是将解析好的html数据放到模板中,然后渲染出我们想要的HTML。在这里我们只需要Go官方的”html/template”包就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// generate index.html by template
func GenerateIndexFile(t *template.Template, data interface{}, dir string) error {
if err := os.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("Create directory error:%v", err)
}
f, err := os.Create(dir + "/index.html")
if err != nil {
return fmt.Errorf("Creating file %s Err:%v", dir, err)
}
defer f.Close()
writer := bufio.NewWriter(f)

if err := t.Execute(writer, data); err != nil {
return fmt.Errorf("Executing template Error: %v", err)
}
if err := writer.Flush(); err != nil {
return fmt.Errorf("Writing file Err: %v", err)
}
return nil
}

此时HTML文件就是显示的页面了。

路由

静态博客的路由就是文件的目录路径。我将生成的HTML文件命名为index.html,因为路径下存在index.html的话,web服务器会默认加载路径下名为index.html的文件。我以文章的Date作为文章的路径。

首页就是在博客的根目录生成index.html,其内容就是将文章的列表放在index.tpl中渲染得到的数据。当首页展示不了太多的文章列表的时候,就需要分页。分页就是在根目录下建立page目录(这也就是page的路由),在page目录下建立index.html,内容同上。

Tags和Category的原理是一样的。建立Tags目录,目录中建立index.html,内容为Tags的列表。每一个tag都建立一个目录,目录下都有一个index.html,其内容是此tag的文章列表。

结语

空想是不行的,要多学,多做,多练!
不能再再再再再再堕落了!!!