package main import ( "bufio" "bytes" "flag" "fmt" "html/template" "io" "io/ioutil" "log" "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}}
` func getContentsOf(r io.Reader) ([]byte, error) { return ioutil.ReadAll(r) } 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, delimiter string) ([][]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 } // makePost creates the post struct, returns that and the template HTML func makePost(fm map[string]interface{}, contents []byte, s [][]byte) (*template.Template, *post, bool) { post := &post{} post.Published = false pubIntf, ok := fm["published"] if ok { if published, ok := pubIntf.(bool); ok { post.Published = published } } if !post.Published { return nil, nil, true } post.Title = "" titleIntf, ok := fm["title"] if ok { if title, ok := titleIntf.(string); ok { post.Title = title } } if post.Title == "" { return nil, nil, true } post.Description = "" descIntf, ok := fm["description"] if ok { if description, ok := descIntf.(string); ok { post.Description = description } } post.Tags = []string{} tagsIntf, ok := fm["tags"] if ok { if tags, ok := tagsIntf.(string); ok { post.Tags = strings.Split(tags, ", ") } } post.CoverImage = "" covIntf, ok := fm["cover_image"] if ok { if coverImage, ok := covIntf.(string); ok { post.CoverImage = coverImage } } post.Series = "" seriesIntf, ok := fm["series"] if ok { if series, ok := seriesIntf.(string); ok { post.Series = series } } pBody := contents[len(s[1])+(len(delimiter)*2):] bf := 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(bf)) tm := template.Must(template.New("post").Parse(postTempl)) return tm, post, false } func writeIndex(idx index, destination string) error { indexFile, err := os.Create(destination + "/" + "index.html") if err != nil { return err } defer indexFile.Close() buffer := bufio.NewWriter(indexFile) tm := template.Must(template.New("index").Parse(indexTempl)) err = tm.Execute(buffer, idx) if err != nil { return err } buffer.Flush() return nil } func main() { var idx index destination := flag.String("destination", "", "destination folder") source := flag.String("source", "", "source directory") flag.Parse() if _, err := os.Stat(*destination); os.IsNotExist(err) { err := os.Mkdir(*destination, 0777) if err != nil { panic(err) } } else { log.Panicf("error: destination '%s' already exists", *destination) } _, err := ioutil.ReadDir(*destination) if err != nil { panic(err) } srcDir, err := ioutil.ReadDir(*source) if err != nil { panic(err) } for _, file := range srcDir { if fileName := file.Name(); strings.HasSuffix(fileName, ".md") { openedFile, err := os.Open(fileName) if err != nil { log.Println(fileName, err) continue } contents, err := getContentsOf(openedFile) if err != nil { openedFile.Close() log.Println(fileName, err) continue } openedFile.Close() s, err := splitData(contents, delimiter) if err != nil { log.Println(fileName, err) continue } fm, err := parseFrontMatter(s[1]) if err != nil { msg := fmt.Sprintf("%v\n", err) log.Println(fileName, msg) continue } template, post, skip := makePost(fm, contents, s) if !skip { trimmedName := strings.TrimSuffix(fileName, ".md") outputFile, err := os.Create(*destination + "/" + trimmedName + ".html") if err != nil { log.Println(err) continue } buffer := bufio.NewWriter(outputFile) err = template.Execute(buffer, post) if err != nil { panic(err) } buffer.Flush() outputFile.Close() indexLinks := Page{ FileName: trimmedName + ".html", Title: post.Title, } idx.Pages = append(idx.Pages, indexLinks) } } } if len(idx.Pages) > 0 { err := writeIndex(idx, *destination) if err != nil { log.Println(err) } } }