Last active
June 5, 2020 06:05
-
-
Save k-nishijima/28892058ba97eec540adb4551a737a57 to your computer and use it in GitHub Desktop.
markdownファイルからWebflow向けCSVファイルを生成
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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