diff --git a/main.go b/cmd/ssg/main.go similarity index 96% rename from main.go rename to cmd/ssg/main.go index 12a56d7..c2f0a8c 100644 --- a/main.go +++ b/cmd/ssg/main.go @@ -8,7 +8,7 @@ import ( "path/filepath" "strings" - "git.sr.ht/~ewintr/shitty-ssg/site" + "git.sr.ht/~ewintr/shitty-ssg/cmd/ssg/site" ) var ( diff --git a/cmd/ssg/site/html.go b/cmd/ssg/site/html.go new file mode 100644 index 0000000..2374c81 --- /dev/null +++ b/cmd/ssg/site/html.go @@ -0,0 +1,75 @@ +package site + +import ( + "fmt" + "html" + "strings" + + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" +) + +type HTMLPost struct { + Slug string + Title string + DateLong string + DateShort string + Content string +} + +type HTMLSummary struct { + Link string + Title string + Language Language + DateShort string + DateLong string + Summary string +} + +func FormatBlock(block adoc.BlockElement) string { + switch block.(type) { + case adoc.Paragraph: + text := "" + for _, inline := range block.(adoc.Paragraph) { + text += FormatInline(inline) + } + return fmt.Sprintf("
%s
", text) + case adoc.SubTitle: + return fmt.Sprintf("%s
", html.EscapeString(block.Text()))
+ case adoc.List:
+ var items []string
+ for _, item := range block.(adoc.List) {
+ itemText := ""
+ for _, inline := range item {
+ itemText += FormatInline(inline)
+ }
+ items = append(items, fmt.Sprintf("%s
", text)
+ default:
+ return ""
+ }
+}
diff --git a/cmd/ssg/site/html_test.go b/cmd/ssg/site/html_test.go
new file mode 100644
index 0000000..997e710
--- /dev/null
+++ b/cmd/ssg/site/html_test.go
@@ -0,0 +1,98 @@
+package site_test
+
+import (
+ "testing"
+
+ "git.sr.ht/~ewintr/go-kit/test"
+ "git.sr.ht/~ewintr/shitty-ssg/cmd/ssg/site"
+ "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc"
+)
+
+func TestFormatBlock(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ element adoc.BlockElement
+ exp string
+ }{
+ {
+ name: "paragraph",
+ element: adoc.Paragraph{
+ adoc.PlainText("one"),
+ adoc.PlainText("two"),
+ adoc.PlainText("three"),
+ },
+ exp: "onetwothree
", + }, + { + name: "subtitle", + element: adoc.SubTitle("text"), + exp: "text
",
+ },
+ {
+ name: "list",
+ element: adoc.List{
+ {adoc.PlainText("one")},
+ {adoc.PlainText("two")},
+ {adoc.PlainText("three")},
+ },
+ exp: "text
",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ test.Equals(t, tc.exp, site.FormatInline(tc.element))
+ })
+ }
+}
diff --git a/site/meta.go b/cmd/ssg/site/meta.go
similarity index 67%
rename from site/meta.go
rename to cmd/ssg/site/meta.go
index fc0d48b..5348daa 100644
--- a/site/meta.go
+++ b/cmd/ssg/site/meta.go
@@ -1,5 +1,9 @@
package site
+import (
+ "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc"
+)
+
const (
KIND_NOTE = Kind("note")
KIND_STORY = Kind("story")
@@ -9,19 +13,15 @@ const (
type Kind string
-var pluralKind = map[Kind]string{
- KIND_NOTE: "notes",
- KIND_STORY: "stories",
- KIND_ARTICLE: "articles",
-}
-
-func NewKind(kind string) Kind {
+func NewKind(kind adoc.Kind) Kind {
switch kind {
- case "note":
+ case adoc.KIND_NOTE:
return KIND_NOTE
- case "story":
+ case adoc.KIND_VKV:
return KIND_STORY
- case "article":
+ case adoc.KIND_ESSAY:
+ fallthrough
+ case adoc.KIND_TUTORIAL:
return KIND_ARTICLE
default:
return KIND_INVALID
@@ -37,11 +37,11 @@ const (
type Language string
-func NewLanguage(text string) Language {
- switch text {
- case "nl":
+func NewLanguage(ln adoc.Language) Language {
+ switch ln {
+ case adoc.LANGUAGE_NL:
return LANGUAGE_NL
- case "en":
+ case adoc.LANGUAGE_EN:
fallthrough
default:
return LANGUAGE_EN
diff --git a/cmd/ssg/site/meta_test.go b/cmd/ssg/site/meta_test.go
new file mode 100644
index 0000000..ea62552
--- /dev/null
+++ b/cmd/ssg/site/meta_test.go
@@ -0,0 +1,79 @@
+package site_test
+
+import (
+ "testing"
+
+ "git.sr.ht/~ewintr/go-kit/test"
+ "git.sr.ht/~ewintr/shitty-ssg/cmd/ssg/site"
+ "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc"
+)
+
+func TestNewKind(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ input adoc.Kind
+ exp site.Kind
+ }{
+ {
+ name: "empty",
+ exp: site.KIND_INVALID,
+ },
+ {
+ name: "note",
+ input: adoc.KIND_NOTE,
+ exp: site.KIND_NOTE,
+ },
+ {
+ name: "work",
+ input: adoc.KIND_WORK,
+ exp: site.KIND_INVALID,
+ },
+ {
+ name: "vkv",
+ input: adoc.KIND_VKV,
+ exp: site.KIND_STORY,
+ },
+ {
+ name: "essay",
+ input: adoc.KIND_ESSAY,
+ exp: site.KIND_ARTICLE,
+ },
+ {
+ name: "tutorial",
+ input: adoc.KIND_TUTORIAL,
+ exp: site.KIND_ARTICLE,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ test.Equals(t, tc.exp, site.NewKind(tc.input))
+ })
+ }
+}
+
+func TestNewLanguage(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ input adoc.Language
+ exp site.Language
+ }{
+ {
+ name: "nl",
+ input: adoc.LANGUAGE_NL,
+ exp: site.LANGUAGE_NL,
+ },
+ {
+ name: "en",
+ input: adoc.LANGUAGE_EN,
+ exp: site.LANGUAGE_EN,
+ },
+ {
+ name: "unknown",
+ input: adoc.LANGUAGE_UNKNOWN,
+ exp: site.LANGUAGE_EN,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ test.Equals(t, tc.exp, site.NewLanguage(tc.input))
+ })
+ }
+}
diff --git a/cmd/ssg/site/post.go b/cmd/ssg/site/post.go
new file mode 100644
index 0000000..4e9fac8
--- /dev/null
+++ b/cmd/ssg/site/post.go
@@ -0,0 +1,143 @@
+package site
+
+import (
+ "errors"
+ "fmt"
+ "html"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+
+ "git.sr.ht/~ewintr/go-kit/slugify"
+ "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc"
+)
+
+var (
+ ErrInvalidPost = errors.New("invalid post")
+)
+
+var pluralKind = map[Kind]string{
+ KIND_NOTE: "notes",
+ KIND_STORY: "stories",
+ KIND_ARTICLE: "articles",
+}
+
+type Post struct {
+ doc *adoc.ADoc
+ Date time.Time
+ Kind Kind
+ Language Language
+ Tags []Tag
+}
+
+func NewPost(doc *adoc.ADoc) *Post {
+ var tags []Tag
+ for _, t := range doc.Tags {
+ tags = append(tags, Tag(t))
+ }
+ return &Post{
+ doc: doc,
+ Date: doc.Date,
+ Kind: NewKind(doc.Kind),
+ Language: Language(doc.Language),
+ Tags: tags,
+ }
+}
+
+func (p Post) Slug() string {
+ return slugify.Slugify(p.doc.Title)
+}
+
+func (p *Post) Year() string {
+ return strconv.Itoa(p.Date.Year())
+}
+
+func (p *Post) Link() string {
+ return fmt.Sprintf("%s/", path.Join("/", pluralKind[p.Kind], p.Year(), p.Slug()))
+}
+
+func (p *Post) FullLink() string {
+ return fmt.Sprintf("https://erikwinter.nl%s", p.Link())
+}
+
+func (p *Post) HTMLSummary() *HTMLSummary {
+ summary := ""
+ if len(p.doc.Content) > 0 {
+ summary = fmt.Sprintf("%s...
", TruncateOnSpace(p.doc.Content[0].Text(), 150)) + } + + return &HTMLSummary{ + Link: p.Link(), + Title: p.doc.Title, + Language: p.Language, + DateLong: FormatDate(p.Date, p.Language, DATE_LONG), + DateShort: FormatDate(p.Date, p.Language, DATE_SHORT), + Summary: summary, + } +} + +func (p *Post) HTMLPost() *HTMLPost { + var content string + for _, be := range p.doc.Content { + content += fmt.Sprintf("%s\n", FormatBlock(be)) + } + + return &HTMLPost{ + Slug: p.Slug(), + Title: html.EscapeString(p.doc.Title), + DateLong: FormatDate(p.Date, p.Language, DATE_LONG), + DateShort: FormatDate(p.Date, p.Language, DATE_SHORT), + Content: content, + } +} + +func (p *Post) XMLPost() *XMLPost { + var content string + for _, be := range p.doc.Content { + content += fmt.Sprintf("%s\n", FormatBlock(be)) + } + + return &XMLPost{ + Link: p.FullLink(), + Title: html.EscapeString(p.doc.Title), + DateFormal: FormatDate(p.Date, p.Language, DATE_FORMAL), + Content: content, + } +} + +func FormatDate(date time.Time, language Language, format DateFormat) string { + switch { + case format == DATE_LONG && language == LANGUAGE_NL: + nlMonth := [...]string{"januari", "februari", "maart", + "april", "mei", "juni", "juli", "augustus", "september", + "oktober", "november", "december", + } + return fmt.Sprintf("%d %s %d", date.Day(), nlMonth[date.Month()-1], date.Year()) + case format == DATE_LONG && language == LANGUAGE_EN: + return date.Format("January 2, 2006") + case format == DATE_FORMAL: + return date.Format(time.RFC1123Z) + case format == DATE_SHORT: + fallthrough + default: + return date.Format("2006-01-02 00:00:00") + } +} + +func TruncateOnSpace(text string, maxChars int) string { + if len(text) <= maxChars { + return text + } + + var keep []string + ss := strings.Split(text, " ") + for _, s := range ss { + if len(strings.Join(keep, " ")+s) > maxChars { + break + } + keep = append(keep, s) + } + + return strings.Join(keep, " ") +} diff --git a/cmd/ssg/site/post_test.go b/cmd/ssg/site/post_test.go new file mode 100644 index 0000000..50c6530 --- /dev/null +++ b/cmd/ssg/site/post_test.go @@ -0,0 +1,182 @@ +package site_test + +import ( + "testing" + "time" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/shitty-ssg/cmd/ssg/site" + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" +) + +func TestPost(t *testing.T) { + title := "title thing" + author := "author" + kind := adoc.KIND_NOTE + language := adoc.LANGUAGE_EN + path := "/path" + date := time.Date(2020, 12, 28, 7, 23, 45, 0, time.UTC) + tag1, tag2 := adoc.Tag("tag1"), adoc.Tag("tag2") + par1 := adoc.Paragraph{adoc.PlainText("one")} + par2 := adoc.Paragraph{adoc.PlainText("two")} + post := site.NewPost(&adoc.ADoc{ + Title: title, + Author: author, + Kind: kind, + Language: language, + Path: path, + Date: date, + Tags: []adoc.Tag{tag1, tag2}, + Content: []adoc.BlockElement{par1, par2}, + }) + + t.Run("new", func(t *testing.T) { + test.Equals(t, date, post.Date) + test.Equals(t, site.Kind(kind), post.Kind) + test.Equals(t, site.Language(language), post.Language) + test.Equals(t, []site.Tag{site.Tag(tag1), site.Tag(tag2)}, post.Tags) + }) + + t.Run("tags", func(t *testing.T) { + test.Equals(t, []site.Tag{site.Tag("tag1"), site.Tag("tag2")}, post.Tags) + }) + + t.Run("slug", func(t *testing.T) { + test.Equals(t, "title-thing", post.Slug()) + }) + + t.Run("year", func(t *testing.T) { + test.Equals(t, "2020", post.Year()) + }) + + t.Run("link", func(t *testing.T) { + test.Equals(t, "/notes/2020/title-thing/", post.Link()) + }) + + t.Run("full link", func(t *testing.T) { + test.Equals(t, "https://erikwinter.nl/notes/2020/title-thing/", post.FullLink()) + }) + + t.Run("html summary", func(t *testing.T) { + exp := &site.HTMLSummary{ + Link: "/notes/2020/title-thing/", + Title: "title thing", + Language: site.LANGUAGE_EN, + DateShort: "2020-12-28 00:00:00", + DateLong: "December 28, 2020", + Summary: "one...
", + } + + test.Equals(t, exp, post.HTMLSummary()) + }) + + t.Run("html post", func(t *testing.T) { + exp := &site.HTMLPost{ + Slug: "title-thing", + Title: "title thing", + DateLong: "December 28, 2020", + DateShort: "2020-12-28 00:00:00", + Content: "one
\ntwo
\n", + } + + test.Equals(t, exp, post.HTMLPost()) + }) + + t.Run("xml post", func(t *testing.T) { + exp := &site.XMLPost{ + Link: "https://erikwinter.nl/notes/2020/title-thing/", + Title: "title thing", + DateFormal: "Mon, 28 Dec 2020 07:23:45 +0000", + Content: "one
\ntwo
\n", + } + + test.Equals(t, exp, post.XMLPost()) + }) +} + +func TestFormatDate(t *testing.T) { + date := time.Date(2020, 12, 28, 7, 23, 45, 0, time.UTC) + for _, tc := range []struct { + name string + language site.Language + format site.DateFormat + exp string + }{ + { + name: "long nl", + language: site.LANGUAGE_NL, + format: site.DATE_LONG, + exp: "28 december 2020", + }, + { + name: "long en", + language: site.LANGUAGE_EN, + format: site.DATE_LONG, + exp: "December 28, 2020", + }, + { + name: "formal nl", + language: site.LANGUAGE_NL, + format: site.DATE_FORMAL, + exp: "Mon, 28 Dec 2020 07:23:45 +0000", + }, + { + name: "formal en", + language: site.LANGUAGE_EN, + format: site.DATE_FORMAL, + exp: "Mon, 28 Dec 2020 07:23:45 +0000", + }, + { + name: "short nl", + language: site.LANGUAGE_NL, + format: site.DATE_SHORT, + exp: "2020-12-28 00:00:00", + }, + { + name: "short en", + language: site.LANGUAGE_EN, + format: site.DATE_SHORT, + exp: "2020-12-28 00:00:00", + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, tc.exp, site.FormatDate(date, tc.language, tc.format)) + }) + } +} + +func TestTruncateOnSpace(t *testing.T) { + text := "this is a short text" + for _, tc := range []struct { + name string + text string + max int + exp string + }{ + { + name: "empty", + }, + { + name: "short text", + text: text, + max: 150, + exp: text, + }, + { + name: "truncate on space", + text: text, + max: 10, + exp: "this is a", + }, + { + name: "truncate in word", + text: text, + max: 12, + exp: "this is a", + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, tc.exp, site.TruncateOnSpace(tc.text, tc.max)) + }) + } +} diff --git a/site/posts.go b/cmd/ssg/site/posts.go similarity index 89% rename from site/posts.go rename to cmd/ssg/site/posts.go index 44d78d4..d8060a4 100644 --- a/site/posts.go +++ b/cmd/ssg/site/posts.go @@ -2,12 +2,13 @@ package site import "sort" -type Posts []Post +type Posts []*Post func (p Posts) Len() int { return len(p) } func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p Posts) Less(i, j int) bool { return p[i].Date.After(p[j].Date) } +// Sort sorts on reverse chronological order func (p Posts) Sort() Posts { sort.Sort(p) @@ -86,15 +87,6 @@ func (p Posts) TagList() []string { return list } -func (p Posts) HTMLSummaries() []*HTMLSummary { - summaries := []*HTMLSummary{} - for _, post := range p { - summaries = append(summaries, post.HTMLSummary()) - } - - return summaries -} - func removeDuplicates(fullList []string) []string { list := []string{} for _, item := range fullList { diff --git a/cmd/ssg/site/posts_test.go b/cmd/ssg/site/posts_test.go new file mode 100644 index 0000000..40d9270 --- /dev/null +++ b/cmd/ssg/site/posts_test.go @@ -0,0 +1,77 @@ +package site_test + +import ( + "testing" + "time" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/shitty-ssg/cmd/ssg/site" +) + +func TestPosts(t *testing.T) { + kind1, kind2 := site.Kind("kind1"), site.Kind("kind2") + tag1, tag2 := site.Tag("tag1"), site.Tag("tag2") + post1 := &site.Post{ + Date: time.Date(2020, 12, 1, 0, 0, 0, 0, time.UTC), + Kind: kind1, + Tags: []site.Tag{tag1}, + } + post2 := &site.Post{ + Date: time.Date(2019, 12, 1, 0, 0, 0, 0, time.UTC), + Kind: kind2, + Tags: []site.Tag{tag1, tag2}, + } + post3 := &site.Post{ + Date: time.Date(2018, 12, 1, 0, 0, 0, 0, time.UTC), + Kind: kind1, + Tags: []site.Tag{tag2}, + } + + t.Run("sort", func(t *testing.T) { + for _, tc := range []struct { + name string + posts site.Posts + exp site.Posts + }{ + { + name: "ordered", + posts: site.Posts{post1, post2, post3}, + }, + { + name: "unordered", + posts: site.Posts{post2, post3, post1}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + exp := site.Posts{post1, post2, post3} + test.Equals(t, exp, tc.posts.Sort()) + }) + } + }) + + posts := site.Posts{post1, post2, post3} + + t.Run("filter kind", func(t *testing.T) { + test.Equals(t, site.Posts{post1, post3}, posts.FilterByKind(kind1)) + }) + + t.Run("filter year", func(t *testing.T) { + test.Equals(t, site.Posts{post2}, posts.FilterByYear("2019")) + }) + + t.Run("tilter tag", func(t *testing.T) { + test.Equals(t, site.Posts{post2, post3}, posts.FilterByTag(tag2)) + }) + + t.Run("limit", func(t *testing.T) { + test.Equals(t, site.Posts{post1, post2}, posts.Limit(2)) + }) + + t.Run("year list", func(t *testing.T) { + test.Equals(t, []string{"2018", "2019", "2020"}, posts.YearList()) + }) + + t.Run("tag list", func(t *testing.T) { + test.Equals(t, []string{"tag1", "tag2"}, posts.TagList()) + }) +} diff --git a/site/render.go b/cmd/ssg/site/render.go similarity index 92% rename from site/render.go rename to cmd/ssg/site/render.go index f1e5fff..52265eb 100644 --- a/site/render.go +++ b/cmd/ssg/site/render.go @@ -69,12 +69,16 @@ func renderStaticPages(targetPath string, tpl *template.Template, statics []*Sta } func renderHome(targetPath string, tpl *template.Template, posts Posts) error { + var summaries []*HTMLSummary + for _, p := range posts { + summaries = append(summaries, p.HTMLSummary()) + } data := struct { Title string Summaries []*HTMLSummary }{ Title: "Recent", - Summaries: posts.HTMLSummaries(), + Summaries: summaries, } hPath := filepath.Join(targetPath, "index.html") @@ -145,7 +149,11 @@ func renderListings(targetPath string, tpl *template.Template, posts Posts) erro for _, kind := range []Kind{KIND_NOTE, KIND_STORY, KIND_ARTICLE} { for _, year := range posts.FilterByKind(kind).YearList() { title := fmt.Sprintf("%s in %s", strings.Title(pluralKind[kind]), year) - summaries := posts.FilterByKind(kind).FilterByYear(year).HTMLSummaries() + kyposts := posts.FilterByKind(kind).FilterByYear(year) + var summaries []*HTMLSummary + for _, p := range kyposts { + summaries = append(summaries, p.HTMLSummary()) + } path := filepath.Join(targetPath, pluralKind[kind], year) if err := renderListing(path, tpl, title, summaries); err != nil { return err @@ -155,7 +163,11 @@ func renderListings(targetPath string, tpl *template.Template, posts Posts) erro for _, tag := range posts.TagList() { title := fmt.Sprintf("Posts Tagged with \"%s\"", tag) - summaries := posts.FilterByTag(Tag(tag)).HTMLSummaries() + tposts := posts.FilterByTag(Tag(tag)) + var summaries []*HTMLSummary + for _, p := range tposts { + summaries = append(summaries, p.HTMLSummary()) + } path := filepath.Join(targetPath, "tags", tag) if err := renderListing(path, tpl, title, summaries); err != nil { return err diff --git a/site/site.go b/cmd/ssg/site/site.go similarity index 93% rename from site/site.go rename to cmd/ssg/site/site.go index 760a66d..cd81bf4 100644 --- a/site/site.go +++ b/cmd/ssg/site/site.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "path/filepath" "text/template" + + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" ) type StaticPage struct { @@ -28,7 +30,7 @@ func New(resourcesPath string) (*Site, error) { return &Site{ resourcesPath: resourcesPath, templates: templates, - posts: []Post{}, + posts: Posts{}, staticPages: []*StaticPage{}, }, nil } @@ -45,7 +47,10 @@ func (s *Site) AddFilePost(fPath string) error { if err != nil { return err } - s.posts = append(s.posts, NewPost(string(content))) + post := NewPost(adoc.New(string(content))) + if post.Kind != KIND_INVALID { + s.posts = append(s.posts, post) + } return nil } diff --git a/site/xml.go b/cmd/ssg/site/xml.go similarity index 100% rename from site/xml.go rename to cmd/ssg/site/xml.go diff --git a/go.mod b/go.mod index e63157f..8283089 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module git.sr.ht/~ewintr/shitty-ssg go 1.14 -require git.sr.ht/~ewintr/go-kit v0.0.0-20200704112030-36275689b0ea +require ( + git.sr.ht/~ewintr/erikwinternl/shitty-ssg v0.0.0-20201204092507-5528bf33815d // indirect + git.sr.ht/~ewintr/go-kit v0.0.0-20200704112030-36275689b0ea +) diff --git a/go.sum b/go.sum index cb99d1c..048b1a9 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +git.sr.ht/~ewintr/erikwinternl v0.0.0-20201204092507-5528bf33815d h1:teNzxpdrJw5sUpB/84ojdjVV2q2+3IVwYjztCze9YmU= +git.sr.ht/~ewintr/erikwinternl/shitty-ssg v0.0.0-20201204092507-5528bf33815d h1:vyJ0Pgdrvqh28jxBCLGCGt/80p86iSQtKJW7rAbYRk8= +git.sr.ht/~ewintr/erikwinternl/shitty-ssg v0.0.0-20201204092507-5528bf33815d/go.mod h1:ka/PzT9A0BNvjcUQpIM0YE1BnOR1/qr6x7XeCUyYDlw= git.sr.ht/~ewintr/go-kit v0.0.0-20200704112030-36275689b0ea h1:YdHan+/QwjzF05Dlp40BHw5N47nWy2QZCY7aseXf2n0= git.sr.ht/~ewintr/go-kit v0.0.0-20200704112030-36275689b0ea/go.mod h1:zrpigRr1EHuVGw7GWwvYWwfLgdAwJ110zIRAGtaicvI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= diff --git a/pkg/adoc/adoc.go b/pkg/adoc/adoc.go new file mode 100644 index 0000000..dca87b1 --- /dev/null +++ b/pkg/adoc/adoc.go @@ -0,0 +1,71 @@ +package adoc + +import ( + "strings" + "time" +) + +const ( + KIND_NOTE = Kind("note") + KIND_VKV = Kind("vkv") + KIND_STORY = Kind("story") + KIND_SNIPPET = Kind("snippet") + KIND_ESSAY = Kind("essay") + KIND_WORK = Kind("work") + KIND_TUTORIAL = Kind("tutorial") + KIND_UNKNOWN = Kind("unknown") +) + +type Kind string + +func NewKind(text string) Kind { + switch text { + case "verhaal": + text = "story" + case "los": + text = "snippet" + } + + for _, k := range []string{ + "note", "vkv", "story", "snippet", + "essay", "tutorial", "work", + } { + if k == text { + return Kind(k) + } + } + + return KIND_UNKNOWN +} + +const ( + LANGUAGE_EN = Language("en") + LANGUAGE_NL = Language("nl") + LANGUAGE_UNKNOWN = Language("unknown") +) + +type Language string + +func NewLanguage(ln string) Language { + switch strings.ToLower(ln) { + case "nl": + return LANGUAGE_NL + case "en": + return LANGUAGE_EN + default: + return LANGUAGE_UNKNOWN + } +} + +type Tag string + +type ADoc struct { + Title string + Author string + Kind Kind + Language Language + Path string + Date time.Time + Tags []Tag + Content []BlockElement +} diff --git a/pkg/adoc/adoc_test.go b/pkg/adoc/adoc_test.go new file mode 100644 index 0000000..5eece4f --- /dev/null +++ b/pkg/adoc/adoc_test.go @@ -0,0 +1,110 @@ +package adoc_test + +import ( + "testing" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" +) + +func TestNewKind(t *testing.T) { + for _, tc := range []struct { + name string + input string + exp adoc.Kind + }{ + { + name: "empty", + exp: adoc.KIND_UNKNOWN, + }, + { + name: "unknown", + input: "something", + exp: adoc.KIND_UNKNOWN, + }, + { + name: "note", + input: "note", + exp: adoc.KIND_NOTE, + }, + { + name: "vkv", + input: "vkv", + exp: adoc.KIND_VKV, + }, + { + name: "story", + input: "verhaal", + exp: adoc.KIND_STORY, + }, + { + name: "snippet", + input: "los", + exp: adoc.KIND_SNIPPET, + }, + { + name: "essay", + input: "essay", + exp: adoc.KIND_ESSAY, + }, + { + name: "tutorial", + input: "tutorial", + exp: adoc.KIND_TUTORIAL, + }, + { + name: "work note", + input: "work", + exp: adoc.KIND_WORK, + }, + } { + t.Run(tc.name, func(t *testing.T) { + act := adoc.NewKind(tc.input) + test.Equals(t, tc.exp, act) + }) + } +} + +func TestNewLanguage(t *testing.T) { + for _, tc := range []struct { + name string + input string + exp adoc.Language + }{ + { + name: "empty", + exp: adoc.LANGUAGE_UNKNOWN, + }, + { + name: "dutch lower", + input: "nl", + exp: adoc.LANGUAGE_NL, + }, + { + name: "dutch upper", + input: "NL", + exp: adoc.LANGUAGE_NL, + }, + { + name: "english lower", + input: "en", + exp: adoc.LANGUAGE_EN, + }, + { + name: "english upper", + input: "EN", + exp: adoc.LANGUAGE_EN, + }, + { + name: "unknown", + input: "something", + exp: adoc.LANGUAGE_UNKNOWN, + }, + } { + t.Run(tc.name, func(t *testing.T) { + act := adoc.NewLanguage(tc.input) + test.Equals(t, tc.exp, act) + }) + + } +} diff --git a/site/block.go b/pkg/adoc/block.go similarity index 51% rename from site/block.go rename to pkg/adoc/block.go index dd04b1e..5e64d63 100644 --- a/site/block.go +++ b/pkg/adoc/block.go @@ -1,14 +1,12 @@ -package site +package adoc import ( "fmt" - "html" "strings" ) type BlockElement interface { Text() string - BlockHTML() string } type Paragraph []InlineElement @@ -22,35 +20,17 @@ func (p Paragraph) Text() string { return strings.Join(text, " ") } -func (p Paragraph) BlockHTML() string { - var body string - for _, ie := range p { - body += ie.InlineHTML() - } - - return fmt.Sprintf("%s
", body) -} - type SubTitle string func (st SubTitle) Text() string { return string(st) } -func (st SubTitle) BlockHTML() string { - return fmt.Sprintf("%s
", html.EscapeString(string(cb)))
-}
type ListItem []InlineElement
@@ -60,16 +40,7 @@ func (li ListItem) Text() string {
text = append(text, ie.Text())
}
- return fmt.Sprintf("%s%s", LISTITEM_PREFIX, strings.Join(text, " "))
-}
-
-func (li ListItem) HTML() string {
- var body string
- for _, ie := range li {
- body += ie.InlineHTML()
- }
-
- return fmt.Sprintf("one two three
" - test.Equals(t, exp, p.BlockHTML()) -} - -func TestSubTitle(t *testing.T) { - text := "text" - st := SubTitle(text) - - exp := fmt.Sprintf("%s
", html.EscapeString(string(ct)))
-}
diff --git a/site/inline_test.go b/site/inline_test.go
deleted file mode 100644
index 1d6b224..0000000
--- a/site/inline_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package site_test
-
-import (
- "fmt"
- "testing"
-
- "git.sr.ht/~ewintr/go-kit/test"
- "git.sr.ht/~ewintr/shitty-ssg/site"
-)
-
-func TestPlainText(t *testing.T) {
- text := "text"
- pt := site.PlainText(text)
-
- test.Equals(t, text, pt.InlineHTML())
-}
-
-func TestStrongText(t *testing.T) {
- text := "text"
- st := site.StrongText(text)
- exp := fmt.Sprintf("%s", text)
-
- test.Equals(t, exp, st.InlineHTML())
-}
-
-func TestEmpText(t *testing.T) {
- text := "text"
- et := site.EmpText(text)
- exp := fmt.Sprintf("%s", text)
-
- test.Equals(t, exp, et.InlineHTML())
-}
-
-func TestLink(t *testing.T) {
- url := "http://example.com"
- title := "link title"
- link := site.NewLink(url, title)
- exp := fmt.Sprintf("%s", url, title)
-
- test.Equals(t, exp, link.InlineHTML())
-}
diff --git a/site/parser_test.go b/site/parser_test.go
deleted file mode 100644
index 224727d..0000000
--- a/site/parser_test.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package site_test
-
-import (
- "fmt"
- "testing"
- "time"
-
- "git.sr.ht/~ewintr/go-kit/test"
- "git.sr.ht/~ewintr/shitty-ssg/site"
-)
-
-func TestNewPost(t *testing.T) {
- one := "one"
- two := "two"
- three := "three"
- ptOne := site.PlainText(one)
- ptTwo := site.PlainText(two)
- ptThree := site.PlainText(three)
- for _, tc := range []struct {
- name string
- input string
- exp site.Post
- }{
- {
- name: "empty",
- exp: site.Post{
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- },
- },
- {
- name: "title",
- input: "= Title",
- exp: site.Post{
- Title: "Title",
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- },
- },
- {
- name: "header",
- input: "= Title\nT. Test\n2020-10-27\n:tags:\ttag1, tag2\n:kind:\tnote\n:language:\tnl",
- exp: site.Post{
- Title: "Title",
- Author: "T. Test",
- Kind: site.KIND_NOTE,
- Language: site.LANGUAGE_NL,
- Tags: []site.Tag{
- site.Tag("tag1"),
- site.Tag("tag2"),
- },
- Date: time.Date(2020, time.October, 27, 0, 0, 0, 0, time.UTC),
- },
- },
- {
- name: "paragraphs",
- input: fmt.Sprintf("%s\n\n%s\n\n%s", one, two, three),
- exp: site.Post{
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- Content: []site.BlockElement{
- site.Paragraph([]site.InlineElement{ptOne}),
- site.Paragraph([]site.InlineElement{ptTwo}),
- site.Paragraph([]site.InlineElement{ptThree}),
- },
- },
- },
- {
- name: "subtitle",
- input: "== Subtitle",
- exp: site.Post{
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- Content: []site.BlockElement{
- site.SubTitle("Subtitle"),
- },
- },
- },
- {
- name: "code block",
- input: "----\nsome code\nmore code\n----",
- exp: site.Post{
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- Content: []site.BlockElement{
- site.CodeBlock("some code\nmore code"),
- },
- },
- },
- {
- name: "code block with empty lines",
- input: "----\nsome code\n\nmore code\n----",
- exp: site.Post{
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- Content: []site.BlockElement{
- site.CodeBlock("some code\n\nmore code"),
- },
- },
- },
- {
- name: "list",
- input: "* item 1\n* item 2\n* *item 3*\n",
- exp: site.Post{
- Tags: []site.Tag{},
- Language: site.LANGUAGE_EN,
- Content: []site.BlockElement{
- site.List{
- site.ListItem([]site.InlineElement{site.PlainText("item 1")}),
- site.ListItem([]site.InlineElement{site.PlainText("item 2")}),
- site.ListItem([]site.InlineElement{site.StrongText("item 3")}),
- },
- },
- },
- },
- } {
- t.Run(tc.name, func(t *testing.T) {
- act := site.NewPost(tc.input)
-
- test.Equals(t, tc.exp, act)
- })
- }
-}
-
-func TestPostBodyHTML(t *testing.T) {
- text := `= Title
-
-Some text. With some *strong*. And a http://example.com[link].
-
-== A Sub Title
-
-And some more text.
- `
- post := site.NewPost(text)
- act := post.HTMLPost()
-
- exp := &site.HTMLPost{
- Slug: "title",
- Title: "Title",
- DateLong: "January 1, 0001",
- DateShort: "0001-01-01 00:00:00",
- Content: `Some text. With some strong. And a link.
-And some more text.
-`, - } - - test.Equals(t, exp, act) - -} - -func TestParseInline(t *testing.T) { - for _, tc := range []struct { - name string - input string - exp []site.InlineElement - }{{ - name: "empty", - }, - { - name: "plain", - input: "some test text", - exp: []site.InlineElement{ - site.PlainText("some test text")}, - }, - { - name: "strong", - input: "*some strong text*", - exp: []site.InlineElement{ - site.StrongText("some strong text"), - }, - }, - { - name: "strong in plain", - input: "some *strong* text", - exp: []site.InlineElement{ - site.PlainText("some "), - site.StrongText("strong"), - site.PlainText(" text"), - }, - }, - { - name: "emphasis", - input: "_some emphasized text_", - exp: []site.InlineElement{ - site.EmpText("some emphasized text"), - }, - }, - { - name: "emphasis in plain", - input: "some _emphasized_ text", - exp: []site.InlineElement{ - site.PlainText("some "), - site.EmpText("emphasized"), - site.PlainText(" text"), - }, - }, - { - name: "emp and strong in plain", - input: "some _*special*_ text", - exp: []site.InlineElement{ - site.PlainText("some "), - site.StrongEmpText("special"), - site.PlainText(" text"), - }, - }, - { - name: "link", - input: "a link[title] somewhere", - exp: []site.InlineElement{ - site.PlainText("a "), - site.NewLink("link", "title"), - site.PlainText(" somewhere"), - }, - }, - { - name: "code", - input: "`command`", - exp: []site.InlineElement{ - site.CodeText("command"), - }, - }, - { - name: "code in plain", - input: "some `code` in text", - exp: []site.InlineElement{ - site.PlainText("some "), - site.CodeText("code"), - site.PlainText(" in text"), - }, - }, - { - name: "link with underscore", - input: "https://example.com/some_url[some url]", - exp: []site.InlineElement{ - site.NewLink("https://example.com/some_url", "some url"), - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - act := site.ParseInline(tc.input) - - test.Equals(t, tc.exp, act) - - }) - } -} diff --git a/site/post.go b/site/post.go deleted file mode 100644 index 36c7f96..0000000 --- a/site/post.go +++ /dev/null @@ -1,125 +0,0 @@ -package site - -import ( - "errors" - "fmt" - "html" - "path" - "strconv" - "strings" - "time" - - "git.sr.ht/~ewintr/go-kit/slugify" -) - -var ( - ErrInvalidPost = errors.New("invalid post") -) - -type Post struct { - Title string - Author string - Kind Kind - Language Language - Path string - Date time.Time - Tags []Tag - Content []BlockElement -} - -func (p Post) Slug() string { - return slugify.Slugify(p.Title) -} - -func (p *Post) Year() string { - return strconv.Itoa(p.Date.Year()) -} - -func (p *Post) Link() string { - return fmt.Sprintf("%s/", path.Join("/", pluralKind[p.Kind], p.Year(), p.Slug())) -} - -func (p *Post) FullLink() string { - return fmt.Sprintf("https://erikwinter.nl%s", p.Link()) -} - -func (p *Post) HTMLSummary() *HTMLSummary { - summary := "" - if len(p.Content) > 0 { - summary = fmt.Sprintf("%s...
", truncateOnSpace(p.Content[0].Text(), 150)) - } - - return &HTMLSummary{ - Link: p.Link(), - Title: p.Title, - Language: p.Language, - DateLong: p.FormattedDate(DATE_LONG), - DateShort: p.FormattedDate(DATE_SHORT), - Summary: summary, - } -} - -func (p *Post) HTMLPost() *HTMLPost { - var content string - for _, be := range p.Content { - content += fmt.Sprintf("%s\n", be.BlockHTML()) - } - - return &HTMLPost{ - Slug: p.Slug(), - Title: html.EscapeString(p.Title), - DateLong: p.FormattedDate(DATE_LONG), - DateShort: p.FormattedDate(DATE_SHORT), - Content: content, - } -} - -func (p *Post) XMLPost() *XMLPost { - var content string - for _, be := range p.Content { - content += fmt.Sprintf("%s\n", html.EscapeString(be.BlockHTML())) - } - - return &XMLPost{ - Link: p.FullLink(), - Title: html.EscapeString(p.Title), - DateFormal: p.FormattedDate(DATE_FORMAL), - Content: content, - } -} - -func (p Post) FormattedDate(format DateFormat) string { - switch { - case format == DATE_LONG && p.Language == LANGUAGE_NL: - nlMonth := [...]string{"januari", "februari", "maart", - "april", "mei", "juni", "juli", "augustus", "september", - "oktober", "november", "december", - } - return fmt.Sprintf("%d %s %d", p.Date.Day(), nlMonth[p.Date.Month()-1], p.Date.Year()) - case format == DATE_LONG && p.Language == LANGUAGE_EN: - return p.Date.Format("January 2, 2006") - case format == DATE_FORMAL: - return p.Date.Format(time.RFC1123Z) - case format == DATE_SHORT: - fallthrough - default: - return p.Date.Format("2006-01-02 00:00:00") - } -} - -func truncateOnSpace(text string, maxChars int) string { - if len(text) <= maxChars { - return text - } - - shortText := "" - ss := strings.Split(text, " ") - for _, s := range ss { - if len(shortText+" "+s) > maxChars { - break - } - shortText += " " + s - } - - return shortText -}