У вас включен AdBlock или иной блокировщик рекламы.

Пожалуйста, отключите его, доход от рекламы помогает развитию сайта и появлению новых статей.

Спасибо за понимание.

В другой раз
DevGang блог о програмировании
Авторизоваться

Изучаем Go - Сборка DevLog Часть 01

Создаем блог разработчика

Я хотел начать кросс-постинг своих сообщений из блога dev.to на свой собственный сайт, но пока не определился с тем, как это сделать. Существует несколько вариантов: можно установить новый экземпляр Ghost или использовать генератор статического сайта. Мне нравится Ghost! Даже совсем не хочется признаваться, что в последнее время я не работал с ним. Использовать его в моем «dev site» кажется немного неправильным, особенно когда вспоминается, что основным направлением сайта будет Go. Мне просто очень нравится возиться с этим всем. Gophercon или баста! (Вероятно, баста, потому что я не уверен, что могу позволить себе «go», хах-хах.)

Хьюго или не Хьюго

Если вы натыкались на мир "генераторов статических сайтов", возможно, вы слышали о Хьюго. Хьюго - один из самых популярных генераторов, и, к тому же, написан на Go. Я поигрался с ним и даже внес один запрос на сайт разработчика на получение информации, он и правда мне понравился. Тем не менее, мне кажется, что использование Хьюго противоречит посылу и цели моих постов. Мы же с вами будем пробовать писать код и с каждым разом все больше и больше развиваться и познавать. Итак, давайте начнем...

Не Хьюго

Итак, мы решили, что не будем использовать Хьюго для нашего сайта. Так что же мы тогда будем делать? Я так и не смог определиться, поэтому просто попробуем кое-что сделать, и посмотрим, как все пойдет. Я думаю, что в первой итерации сайта dev , мы будет стараться реализовывать статический сайт, для чего настроим все, что находится за Nginx, и конвертируем существующие маркдаун файлы в HTML, используя собственный генератор статического сайта. Затем мы переключимся на хостинг с помощью программы Go, и как раз рассмотрим создание веб-серверов используя Go.

Форсмажоры

Пора уже разобраться с этим. Начнем с одного тестового маркдаун файла. Во-первых, нам нужно открыть его. Это можно очень легко сделать с помощью ioutils.ReadFile(), поэтому мы создадим небольшую функцию loadFile(). Функция будет принимать строку, в данном случае это будет путь к файлу и возвращать часть байтов или ошибку.

---
title: "test file"
published: false
description: "This is the description from the test file"
tags: these, are, tags
cover_image: https://someimage
series: "Attempting to Learn Go"
---
Post body goes here.

Итак, начинается все с строки --- после которой следует YAML, а затем мы используем ту же строку ---, для завершения. Быстро проведем проверку того, что файл имеет верное начало. Немного подумав, я решил, что быстрее всего в данном случае будет просто использовать метод bytes.Split() передаваемый в части байтов и разделителе начальной части вопроса ---. Разделение должно дать нам фрагмент фрагментов, который, в свою очередь, будет допустимым файлом и иметь длину не менее 3. Первый (b[0]) должен быть пустым, второй (b[1]) будет содержать []byte, который составляет нашу начальную часть вопроса, а все оставшиеся биты должны содержать остальную частью нашего post тела.

Давайте закодим это

Начнем, как обычно, с того, что объявим наш пакет и импорт. Для работы с YAML внутри заголовка исходного текста, будем использовать простые компоненты, из стандартной библиотеки Go, а так же наш импорт. Стандартная библиотека Go не имеет поддержки YAML, поэтому просто наберите в Google, "golang yaml" и буквально первая ссылка на GitHub сможет вам помочь.

package main

import (
  "bytes"
  "fmt"
  "io/ioutil"
  "strings"

  yaml "gopkg.in/yaml.v2"
)

Так как с импортами покончено, давайте создадим нашу константу-разделитель. Мы знаем основное содержание заголовка, поэтому давайте создадим для нее структуру.

const delim = "---"

type post struct {
  title       string
  published   bool
  description string
  tags        []string
  coverImage  string
  series      string
}

На данном шаге у нас будет только одна функция вне main(), и это будет функция loadFile(). Вероятно, мы изменим это в следующих ревизиях, не думайте, что мы застряли на методе main(), просто пока мы используем короткую и красивую реализацию. Очевидно, что стандартная библиотека делает всю тяжелую работу за нас.

func loadFile(s string) (b []byte, err error) {
  f, err := ioutil.ReadFile(s)
  if err != nil {
    return nil, err
  }
  return f, nil
}

Отправляемся к методу main()

 Начнем с вызова метода loadFile() для загрузки текстового файла в память.

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"))
  }

И первое с чего мы начнем - создадим новую карту (map), map[string]interface{}. Благодаря этому данные YAML, находящиеся в начале, будут извлекаться из верхней части файла. Детально рассматривать интерфейсы мы не будем, поэтому я рекомендую прочитать отличную статью Джорда Орелли «Как использовать интерфейсы в Go». Мы собираемся передать b[1] прямо в интерфейс. Нам очень повезло с тем, что если реализовывать это таким образом, мы будем уверены в том, что неправильно отформатированный YAML выдаст ошибку. Наверняка есть и частные случаи, но я уверен, что в этой версии все будет в порядке. Для создания карты мы будем использовать yaml.Unmarshal() . Таким образом, объект post будет выглядеть примерно так: &post{title: "test file", ...}.

  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, для хранения наших данных. Позже мы будем использовать эти данные для отправки в HTML-шаблоны. Мы не можем просто использовать m["title"] и назначить его в нашу структуру, поскольку все в настоящее время имеет тип interface{}, поэтому мы будем задавать тип с помощью ключевого .(Type). Кроме того, можно использовать что-то вроде fmt.Sprintf("% v", m["title"]), чтобы привести интерфейс к нужному типу, к примеру к строке, как здесь. Пройдем дополнительный шаг с тегами, чтобы удостовериться, что у нас получился фрагмент, а не строка значений, разделенных запятыми. В этот раз мы просто выведем нашу структуру, чтобы убедиться, что она выглядит так, как мы и ожидали.

  p := &post{}
  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)

  fmt.Printf("%#v\n", p)
}

Теперь у нас в коде есть один существенный недостаток, который мы должны рассмотреть - в настоящее время предполагается, что любой загруженный файл будет иметь точный заголовок передней части. И если, скажем, кто-то попытается загрузить маркдаун файл у которого не было метки серии, это вызовет проблемы. Мы создадим функцию для проверки значения и вернем ее в следующем посте.

➜  go run main.go
&main.post{title:"test file", published:false, 
description:"This is the description from the test file",
tags:[]string{"these", "are", "tags"},
coverImage:"https://someimage",
series:"Attempting to Learn Go"}

Планы на будущее

К следующей части подумайте об одном интересном моменте - используя файл, который мы загрузили, каким образом можно было бы извлечь только тело сообщения? Мы обязательно рассмотрим это в следующий раз и вернемся к шаблонам при выводе загруженного маркдауна.

Другие статьи из цикла:

#Golang