Изучаем Go - Сборка DevLog Часть 02
Создаем блог разработчика
В прошлой статье мы с вами просто прочитали маркдаун файл и спарсили его содержимое. Это пусть и не очень классный, но необходимый первый шаг к созданию генератора статического сайта. На этот, для достижения нашей цели придется также усердно поработать. Начнем с того, что доработаем нашу структуру, представим новую функцию для некоторого базового тестирования загруженной разметки, а затем выведем настоящий реальный HTML. И, как обычно, если вы все еще не читали предыдущий пост, я настоятельно рекомендую прочитать его прямо сейчас, ведь сейчас мы просто возьмем код оттуда и будем его дополнять.
Переходим к кодингу
Начнем с дополненного набора импорта. Мы добавляем в html/template, BlackFriday и BlueMonday. Если вы читали предыдущий пост, в котором мы разбирали отправку электронного письма, вы должны быть знакомы с text/template, html/template, что, на самом деле, в основе то то же самое, но с добавленными функциями для работы с HTML.
package main import ( "bytes" "fmt" "html/template" "io/ioutil" "os" "strings" "github.com/microcosm-cc/bluemonday" "github.com/russross/blackfriday" yaml "gopkg.in/yaml.v2" ) const delim = "---"
Структурные изменения
Для правильной работы, необходимо экспортировать элементы внутри нашей структуры, чтобы шаблон мог использовать те данные, которые в него входят. Обратите внимание, что мы также настраиваем PostBody, чтобы иметь тип template.HTML. Вскоре немного углубимся в это.
type post struct {
  Title       string
  Published   bool
  Description string
  Tags        []string
  CoverImage  string
  Series      string
  PostBody    template.HTML
}Шаблон HTML
Вот мы и медленно продвигаемся вперед, так что в этой версии будет использоваться только один встроенный шаблон. Это далеко не окончательное решение, но пока этого достаточно для того, чтобы обозначить нашу цель. Через несколько итераций, мы перейдем к загрузке шаблонов из файлов.
var templ = `
  
    {{.Title}} 
    
    
    
    
    
    
  
  
    
      {{.Title}}
      {{.PostBody}}
    
  
`Что, если isNil() выдает ошибку?
Так как функция loadFile() вообще не изменилась, мы просто пропустим все остальное до isNil(). Если вы читали предыдущий пост, то вспомните, что я писал о том, как было легко вызвать панику, если не был задан вопрос. isNil() - сможет немного облегчить нам жизнь и будет гарантировать, что наши входящие данные установлены. Мы будем расширять его, чтобы возвращать корректные ошибки, а не просто останавливать выполнение программы. В текущей ситуации мы будем использовать его только в main(), но не прямо сейчас, так как он пока закомментирован. (cough)
 func loadFile(s string) (b []byte, err error) {
  f, err := ioutil.ReadFile(s)
  if err != nil {
    return nil, err
  }
  return f, nil
}
func isNil(i interface{}) bool {
  if i != nil {
    return false
  }
  return true
}
func main() {
  f, err := loadFile("test.md")
  if err != nil {
    panic(err)
  }
  b := bytes.Split(f, []byte(delim))
  if len(b) < 3 || len(b[0]) != 0 {
    panic(fmt.Errorf("Front matter is damaged"))
  }
  pBody := f[len(b[1])+(len(delim)*2):]
  m := make(map[string]interface{})
  err = yaml.Unmarshal([]byte(b[1]), &m)
  if err != nil {
    msg := fmt.Sprintf("error: %v\ninput:\n%s", err, b[1])
    panic(msg)
  }
  p := &post{}Не используем Nil
Здесь можно увидеть начало использования isNil(). Я не очень доволен тем, что он все еще здесь, но у меня было мало времени на то, чтобы его заменить. Думаю, что пока все будет работать как нужно, так как мы все еще работаем с "known-good" тестовым файлом. Мне нужно будет более внимательно рассмотреть эту часть.
  // if isNil(m["title"]) {
  // in final we wouldn't actually panic just reject the file and continue on
  //   panic(err)
  // } else {
  p.Title = m["title"].(string)
  p.Published = m["published"].(bool)
  p.Description = m["description"].(string)
  // TODO: Strip space after comma prior to parse?
  tmp := m["tags"].(string)
  p.Tags = strings.Split(tmp, ", ")
  p.CoverImage = m["cover_image"].(string)
  p.Series = m["series"].(string)Тело запроса POST
В прошлый раз я задумался о том, как мы можем продолжать использовать файл, который уже загрузили, чтобы получить тело сообщения. Давайте рассмотрим один из способов! У нас есть данные файла в f, который является []byte, поэтому мы можем взять длину нашего заголовка YAML, b[1] и добавить ее к длине, в два раза превышающей символ разделителя начальной части (---). Для начала используем этот пример и идем до конца f, получая в результате то, что нам нужно, f[x:]. Этот код, вероятно, слишком неподготовлен для «продакшена», пока что мы будем рисковать, но разделенная проверка и тот факт, что YAML необходимо правильно проанализировать, должны, по крайней мере, на данный момент, дать нам некоторое покрытие тестовыми случаями. Я думаю, мы еще вернемся к этому в будущем.
pBody := f[len(b[1])+(len(delim)*2):]
Получив pBody, нам нужно передать его в blackfriday. Мы используем парсер разметки по умолчанию в blackfriday, который отработав, вернет нам []byte, который мы переворачиваем и передаем в bluemonday. Я делаю это почти так же, как это было предложено в файле readme, посвященном «blackfriday», и использую политику для сохранения блоков изолированного кода. Bluemonday используется для санации HTML. Используя данные подход, мы пытаемся убрать все, что непреднамеренно вызывало ошибки в наших сообщениях. И вот, мы собрали все воедино и задампили в стандартный вывод. Так что прямо сейчас, если мы на сервере, можно запустить go run main.go > /var/www/dev.shindakun.net/index.html и получить хотя бы одностраничный статический веб-сайт. Не сказать, что очень круто и быстро, но довольно стабильно мы с вами движемся вперед.
out := blackfriday.Run(pBody)
  bm := bluemonday.UGCPolicy()
  bm.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
  p.PostBody = template.HTML(bm.SanitizeBytes(out))
  t := template.Must(template.New("msg").Parse(templ))
  err = t.Execute(os.Stdout, p)
  if err != nil {
    panic(err)
  }
}Выводим результат

