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.SubSubTitle: + return fmt.Sprintf("

%s

", html.EscapeString(block.Text())) + case adoc.CodeBlock: + 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
  • ", itemText)) + } + return fmt.Sprintf("", strings.Join(items, "\n")) + default: + return "" + } +} + +func FormatInline(src adoc.InlineElement) string { + text := html.EscapeString(src.Text()) + switch src.(type) { + case adoc.PlainText: + return text + case adoc.StrongText: + return fmt.Sprintf("%s", text) + case adoc.EmpText: + return fmt.Sprintf("%s", text) + case adoc.StrongEmpText: + return fmt.Sprintf("%s", text) + case adoc.Link: + return fmt.Sprintf("%s", src.(adoc.Link).URL(), text) + case adoc.CodeText: + return 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: "subsubtitle", + element: adoc.SubSubTitle("text"), + exp: "

    text

    ", + }, + { + name: "code", + element: adoc.CodeBlock("text"), + exp: "
    text
    ", + }, + { + name: "list", + element: adoc.List{ + {adoc.PlainText("one")}, + {adoc.PlainText("two")}, + {adoc.PlainText("three")}, + }, + exp: "", + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, tc.exp, site.FormatBlock(tc.element)) + }) + } +} + +func TestFormatInline(t *testing.T) { + for _, tc := range []struct { + name string + element adoc.InlineElement + exp string + }{ + { + name: "plain text", + element: adoc.PlainText("text"), + exp: "text", + }, + { + name: "strong", + element: adoc.StrongText("text"), + exp: "text", + }, + { + name: "emphasis", + element: adoc.EmpText("text"), + exp: "text", + }, + { + name: "strong emphasis", + element: adoc.StrongEmpText("text"), + exp: "text", + }, + { + name: "link", + element: adoc.NewLink("url", "title"), + exp: `title`, + }, + { + name: "code", + element: adoc.CodeText("text"), + 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

    \n

    two

    \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

    \n

    two

    \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

    ", st) -} type SubSubTitle string func (st SubSubTitle) Text() string { return string(st) } -func (st SubSubTitle) BlockHTML() string { - return fmt.Sprintf("

    %s

    ", st) -} type CodeBlock string func (cb CodeBlock) Text() string { return string(cb) } -func (cb CodeBlock) 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("
  • %s
  • ", body) + return fmt.Sprintf("%s%s", LISTITEM_PREFIX, strings.Join(text, "")) } type List []ListItem @@ -82,12 +53,3 @@ func (l List) Text() string { return strings.Join(items, "\n") } - -func (l List) BlockHTML() string { - var items []string - for _, item := range l { - items = append(items, item.HTML()) - } - - return fmt.Sprintf("", strings.Join(items, "\n")) -} diff --git a/pkg/adoc/block_test.go b/pkg/adoc/block_test.go new file mode 100644 index 0000000..e30169b --- /dev/null +++ b/pkg/adoc/block_test.go @@ -0,0 +1,134 @@ +package adoc_test + +import ( + "testing" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" +) + +func TestParagraph(t *testing.T) { + for _, tc := range []struct { + name string + elements []adoc.InlineElement + exp string + }{ + { + name: "empty", + elements: []adoc.InlineElement{}, + }, + { + name: "one", + elements: []adoc.InlineElement{ + adoc.PlainText("one"), + }, + exp: "one", + }, + { + name: "many", + elements: []adoc.InlineElement{ + adoc.PlainText("one"), + adoc.PlainText("two"), + adoc.PlainText("three"), + }, + exp: "one two three", + }, + } { + t.Run(tc.name, func(t *testing.T) { + p := adoc.Paragraph(tc.elements) + test.Equals(t, tc.exp, p.Text()) + }) + } +} + +func TestBlockSimple(t *testing.T) { + text := "text" + for _, tc := range []struct { + name string + element adoc.BlockElement + }{ + { + name: "subtitle", + element: adoc.SubTitle(text), + }, + { + name: "subsubtitle", + element: adoc.SubSubTitle(text), + }, + { + name: "code block", + element: adoc.CodeBlock(text), + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, text, tc.element.Text()) + }) + } +} + +func TestListItem(t *testing.T) { + for _, tc := range []struct { + name string + elements []adoc.InlineElement + exp string + }{ + { + name: "empty", + exp: "* ", + }, + { + name: "one", + elements: []adoc.InlineElement{ + adoc.PlainText("one"), + }, + exp: "* one", + }, + { + name: "many", + elements: []adoc.InlineElement{ + adoc.PlainText("one"), + adoc.PlainText("two"), + adoc.PlainText("three"), + }, + exp: "* onetwothree", + }, + } { + t.Run(tc.name, func(t *testing.T) { + li := adoc.ListItem(tc.elements) + test.Equals(t, tc.exp, li.Text()) + }) + } +} + +func TestList(t *testing.T) { + for _, tc := range []struct { + name string + elements []adoc.ListItem + exp string + }{ + { + name: "empty", + }, + { + name: "one", + elements: []adoc.ListItem{ + {adoc.PlainText("one")}, + }, + exp: "* one", + }, + { + name: "many", + elements: []adoc.ListItem{ + {adoc.PlainText("one")}, + {adoc.PlainText("two")}, + {adoc.PlainText("three")}, + }, + exp: "* one\n* two\n* three", + }, + } { + t.Run(tc.name, func(t *testing.T) { + l := adoc.List(tc.elements) + test.Equals(t, tc.exp, l.Text()) + }) + } +} diff --git a/pkg/adoc/inline.go b/pkg/adoc/inline.go new file mode 100644 index 0000000..c6b6b9d --- /dev/null +++ b/pkg/adoc/inline.go @@ -0,0 +1,40 @@ +package adoc + +type InlineElement interface { + Text() string +} + +type PlainText string + +func (pt PlainText) Text() string { return string(pt) } + +type StrongText string + +func (st StrongText) Text() string { return string(st) } + +type EmpText string + +func (et EmpText) Text() string { return string(et) } + +type StrongEmpText string + +func (set StrongEmpText) Text() string { return string(set) } + +type Link struct { + url string + title string +} + +func NewLink(url, title string) Link { + return Link{ + url: url, + title: title, + } +} + +func (l Link) URL() string { return l.url } +func (l Link) Text() string { return l.title } + +type CodeText string + +func (ct CodeText) Text() string { return string(ct) } diff --git a/pkg/adoc/inline_test.go b/pkg/adoc/inline_test.go new file mode 100644 index 0000000..9800b3e --- /dev/null +++ b/pkg/adoc/inline_test.go @@ -0,0 +1,50 @@ +package adoc_test + +import ( + "testing" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" +) + +func TestInlineSimple(t *testing.T) { + text := "text" + for _, tc := range []struct { + name string + element adoc.InlineElement + }{ + { + name: "plain text", + element: adoc.PlainText(text), + }, + { + name: "strong", + element: adoc.StrongText(text), + }, + { + name: "emphasis", + element: adoc.EmpText(text), + }, + { + name: "strong emphasis", + element: adoc.StrongEmpText(text), + }, + { + name: "code", + element: adoc.CodeText(text), + }, + } { + t.Run(tc.name, func(t *testing.T) { + test.Equals(t, text, tc.element.Text()) + }) + } +} + +func TextLink(t *testing.T) { + url := "url" + title := "title" + l := adoc.NewLink(url, title) + + test.Equals(t, url, l.URL()) + test.Equals(t, title, l.Text()) +} diff --git a/site/parser.go b/pkg/adoc/parser.go similarity index 82% rename from site/parser.go rename to pkg/adoc/parser.go index 5f72090..2926e46 100644 --- a/site/parser.go +++ b/pkg/adoc/parser.go @@ -1,4 +1,4 @@ -package site +package adoc import ( "strings" @@ -6,19 +6,18 @@ import ( ) const ( - TITLE_PREFIX = "= " - SUBTITLE_PREFIX = "== " - SUBSUBTITLE_PREFIX = "=== " - PARAGRAPH_SEPARATOR = "\n\n" - LINE_SEPARATOR = "\n" - CODE_PREFIX = "----\n" - CODE_SUFFIX = "\n----" - LISTITEM_PREFIX = "* " - PARAGRAPH_CONTINUATION = "\n+\n" + TITLE_PREFIX = "= " + SUBTITLE_PREFIX = "== " + SUBSUBTITLE_PREFIX = "=== " + PARAGRAPH_SEPARATOR = "\n\n" + LINE_SEPARATOR = "\n" + CODE_PREFIX = "----\n" + CODE_SUFFIX = "\n----" + LISTITEM_PREFIX = "* " ) -func NewPost(text string) Post { - post := Post{ +func New(text string) *ADoc { + doc := &ADoc{ Language: LANGUAGE_EN, Tags: []Tag{}, } @@ -27,6 +26,7 @@ func NewPost(text string) Post { var pars []string for _, s := range strings.Split(text, PARAGRAPH_SEPARATOR) { if s == "" { + continue } @@ -60,7 +60,7 @@ func NewPost(text string) Post { for i, p := range blocks { switch { case i == 0 && strings.HasPrefix(p, TITLE_PREFIX): - ParseHeader(p, &post) + ParseHeader(p, doc) case strings.HasPrefix(p, SUBTITLE_PREFIX): p = strings.TrimSpace(p) s := strings.Split(p, SUBTITLE_PREFIX) @@ -68,7 +68,7 @@ func NewPost(text string) Post { continue } - post.Content = append(post.Content, SubTitle(s[1])) + doc.Content = append(doc.Content, SubTitle(s[1])) case strings.HasPrefix(p, SUBSUBTITLE_PREFIX): p = strings.TrimSpace(p) s := strings.Split(p, SUBSUBTITLE_PREFIX) @@ -76,9 +76,9 @@ func NewPost(text string) Post { continue } - post.Content = append(post.Content, SubSubTitle(s[1])) + doc.Content = append(doc.Content, SubSubTitle(s[1])) case isCodeBlock(p): - post.Content = append(post.Content, parseCodeBlock(p)) + doc.Content = append(doc.Content, parseCodeBlock(p)) case strings.HasPrefix(p, LISTITEM_PREFIX): p = strings.TrimSpace(p) var items []ListItem @@ -88,15 +88,15 @@ func NewPost(text string) Post { items = append(items, ListItem(inline)) } } - post.Content = append(post.Content, List(items)) + doc.Content = append(doc.Content, List(items)) default: p = strings.TrimSpace(p) - post.Content = append(post.Content, Paragraph(ParseInline(p))) + doc.Content = append(doc.Content, Paragraph(ParseInline(p))) } } - return post + return doc } func isCodeBlock(par string) bool { @@ -111,31 +111,31 @@ func parseCodeBlock(par string) CodeBlock { return CodeBlock(content) } -func ParseHeader(text string, post *Post) { +func ParseHeader(text string, doc *ADoc) { text = strings.TrimSpace(text) lines := strings.Split(text, LINE_SEPARATOR) for i, l := range lines { switch { case i == 0: s := strings.Split(l, TITLE_PREFIX) - post.Title = s[1] + doc.Title = s[1] case isDate(l): date, _ := time.Parse("2006-01-02", l) - post.Date = date + doc.Date = date case strings.HasPrefix(l, ":kind:"): s := strings.Split(l, ":") - post.Kind = NewKind(strings.TrimSpace(s[2])) + doc.Kind = NewKind(strings.TrimSpace(s[2])) case strings.HasPrefix(l, ":language:"): s := strings.Split(l, ":") - post.Language = NewLanguage(strings.TrimSpace(s[2])) + doc.Language = NewLanguage(strings.TrimSpace(s[2])) case strings.HasPrefix(l, ":tags:"): s := strings.Split(l, ":") t := strings.Split(s[2], ",") for _, tag := range t { - post.Tags = append(post.Tags, Tag(strings.TrimSpace(tag))) + doc.Tags = append(doc.Tags, Tag(strings.TrimSpace(tag))) } default: - post.Author = l + doc.Author = l } } } diff --git a/pkg/adoc/parser_test.go b/pkg/adoc/parser_test.go new file mode 100644 index 0000000..9149300 --- /dev/null +++ b/pkg/adoc/parser_test.go @@ -0,0 +1,219 @@ +package adoc_test + +import ( + "fmt" + "testing" + "time" + + "git.sr.ht/~ewintr/go-kit/test" + "git.sr.ht/~ewintr/shitty-ssg/pkg/adoc" +) + +func TestNew(t *testing.T) { + one := "one" + two := "two" + three := "three" + ptOne := adoc.PlainText(one) + ptTwo := adoc.PlainText(two) + ptThree := adoc.PlainText(three) + for _, tc := range []struct { + name string + input string + exp *adoc.ADoc + }{ + { + name: "empty", + exp: &adoc.ADoc{ + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + }, + }, + { + name: "title", + input: "= Title", + exp: &adoc.ADoc{ + Title: "Title", + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + }, + }, + { + name: "header", + input: "= Title\nT. Test\n2020-10-27\n:tags:\ttag1, tag2\n:kind:\tnote\n:language:\tnl", + exp: &adoc.ADoc{ + Title: "Title", + Author: "T. Test", + Kind: adoc.KIND_NOTE, + Language: adoc.LANGUAGE_NL, + Tags: []adoc.Tag{ + adoc.Tag("tag1"), + adoc.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: &adoc.ADoc{ + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + Content: []adoc.BlockElement{ + adoc.Paragraph([]adoc.InlineElement{ptOne}), + adoc.Paragraph([]adoc.InlineElement{ptTwo}), + adoc.Paragraph([]adoc.InlineElement{ptThree}), + }, + }, + }, + { + name: "subtitle", + input: "== Subtitle", + exp: &adoc.ADoc{ + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + Content: []adoc.BlockElement{ + adoc.SubTitle("Subtitle"), + }, + }, + }, + { + name: "code block", + input: "----\nsome code\nmore code\n----", + exp: &adoc.ADoc{ + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + Content: []adoc.BlockElement{ + adoc.CodeBlock("some code\nmore code"), + }, + }, + }, + { + name: "code block with empty lines", + input: "----\nsome code\n\nmore code\n----", + exp: &adoc.ADoc{ + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + Content: []adoc.BlockElement{ + adoc.CodeBlock("some code\n\nmore code"), + }, + }, + }, + { + name: "list", + input: "* item 1\n* item 2\n* *item 3*\n", + exp: &adoc.ADoc{ + Tags: []adoc.Tag{}, + Language: adoc.LANGUAGE_EN, + Content: []adoc.BlockElement{ + adoc.List{ + adoc.ListItem([]adoc.InlineElement{adoc.PlainText("item 1")}), + adoc.ListItem([]adoc.InlineElement{adoc.PlainText("item 2")}), + adoc.ListItem([]adoc.InlineElement{adoc.StrongText("item 3")}), + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + act := adoc.New(tc.input) + + test.Equals(t, tc.exp, act) + }) + } +} + +func TestParseInline(t *testing.T) { + for _, tc := range []struct { + name string + input string + exp []adoc.InlineElement + }{{ + name: "empty", + }, + { + name: "plain", + input: "some test text", + exp: []adoc.InlineElement{ + adoc.PlainText("some test text")}, + }, + { + name: "strong", + input: "*some strong text*", + exp: []adoc.InlineElement{ + adoc.StrongText("some strong text"), + }, + }, + { + name: "strong in plain", + input: "some *strong* text", + exp: []adoc.InlineElement{ + adoc.PlainText("some "), + adoc.StrongText("strong"), + adoc.PlainText(" text"), + }, + }, + { + name: "emphasis", + input: "_some emphasized text_", + exp: []adoc.InlineElement{ + adoc.EmpText("some emphasized text"), + }, + }, + { + name: "emphasis in plain", + input: "some _emphasized_ text", + exp: []adoc.InlineElement{ + adoc.PlainText("some "), + adoc.EmpText("emphasized"), + adoc.PlainText(" text"), + }, + }, + { + name: "emp and strong in plain", + input: "some _*special*_ text", + exp: []adoc.InlineElement{ + adoc.PlainText("some "), + adoc.StrongEmpText("special"), + adoc.PlainText(" text"), + }, + }, + { + name: "link", + input: "a link[title] somewhere", + exp: []adoc.InlineElement{ + adoc.PlainText("a "), + adoc.NewLink("link", "title"), + adoc.PlainText(" somewhere"), + }, + }, + { + name: "code", + input: "`command`", + exp: []adoc.InlineElement{ + adoc.CodeText("command"), + }, + }, + { + name: "code in plain", + input: "some `code` in text", + exp: []adoc.InlineElement{ + adoc.PlainText("some "), + adoc.CodeText("code"), + adoc.PlainText(" in text"), + }, + }, + { + name: "link with underscore", + input: "https://example.com/some_url[some url]", + exp: []adoc.InlineElement{ + adoc.NewLink("https://example.com/some_url", "some url"), + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + act := adoc.ParseInline(tc.input) + + test.Equals(t, tc.exp, act) + }) + } +} diff --git a/site/block_test.go b/site/block_test.go deleted file mode 100644 index e61cb6e..0000000 --- a/site/block_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package site - -import ( - "fmt" - "testing" - - "git.sr.ht/~ewintr/go-kit/test" -) - -func TestParagraph(t *testing.T) { - p := Paragraph([]InlineElement{ - PlainText("one "), - PlainText("two "), - PlainText("three"), - }) - - exp := "

    one two three

    " - test.Equals(t, exp, p.BlockHTML()) -} - -func TestSubTitle(t *testing.T) { - text := "text" - st := SubTitle(text) - - exp := fmt.Sprintf("

    %s

    ", text) - test.Equals(t, exp, st.BlockHTML()) -} diff --git a/site/html.go b/site/html.go deleted file mode 100644 index 75d860b..0000000 --- a/site/html.go +++ /dev/null @@ -1,18 +0,0 @@ -package site - -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 -} diff --git a/site/inline.go b/site/inline.go deleted file mode 100644 index 33e3c59..0000000 --- a/site/inline.go +++ /dev/null @@ -1,71 +0,0 @@ -package site - -import ( - "fmt" - "html" -) - -type InlineType int - -type InlineElement interface { - Text() string - InlineHTML() string -} - -type PlainText string - -func (pt PlainText) Text() string { return string(pt) } - -func (pt PlainText) InlineHTML() string { - return html.EscapeString(string(pt)) -} - -type StrongText string - -func (st StrongText) Text() string { return string(st) } - -func (st StrongText) InlineHTML() string { - return fmt.Sprintf("%s", html.EscapeString(string(st))) -} - -type EmpText string - -func (et EmpText) Text() string { return string(et) } - -func (et EmpText) InlineHTML() string { - return fmt.Sprintf("%s", html.EscapeString(string(et))) -} - -type StrongEmpText string - -func (set StrongEmpText) Text() string { return string(set) } - -func (set StrongEmpText) InlineHTML() string { - return fmt.Sprintf("%s", html.EscapeString(string(set))) -} - -type Link struct { - url string - title string -} - -func NewLink(url, title string) Link { - return Link{ - url: url, - title: title, - } -} - -func (l Link) Text() string { return l.title } - -func (l Link) InlineHTML() string { - return fmt.Sprintf("%s", l.url, html.EscapeString(l.title)) -} - -type CodeText string - -func (ct CodeText) Text() string { return string(ct) } - -func (ct CodeText) InlineHTML() string { - return 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.

    -

    A Sub Title

    -

    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 -}