separate adoc pkg, prep dir structure for multiple binaries

This commit is contained in:
Erik Winter 2020-12-21 07:36:45 +01:00
parent 40e2a9d93f
commit 149a95b56c
28 changed files with 1352 additions and 626 deletions

View File

@ -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 (

75
cmd/ssg/site/html.go Normal file
View File

@ -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("<p>%s</p>", text)
case adoc.SubTitle:
return fmt.Sprintf("<h2>%s</h2>", html.EscapeString(block.Text()))
case adoc.SubSubTitle:
return fmt.Sprintf("<h3>%s</h3>", html.EscapeString(block.Text()))
case adoc.CodeBlock:
return fmt.Sprintf("<pre><code>%s</code></pre>", 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("<li>%s</li>", itemText))
}
return fmt.Sprintf("<ul>\n%s\n</ul>", 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("<strong>%s</strong>", text)
case adoc.EmpText:
return fmt.Sprintf("<em>%s</em>", text)
case adoc.StrongEmpText:
return fmt.Sprintf("<strong><em>%s</em></strong>", text)
case adoc.Link:
return fmt.Sprintf("<a href=%q>%s</a>", src.(adoc.Link).URL(), text)
case adoc.CodeText:
return fmt.Sprintf("<code>%s</code>", text)
default:
return ""
}
}

98
cmd/ssg/site/html_test.go Normal file
View File

@ -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: "<p>onetwothree</p>",
},
{
name: "subtitle",
element: adoc.SubTitle("text"),
exp: "<h2>text</h2>",
},
{
name: "subsubtitle",
element: adoc.SubSubTitle("text"),
exp: "<h3>text</h3>",
},
{
name: "code",
element: adoc.CodeBlock("text"),
exp: "<pre><code>text</code></pre>",
},
{
name: "list",
element: adoc.List{
{adoc.PlainText("one")},
{adoc.PlainText("two")},
{adoc.PlainText("three")},
},
exp: "<ul>\n<li>one</li>\n<li>two</li>\n<li>three</li>\n</ul>",
},
} {
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: "<strong>text</strong>",
},
{
name: "emphasis",
element: adoc.EmpText("text"),
exp: "<em>text</em>",
},
{
name: "strong emphasis",
element: adoc.StrongEmpText("text"),
exp: "<strong><em>text</em></strong>",
},
{
name: "link",
element: adoc.NewLink("url", "title"),
exp: `<a href="url">title</a>`,
},
{
name: "code",
element: adoc.CodeText("text"),
exp: "<code>text</code>",
},
} {
t.Run(tc.name, func(t *testing.T) {
test.Equals(t, tc.exp, site.FormatInline(tc.element))
})
}
}

View File

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

79
cmd/ssg/site/meta_test.go Normal file
View File

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

143
cmd/ssg/site/post.go Normal file
View File

@ -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("<p>%s...</p>", 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, " ")
}

182
cmd/ssg/site/post_test.go Normal file
View File

@ -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: "<p>one...</p>",
}
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: "<p>one</p>\n<p>two</p>\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: "<p>one</p>\n<p>two</p>\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))
})
}
}

View File

@ -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 {

View File

@ -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())
})
}

View File

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

View File

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

5
go.mod
View File

@ -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
)

3
go.sum
View File

@ -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=

71
pkg/adoc/adoc.go Normal file
View File

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

110
pkg/adoc/adoc_test.go Normal file
View File

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

View File

@ -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("<p>%s</p>", body)
}
type SubTitle string
func (st SubTitle) Text() string { return string(st) }
func (st SubTitle) BlockHTML() string {
return fmt.Sprintf("<h2>%s</h2>", st)
}
type SubSubTitle string
func (st SubSubTitle) Text() string { return string(st) }
func (st SubSubTitle) BlockHTML() string {
return fmt.Sprintf("<h3>%s</h3>", st)
}
type CodeBlock string
func (cb CodeBlock) Text() string { return string(cb) }
func (cb CodeBlock) BlockHTML() string {
return fmt.Sprintf("<pre><code>%s</code></pre>", 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("<li>%s</li>", 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("<ul>\n%s\n</ul>", strings.Join(items, "\n"))
}

134
pkg/adoc/block_test.go Normal file
View File

@ -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())
})
}
}

40
pkg/adoc/inline.go Normal file
View File

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

50
pkg/adoc/inline_test.go Normal file
View File

@ -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())
}

View File

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

219
pkg/adoc/parser_test.go Normal file
View File

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

View File

@ -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 := "<p>one two three</p>"
test.Equals(t, exp, p.BlockHTML())
}
func TestSubTitle(t *testing.T) {
text := "text"
st := SubTitle(text)
exp := fmt.Sprintf("<h2>%s</h2>", text)
test.Equals(t, exp, st.BlockHTML())
}

View File

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

View File

@ -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("<strong>%s</strong>", html.EscapeString(string(st)))
}
type EmpText string
func (et EmpText) Text() string { return string(et) }
func (et EmpText) InlineHTML() string {
return fmt.Sprintf("<em>%s</em>", html.EscapeString(string(et)))
}
type StrongEmpText string
func (set StrongEmpText) Text() string { return string(set) }
func (set StrongEmpText) InlineHTML() string {
return fmt.Sprintf("<strong><em>%s</em></strong>", 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("<a href=%q>%s</a>", 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("<code>%s</code>", html.EscapeString(string(ct)))
}

View File

@ -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("<strong>%s</strong>", text)
test.Equals(t, exp, st.InlineHTML())
}
func TestEmpText(t *testing.T) {
text := "text"
et := site.EmpText(text)
exp := fmt.Sprintf("<em>%s</em>", 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("<a href=%q>%s</a>", url, title)
test.Equals(t, exp, link.InlineHTML())
}

View File

@ -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: `<p>Some text. With some <strong>strong</strong>. And a <a href="http://example.com">link</a>.</p>
<h2>A Sub Title</h2>
<p>And some more text.</p>
`,
}
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)
})
}
}

View File

@ -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("<p>%s...</p>", 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
}