/
post.go
205 lines (172 loc) · 4.12 KB
/
post.go
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package main
import (
"bufio"
"bytes"
"encoding/xml"
"fmt"
"html/template"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
// Create a PostCreator that operate in specified directory
func NewPostCreator(dir string) PostCreator {
return PostCreator{dir}
}
// A PostCreator create folder and files for a new post
type PostCreator struct {
// directory to create post
dir string
}
// Create a post with specified title
func (pc PostCreator) Create(title string) error {
date := time.Now().Format(ISO8601Date)
postDir := filepath.Join(pc.dir, Prettify(title))
if err := os.Mkdir(postDir, os.ModePerm); err != nil {
return fmt.Errorf("Unable to create folder %s due to %v", postDir, err)
}
file, err := os.Create(filepath.Join(postDir, "post.md"))
if err != nil {
return fmt.Errorf("Unable to create post.md due to %v", err)
}
content := fmt.Sprintf(`---
date: %s
title: %s
---
`, date, title)
file.WriteString(content)
return nil
}
// PostParser parse file into Post
type PostParser struct {
converter MarkdownConverter
conf Configuration
}
func NewPostParser(conf Configuration) PostParser {
return PostParser{NewGithubMarkdownConverter(conf), conf}
}
// Parse will parse Post from specified post folder
func (pp PostParser) Parse(dir string) Post {
post := NewPost()
post.dir = dir
post.key = filepath.Base(dir)
post.variables, post.content = pp.parsePostFile(dir)
post.htmlContent, _ = pp.converter.Convert(post.content)
post.url = pp.conf.BaseUrl + filepath.Base(dir) + "/"
return *post
}
func (pp PostParser) parsePostFile(dir string) (map[string]string, string) {
f, _ := os.Open(filepath.Join(dir, PostFileName))
defer f.Close()
variables := make(map[string]string)
content := ""
isInVariablesBlock := false
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !isInVariablesBlock && line == "---" {
isInVariablesBlock = true
continue
} else if isInVariablesBlock && line == "---" {
isInVariablesBlock = false
continue
}
if isInVariablesBlock {
if key, value, err := pp.parseVariable(line); err == nil {
variables[key] = value
}
} else {
content += (line + "\n")
}
}
return variables, content
}
func (pp PostParser) parseVariable(line string) (key string, value string, err error) {
pair := strings.SplitN(line, ":", 2)
if len(pair) == 2 {
key = strings.TrimSpace(pair[0])
value = strings.TrimSpace(pair[1])
} else {
err = fmt.Errorf("Invalid post variable format : %s", line)
}
return
}
func NewPost() *Post {
p := new(Post)
return p
}
// Post represented a post in blog
type Post struct {
// post folder name as the post identifier
key string
// variables in post file
variables map[string]string
// path of original post folder
dir string
// url of the post
url string
// content in Markdown
content string
// content in HTML converted from Markdown
htmlContent string
}
func (p Post) Title() string {
return p.variables["title"]
}
func (p Post) Date() string {
return p.variables["date"]
}
func (p Post) RSSDate() string {
return p.Time().Format(time.RFC1123)
}
func (p Post) Time() time.Time {
t, err := time.Parse("2006-01-02", p.Date())
if err != nil {
panic(fmt.Sprintf("Post %s has an invalid date", p.Title()))
}
return t
}
func (p Post) Content() string {
return p.content
}
func (p Post) Excerpt() string {
r := regexp.MustCompile("<p>(.+?)</p>")
result := r.FindStringSubmatch(p.htmlContent)
if len(result) > 1 {
buf := new(bytes.Buffer)
xml.EscapeText(buf, []byte(result[1]))
return buf.String() + " ..."
}
return ""
}
func (p Post) HTMLContent() template.HTML {
return template.HTML(p.htmlContent)
}
func (p Post) Key() string {
return p.key
}
func (p Post) Dir() string {
return p.dir
}
func (p Post) Url() string {
return p.url
}
func (p Post) Variable(key string) string {
return p.variables[key]
}
// Posts represented an array of Post
type Posts []Post
func (p Posts) Len() int {
return len(p)
}
func (p Posts) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
type PostsByDateDesc struct {
Posts
}
func (s PostsByDateDesc) Less(i, j int) bool {
return !s.Posts[i].Time().Before(s.Posts[j].Time())
}