Изучаем 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"}
Планы на будущее
К следующей части подумайте об одном интересном моменте - используя файл, который мы загрузили, каким образом можно было бы извлечь только тело сообщения? Мы обязательно рассмотрим это в следующий раз и вернемся к шаблонам при выводе загруженного маркдауна.
Другие статьи из цикла:
- Изучаем 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