Skip to content

Instantly share code, notes, and snippets.

@k-nishijima
Last active June 5, 2020 06:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save k-nishijima/28892058ba97eec540adb4551a737a57 to your computer and use it in GitHub Desktop.
Save k-nishijima/28892058ba97eec540adb4551a737a57 to your computer and use it in GitHub Desktop.
markdownファイルからWebflow向けCSVファイルを生成
package main
import (
"bufio"
"bytes"
"encoding/csv"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/renderer/html"
"gopkg.in/yaml.v2"
)
type Source struct {
Title string `yaml:"title"`
Description string `yaml:"description"`
DateStr string `yaml:"date"`
Slug string `yaml:"slug"`
Content string `fm:"content" yaml:"-"`
}
type MediumSlug struct {
AuthorName string
CustomLink string
GUID string
}
type CMSItem struct {
Name string
Title string
DateStr string
Slug string
Author string
Summary string
Body string
MarkdownBody string
// Category []string
// Tags []string
}
func dirwalk(dir, ext string) []string {
files, err := ioutil.ReadDir(dir)
if err != nil {
panic(err)
}
var paths []string
for _, file := range files {
if file.IsDir() {
paths = append(paths, dirwalk(filepath.Join(dir, file.Name()), ext)...)
continue
}
if filepath.Ext(file.Name()) == ".md" {
paths = append(paths, filepath.Join(dir, file.Name()))
}
}
return paths
}
func normalize(str string) string {
return strings.ReplaceAll(str, "\n", "")
}
func newSource(fileName string) *Source {
fp, err := os.Open(fileName)
if err != nil {
panic(err)
}
defer fp.Close()
header := ""
body := ""
delim := 0
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
line := scanner.Text()
if line == "---" {
delim++
if delim == 2 {
header += line + "\n"
continue
}
}
// fmt.Printf("%v:%s\n", delim, line)
if delim < 2 {
header += line + "\n"
} else {
body += line + "\n"
}
}
front := make(map[string]interface{})
if err := yaml.Unmarshal([]byte(header), front); err != nil {
panic(err)
}
return &Source{
Title: front["title"].(string),
Description: normalize(front["description"].(string)),
DateStr: front["date"].(string),
Slug: front["slug"].(string),
Content: body,
}
}
func createSlug(org string) *MediumSlug {
/*
元のslugは /@r3_nishijima/cls-kochi-201910-ae79bd7c2c97 のような
userId + customelink + GUID
というフォーマットになっている
*/
author := strings.Split(org, "/")[1]
author = strings.ReplaceAll(author, "@r3_", "")
key := strings.Split(org, "/")[2]
link := key[0:strings.LastIndex(key, "-")]
guid := key[strings.LastIndex(key, "-")+1 : len(key)]
return &MediumSlug{
AuthorName: author,
CustomLink: link,
GUID: guid,
}
}
func newCMSItem(source Source) *CMSItem {
orgSlug := createSlug(source.Slug)
item := CMSItem{
Name: source.Title,
Title: source.Title,
DateStr: source.DateStr,
Slug: orgSlug.CustomLink,
Author: orgSlug.AuthorName,
Summary: source.Description,
MarkdownBody: source.Content,
}
item.Body = normalize(item.renderHTML()) // CSV用に改行を全て削除
return &item
}
func converter(md string) string {
v := strings.ReplaceAll(md, "### ", "# ") // h3 -> h1
v = strings.ReplaceAll(v, "#### ", "## ") // h4 -> h2
/**
自サイト内のリンクは
https://blog.r3it.com/20180327-gusuku-customine-release-11685ed8892f
https://www.r3it.com/blog/20180327-gusuku-customine-release
と変換できればよい。
*/
rep := regexp.MustCompile(`https://blog.r3it.com/([0-9a-zA-Z?\-=#+_&:/.%]+)`)
matchs := rep.FindAllStringSubmatch(md, -1)
for _, m := range matchs {
frag := m[1]
slug := frag[0:strings.LastIndex(frag, "-")]
newURL := "/blog/" + slug
v = rep.ReplaceAllString(v, newURL)
fmt.Printf(" convert <%s> to <%s>\n", m[0], newURL)
}
return v
}
func (c CMSItem) renderHTML() string {
md := goldmark.New(
goldmark.WithExtensions(extension.GFM),
// goldmark.WithParserOptions(
// parser.WithAutoHeadingID(),
// ),
goldmark.WithRendererOptions(
html.WithHardWraps(),
html.WithXHTML(),
html.WithUnsafe(),
),
)
var buf bytes.Buffer
if err := md.Convert([]byte(converter(c.MarkdownBody)), &buf); err != nil {
panic(err)
}
return string(buf.Bytes())
}
func writeCSV(cmsItems []CMSItem) {
f, err := os.Create("cmsitems.csv")
if err != nil {
panic(err)
}
defer f.Close()
w := csv.NewWriter(f)
w.UseCRLF = true
if err := w.Write([]string{"Name", "Title", "Date", "Slug", "Author", "Summary", "Body"}); err != nil {
panic(err)
}
for _, item := range cmsItems {
if err := w.Write([]string{
item.Name, item.Title, item.DateStr, item.Slug, item.Author, item.Summary, item.Body,
}); err != nil {
panic(err)
}
}
w.Flush()
if err := w.Error(); err != nil {
panic(err)
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("usage: go run md2csv.go targetDir")
os.Exit(0)
}
targetDir := os.Args[1]
files := dirwalk(targetDir, ".md")
cmsItems := []CMSItem{}
for _, f := range files {
fmt.Printf("\nstart parse : %s\n", f)
source := newSource(f)
// fmt.Printf("%s, %s, %s\n", source.Title, source.Slug, source.Content)
item := newCMSItem(*source)
// fmt.Printf("%s, %s, %s\n", item.Slug, item.Title, item.Body)
cmsItems = append(cmsItems, *item)
}
writeCSV(cmsItems)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment