Изучаем Go - Сборка DevLog Часть 04
Создаем блог разработчика
Вы только посмотрите! Мы вернулись и первым делом я хочу начать с благодарности за конструктивные комментарии к последнему посту. Я пытаюсь уделить время рассмотрению любого комментария - в конце концов, я все еще учусь, поэтому я (по общему признанию) не все делаю правильно. Наверняка я могу сказать только то, что код компилируется на моей машине))).
Сделаем шаг назад
Просмотрев код и комментарии, я понял, что, возможно, я даю слишком краткие имена переменным. Go обычно известен тем, что имеет короткие имена переменных, но, похоже, что сопровождение кода ухудшается. Я принял это к сведению и начал делать имена переменных более явными. Все еще есть случаи, когда я использую короткое имя, например, b для байта[]. Надеюсь, эти случаи покажутся вам довольно ясными и легкими для отслеживания и понимания, так как я проследил за тем, что они объявлены рядом с тем местом, где используются.
Итак, посмотрим на это в коде
package mainimport (
"bufio"
"bytes"
"fmt"
"html/template"
"io"
"io/ioutil"
"os"
"regexp"
"strings" "github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
yaml "gopkg.in/yaml.v2"
)const delimiter = "---"type post struct {
Title string
Published bool
Description string
Tags []string
CoverImage string
Series string
PostBody template.HTML
}type index struct {
Pages []Page
}type Page struct {
FileName string
Title string
}Я добавил шаблон индекса, чтобы мы могли создать простую страницу индекса для сайта. Мы передаем нашу структуру индекса и перемещаемся по страницам, имея возможность получить каждую страницу, которая содержит имя файла и заголовок страницы.
var indexTempl = `shindakun's dev site {{ range $key, $value := .Pages }} {{ $value.Title }} {{ end }}`var postTempl = `{{.Title}} `{{.Title}}
{{.PostBody}}
В комментариях @ladydascalie указал, что нам лучше использовать io.Reader, что позволит мне загрузить файл и легко смоделировать тесты к этой функции. К тому же я переименовал функцию, чтобы она стала более наглядной. Я решил начать с getContentsOf(), поэтому в коде он читается как getContentsOf(openFile).
func getContentsOf(r io.Reader) ([]byte, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
return b, nil
}func parseFrontMatter(b []byte) (map[string]interface{}, error) {
fm := make(map[string]interface{})
err := yaml.Unmarshal(b, &fm)
if err != nil {
msg := fmt.Sprintf("error: %v\ninput:\n%s", err, b)
return nil, fmt.Errorf(msg)
}
return fm, nil
}func splitData(fm []byte) ([][]byte, error) {
b := bytes.Split(fm, []byte(delimiter))
if len(b) < 3 || len(b[0]) != 0 {
return nil, fmt.Errorf("Front matter is damaged")
}
return b, nil
}Я заинтересовался предложением @krusenas о том, чтобы на самом деле сделать некоторую обработку для makePost(). Мы не всегда будем работать с заведомо хорошими файлами, и мы действительно должны учитывать, что на вход может попасть и «плохой» YAML. Вероятно, есть менее подробный способ исправления, но пока он работает. Однако этого не сделано! Я напишу хотя бы еще один пост в этой серии постов, нам нужно будет отклонить файл, если заголовок отсутствует. В конце концов, если заголовка нет, нам не на что ссылаться в индексе.
// makePost creates the post struct, returns that and the template HTML
func makePost(fm map[string]interface{}, contents []byte, s [][]byte) (*template.Template, *post) {
post := &post{} // TODO: Reject post if we don't have a title
titleIntf, ok := fm["title"]
if ok {
title, ok := titleIntf.(string)
if ok {
post.Title = title
} else {
post.Title = ""
}
} else {
post.Title = ""
} pubIntf, ok := fm["published"]
if ok {
published, ok := pubIntf.(bool)
if ok {
post.Published = published
} else {
post.Published = false
}
} else {
post.Published = false
} descIntf, ok := fm["description"]
if ok {
description, ok := descIntf.(string)
if ok {
post.Description = description
} else {
post.Description = ""
}
} else {
post.Description = ""
} tagsIntf, ok := fm["tags"]
if ok {
tags, ok := tagsIntf.(string)
if ok {
post.Tags = strings.Split(tags, ", ")
} else {
post.Tags = []string{}
}
} else {
post.Tags = []string{}
} covIntf, ok := fm["cover_image"]
if ok {
coverImage, ok := covIntf.(string)
if ok {
post.CoverImage = coverImage
} else {
post.CoverImage = ""
}
} else {
post.CoverImage = ""
} seriesIntf, ok := fm["series"]
if ok {
series, ok := seriesIntf.(string)
if ok {
post.Series = series
} else {
post.Series = ""
}
} else {
post.Series = ""
} pBody := contents[len(s[1])+(len(delimiter)*2):] out := blackfriday.Run(pBody) bm := bluemonday.UGCPolicy()
bm.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
post.PostBody = template.HTML(bm.SanitizeBytes(out)) tm := template.Must(template.New("post").Parse(postTempl))
return tm, post
}Наша функция writeIndex() довольно проста, и мы можем немного расширить ее, чтобы у нас была только одна функция записи в файл. У нас в основном один и тот же код в нашем основном цикле. Но сейчас, я считаю приемлимым использовать немного дополнительного кода.
func writeIndex(idx index) {
indexFile, err := os.Create("index.html")
if err != nil {
panic(err)
}
defer indexFile.Close()
buffer := bufio.NewWriter(indexFile)
tm := template.Must(template.New("index").Parse(indexTempl))
err = tm.Execute(buffer, idx)
if err != nil {
panic(err)
}
buffer.Flush()
}func main() {
var idx index dir, err := ioutil.ReadDir(".")
if err != nil {
panic(err)
} for _, file := range dir {
if fileName := file.Name(); strings.HasSuffix(fileName, ".md") { openedFile, err := os.Open(fileName)
if err != nil {
panic(err)
} contents, err := getContentsOf(openedFile)
if err != nil {
panic(err)
}
s, err := splitData(contents)
if err != nil {
panic(err)
} fm, err := parseFrontMatter(s[1])
if err != nil {
msg := fmt.Sprintf("error: %v\ninput:\n%s", err, s[1])
panic(msg)
} template, post := makePost(fm, contents, s) trimmedName := strings.TrimSuffix(fileName, ".md")
outputFile, err := os.Create(trimmedName + ".html")
if err != nil {
panic(err)
}
defer outputFile.Close() buffer := bufio.NewWriter(outputFile) err = template.Execute(buffer, post)
if err != nil {
panic(err)
}
buffer.Flush() indexLinks := Page{
FileName: trimmedName + ".html",
Title: post.Title,
}
idx.Pages = append(idx.Pages, indexLinks)
}
}
writeIndex(idx)
}В итоге, на этой неделе мы не сделали слишком много изменений, только провели небольшую очистку и добавление кода для написания нашего индексного файла. Это делает код довольно полным, но не идеальным, так как мы не можем указать каталоги ввода и вывода, но все еще используем их. Во всяком случае, на этой неделе у меня было мало времени, поэтому вскоре я постараюсь добавить наши последние биты кода, чтобы закрыть эту часть серии на следующей неделе. Интересно, чем же заняться после ... Есть идеи?
Другие статьи из цикла:
- Изучаем Go - создание загрузчика (часть 1)
- Изучаем Go - создание загрузчика (часть 2)
- Изучаем Go - создание загрузчика (часть 3)
- Изучаем Go - создание загрузчика (часть 4)
- Изучаем Go - создание загрузчика (часть 5)
- Изучаем Go - Использование REST API
- Изучаем Go - Продолжаем работать с REST API
- Изучаем Go - Отправка REST-запросов
- Изучаем Go - Используем REST API в паре с шаблонами проектирования
- Изучаем Go – Наконец-то используем модули
- Изучаем Go - Давайте станем модульными снова!
- Изучаем Go - Сборка DevLog Часть 01
- Изучаем Go - Сборка DevLog Часть 02
- Изучаем Go - Сборка DevLog Часть 03
- Изучаем Go - Сборка DevLog Часть 04