Все верно! Плоды нашего труда в настоящее время (на момент написания этой статьи) живут на dev.shindakun.net.
В будущем
Мы написали много базисного кода, но все же нам еще многое предстоит сделать. В следующий раз я надеюсь, что мы реализуем загрузку и вывод нескольких файлов. Так же мы вернемся назад и улучшим обработку ошибок. Наверное, мне стоит подумать о добавлении тестов.
Другие статьи из цикла:
- Изучаем Go - создание загрузчика (часть 1)
 - Изучаем Go - создание загрузчика (часть 2)
 - Изучаем Go - создание загрузчика (часть 3)
 - Изучаем Go - создание загрузчика (часть 4)
 - Изучаем Go - создание загрузчика (часть 5)
 - Изучаем Go - Использование REST API
 - Изучаем Go - Продолжаем работать с REST API
 - Изучаем Go - Отправка REST-запросов
 - Изучаем Go - Используем REST API в паре с шаблонами проектирования
 - Изучаем Go - Повторная отправка электронной почты через API
 - Изучаем Go – Наконец-то используем модули
 - Изучаем Go - Давайте станем модульными снова!
 - Изучаем Go - Сборка DevLog Часть 01
 - Изучаем Go - Сборка DevLog Часть 02
 - Изучаем Go - Сборка DevLog Часть 03
 - Изучаем Go - Сборка DevLog Часть 04