DevGang
Авторизоваться

Изучаем 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)
}

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

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

#Golang
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить