diff --git a/.gitignore b/.gitignore index 89f9ac04aac6c8ee66e158853e7d0439b3ec782d..c6aa2a4851f3a5271fdf9b86ce78e6e8ce1cbcef 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -out/ +/blog* +!blog.go +!blog_test.go + +_example/out diff --git a/_example/README.md b/_example/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9a7224f619191edd2bcdf5d9f5acc90976f2e60d --- /dev/null +++ b/_example/README.md @@ -0,0 +1,13 @@ +# Example blog structure + +The `articles` dir contains a list of articles, namely this one. + +The `templates` dir contains a handful of [go templates](https://pkg.go.dev/html/template) that make up how to render the blog. +- `article.tmpl` is a template for an individual article (this page). +- `index.tmpl` is the main blog overview. +- `base.tmpl` is a collection of [defined blocks](https://pkg.go.dev/text/template#hdr-Nested_template_definitions) +that are then re-used in `index.tmpl` and `article.tmpl` to create a cohesive look-and-feel. + +Finally, `config.yaml` (which can also be TOML if desired) allows you to not have to continuously re-type all the flags to define things like your +`--article-dir`, `--template-dir`, or `--out`. + diff --git a/_example/config.yaml b/_example/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..914646cc3ba1a16edd9b65861c517ff51634acc7 --- /dev/null +++ b/_example/config.yaml @@ -0,0 +1,9 @@ +article-dir: articles +template-dir: templates +out-dir: out +author: + name: jolheiser + email: john+blog@jolheiser.com + links: + - name: GitHub + url: "https://github.com/jolheiser" diff --git a/_example/templates/article.tmpl b/_example/templates/article.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..db4e9ecb5eab85d4a3d0f5bbc553b3b5f6659c02 --- /dev/null +++ b/_example/templates/article.tmpl @@ -0,0 +1,8 @@ +{{template "head" .Article.Title}} + +
+ {{.Author.Name}} +
+
+
{{.Article.Content}}
+ diff --git a/_example/templates/base.tmpl b/_example/templates/base.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..7a05f6c9c322f6db80c4885b595f3a2c944552ee --- /dev/null +++ b/_example/templates/base.tmpl @@ -0,0 +1,8 @@ +{{define "head"}} + + +{{.}} + + + +{{end}} diff --git a/_example/templates/index.tmpl b/_example/templates/index.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..20ddba201b1aab26c30e6d8bc357fddfaaed7f0b --- /dev/null +++ b/_example/templates/index.tmpl @@ -0,0 +1,17 @@ +{{template "head" (printf "%s's Blog" .Author.Name)}} + +
+

{{.Author.Name}}

+

I really like to hack on Gitea and I use Catppuccin (btw)

+
+
+{{range $category, $articles := .ArticlesByCategory}} +

{{$category}}

+ +{{end}} +
+ diff --git a/articles/introduction.md b/_example/articles/introduction.md rename from articles/introduction.md rename to _example/articles/introduction.md index 221258b975335736ddb331fa0f3d20d269d75495..67956be729182b0ac63aa2360d99554f7152f141 100644 --- a/articles/introduction.md +++ b/_example/articles/introduction.md @@ -1,9 +1,16 @@ -+++ +--- +date = 2024-02-17 title = "Introduction" +date = 2024-02-17 summary = "An introduction of `blog`" date = 2024-02-17 +date = 2024-02-17 +date = 2024-02-17 category = "Miscellaneous" -+++ +authors: + - name: "jolheiser" + email: "john+blog@jolheiser.com" +--- # Hello and welcome to blog! @@ -61,4 +68,3 @@ that are then re-used in `index.tmpl` and `article.tmpl` to create a cohesive look-and-feel. Finally, `config.yaml` (which can also be TOML if desired) allows you to not have to continuously re-type all the flags to define things like your `--article-dir`, `--template-dir`, or `--out`. - diff --git a/blog.go b/blog.go new file mode 100644 index 0000000000000000000000000000000000000000..21f761408aedd5cedc2f77052c46a8941dca5727 --- /dev/null +++ b/blog.go @@ -0,0 +1,244 @@ +package blog + +import ( + "bytes" + "errors" + "fmt" + "html/template" + "io" + "io/fs" + "net/url" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/bmatcuk/doublestar/v4" + "github.com/pelletier/go-toml/v2" + "gopkg.in/yaml.v3" +) + +// Blog is a collection of [Article] +type Blog struct { + indexTemplate *template.Template + articleTemplate *template.Template + Articles []Article + Author Author +} + +// Article is a blog post/article +type Article struct { + Filename string + Content template.HTML + ArticleMeta +} + +// ArticleMeta is the metadata of an [Article] +// The meta is parsed from frontmatter in an article +type ArticleMeta struct { + Title string + Subtitle string + Summary string + Time time.Time + Author Author + Tags []string + Category string +} + +// Author is an author of a blog/post +type Author struct { + Name string + Job string + Email string + Links []Link +} + +// Link is a link name and URL +type Link struct { + Name string + URL LinkURL +} + +// LinkURL is a URL +type LinkURL url.URL + +func (l *LinkURL) UnmarshalText(data []byte) error { + u, err := url.Parse(string(data)) + if err != nil { + return err + } + *l = LinkURL(*u) + return nil +} + +// NewBlog constructs a new blog from articles in articleDir and templates in templateDir +func NewBlog(articleDir, templateDir string, author Author) (*Blog, error) { + tmpl, err := parseTemplates(os.DirFS(templateDir)) + if err != nil { + return nil, fmt.Errorf("could not parse templates in %q: %w", templateDir, err) + } + indexTmpl := tmpl.Lookup("index.tmpl") + if indexTmpl == nil { + indexTmpl = tmpl.Lookup("index") + if indexTmpl == nil { + return nil, errors.New("`index` template is required but was not found") + } + } + articleTmpl := tmpl.Lookup("article.tmpl") + if articleTmpl == nil { + articleTmpl = tmpl.Lookup("article") + if articleTmpl == nil { + return nil, errors.New("`article` template is required but was not found") + } + } + articles, err := parseArticles(os.DirFS(articleDir)) + if err != nil { + return nil, fmt.Errorf("could not parse articles in %q: %w", articleDir, err) + } + return &Blog{ + indexTemplate: indexTmpl, + articleTemplate: articleTmpl, + Articles: articles, + Author: author, + }, nil +} + +// Index renders the blog index to w +func (b *Blog) Index(w io.Writer) error { + byCat := make(map[string][]Article) + for _, article := range b.Articles { + byCat[article.Category] = append(byCat[article.Category], article) + } + return b.indexTemplate.Execute(w, map[string]any{ + "Articles": b.Articles, + "ArticlesByCategory": byCat, + "Author": b.Author, + }) +} + +// Article renders an article to w +func (b *Blog) Article(w io.Writer, a Article) error { + return b.articleTemplate.Execute(w, map[string]any{ + "Article": a, + "Author": b.Author, + }) +} + +func parseTemplates(fs fs.FS) (*template.Template, error) { + matches, err := doublestar.Glob(fs, "**/*.{tmpl,gohtml}") + if err != nil { + return nil, fmt.Errorf("could not glob templates: %w", err) + } + tmpl, err := template.New("").ParseFS(fs, matches...) + if err != nil { + return nil, fmt.Errorf("could not parse templates: %w", err) + } + return tmpl, nil +} + +func parseArticles(fs fs.FS) ([]Article, error) { + matches, err := doublestar.Glob(fs, "**/*.md") + if err != nil { + return nil, fmt.Errorf("could not glob articles: %w", err) + } + articles := make([]Article, 0, len(matches)) + for _, match := range matches { + if err := func() error { + fi, err := fs.Open(match) + if err != nil { + return err + } + defer fi.Close() + + content, err := io.ReadAll(fi) + if err != nil { + return err + } + + article, err := parseArticle(string(content)) + if err != nil { + return err + } + article.Filename = strings.TrimSuffix(filepath.Base(match), filepath.Ext(match)) + articles = append(articles, article) + return nil + }(); err != nil { + return nil, err + } + } + sort.SliceStable(articles, func(i, j int) bool { + return articles[i].Time.After(articles[j].Time) + }) + return articles, nil +} + +func parseArticle(content string) (Article, error) { + lines := strings.Split(content, "\n") + + start, end := -1, -1 + var isSep func(string) bool + var decoder func([]byte, any) error + + for idx, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + if isSep != nil && isSep(line) { + end = idx + break + } + + if isTOMLSeparator(line) { + start = idx + isSep = isTOMLSeparator + decoder = toml.Unmarshal + continue + } + + if isYAMLSeparator(line) { + start = idx + isSep = isYAMLSeparator + decoder = yaml.Unmarshal + continue + } + } + + var meta ArticleMeta + body := content + if start != -1 && end != -1 { + body = strings.Join(lines[end+1:], "\n") + if err := decoder([]byte(strings.Join(lines[start+1:end], "\n")), &meta); err != nil { + return Article{}, fmt.Errorf("could not parse frontmatter: %w", err) + } + } + + var buf bytes.Buffer + err := Markdown.Convert([]byte(body), &buf) + if err != nil { + return Article{}, fmt.Errorf("could not convert article: %w", err) + } + + return Article{ + Content: template.HTML(buf.String()), + ArticleMeta: meta, + }, nil +} + +func isTOMLSeparator(line string) bool { + for _, char := range line { + if char != '+' { + return false + } + } + return true +} + +func isYAMLSeparator(line string) bool { + for _, char := range line { + if char != '-' { + return false + } + } + return true +} diff --git a/blog_test.go b/blog_test.go new file mode 100644 index 0000000000000000000000000000000000000000..821a659ae04f26a6c4bb1157f8c4835e58de07b2 --- /dev/null +++ b/blog_test.go @@ -0,0 +1,72 @@ +package blog + +import ( + "net/url" + "testing" + "time" + + "github.com/alecthomas/assert" +) + +func TestParseArticle(t *testing.T) { + var ( + raw = `+++ +title = "Honk" +subtitle = "Bonk" +summary = """This +is +a +summary""" +time = 2024-02-16 +tags = ["bocchi", "rocks"] + +[author] +name = "Bocchi" +job = "Guitarist" +email = "bocchi@rock.s" + +[[author.links]] +name = "website" +url = "https://example.com/bocchi" ++++ + +# Hello world + +Beep boop` + meta = ArticleMeta{ + Title: "Honk", + Subtitle: "Bonk", + Summary: `This +is +a +summary`, + Time: func() time.Time { + t, _ := time.ParseInLocation("2006-01-02", "2024-02-16", time.Local) + return t + }(), + Author: Author{ + Name: "Bocchi", + Job: "Guitarist", + Email: "bocchi@rock.s", + Links: []Link{ + { + Name: "website", + URL: func() LinkURL { + u, _ := url.Parse("https://example.com/bocchi") + return LinkURL(*u) + }(), + }, + }, + }, + Tags: []string{"bocchi", "rocks"}, + } + body = `

Hello world

+

Beep boop

+` + ) + + article, err := parseArticle(raw) + assert.NoError(t, err) + assert.Equal(t, meta, article.ArticleMeta) + assert.Equal(t, body, article.Content) +} diff --git a/cmd/blog/main.go b/cmd/blog/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d5d835e2eab4153e10fc157dd07aec9cbb7507d4 --- /dev/null +++ b/cmd/blog/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/pelletier/go-toml/v2" + "go.jolheiser.com/blog" + "gopkg.in/yaml.v3" +) + +type args struct { + ArticleDir string `yaml:"article-dir" toml:"article-dir"` + TemplateDir string `yaml:"template-dir" toml:"template-dir"` + OutDir string `yaml:"out-dir" toml:"out-dir"` + Author blog.Author `yaml:"author" toml:"author"` +} + +func main() { + if err := maine(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func maine() error { + var args args + + fs := flag.NewFlagSet("blog", flag.ExitOnError) + + configFlag := fs.String("config", "", "Configuration, TOML or YAML") + fs.StringVar(configFlag, "c", *configFlag, "--config") + articlesFlag := fs.String("articles", "", "Path to articles") + fs.StringVar(articlesFlag, "a", *articlesFlag, "--articles") + templatesFlag := fs.String("templates", "", "Path to templates") + fs.StringVar(templatesFlag, "t", *templatesFlag, "--templates") + outFlag := fs.String("out", "", "Path to output") + fs.StringVar(outFlag, "o", *outFlag, "--out") + + if err := fs.Parse(os.Args[1:]); err != nil { + return err + } + + if *configFlag != "" { + data, err := os.ReadFile(*configFlag) + if err != nil { + return err + } + switch ext := filepath.Ext(*configFlag); ext { + case ".yaml", ".yml": + if err := yaml.Unmarshal(data, &args); err != nil { + return err + } + case ".toml": + if err := toml.Unmarshal(data, &args); err != nil { + return err + } + default: + return fmt.Errorf("could not determine config type %q", ext) + } + } + + if *articlesFlag != "" { + args.ArticleDir = *articlesFlag + } + if args.ArticleDir == "" { + args.ArticleDir = "articles" + } + if *templatesFlag != "" { + args.TemplateDir = *templatesFlag + } + if args.TemplateDir == "" { + args.TemplateDir = "templates" + } + if *outFlag != "" { + args.OutDir = *outFlag + } + if args.OutDir == "" { + args.OutDir = "out" + } + + if err := os.MkdirAll(args.OutDir, os.ModePerm); err != nil { + return err + } + + blog, err := blog.NewBlog(args.ArticleDir, args.TemplateDir, args.Author) + if err != nil { + return err + } + + indexFile, err := os.Create(filepath.Join(args.OutDir, "index.html")) + if err != nil { + return err + } + defer indexFile.Close() + if err := blog.Index(indexFile); err != nil { + return err + } + + for _, article := range blog.Articles { + if err := func() error { + articleFile, err := os.Create(filepath.Join(args.OutDir, article.Filename+".html")) + if err != nil { + return err + } + defer articleFile.Close() + return blog.Article(articleFile, article) + }(); err != nil { + return err + } + } + + return nil +} diff --git a/cmd/chromacss/main.go b/cmd/chromacss/main.go new file mode 100644 index 0000000000000000000000000000000000000000..e2b1eefe7aa3763e530c534812bee0f951a768b8 --- /dev/null +++ b/cmd/chromacss/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/styles" + "go.jolheiser.com/blog" +) + +func main() { + if err := maine(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func maine() error { + fs := flag.NewFlagSet("chromacss", flag.ExitOnError) + lightFlag := fs.String("light", "catppuccin-latte", "Light theme") + fs.StringVar(lightFlag, "l", *lightFlag, "--light") + darkFlag := fs.String("dark", "catppuccin-mocha", "Dark theme") + fs.StringVar(darkFlag, "d", *darkFlag, "--dark") + outFlag := fs.String("out", "", "Output (default: stdout)") + fs.StringVar(outFlag, "o", *outFlag, "--out") + if err := fs.Parse(os.Args[1:]); err != nil { + return err + } + + out := os.Stdout + if *outFlag != "" { + fi, err := os.Create(*outFlag) + if err != nil { + return err + } + defer fi.Close() + out = fi + } + + formatter := html.New(blog.ChromaOpts...) + + lightStyle := *lightFlag + darkStyle := *darkFlag + + if lightStyle == "" { + lightStyle = "catppuccin-latte" + if darkStyle != "" { + lightStyle = darkStyle + darkStyle = "" + } + } + + styles.Fallback = styles.Get("catppuccin-latte") + style := styles.Get(lightStyle) + if err := formatter.WriteCSS(out, style); err != nil { + return err + } + + if darkStyle != "" { + out.WriteString("@media (prefers-color-scheme: dark) {") + styles.Fallback = styles.Get("catpuccin-mocha") + style = styles.Get(darkStyle) + if err := formatter.WriteCSS(out, style); err != nil { + return err + } + out.WriteString("}") + } + + return nil +} diff --git a/go.mod b/go.mod index 3d226dae1f71f357af8b8c8cbd8f38889bcba113..8c1aa017c7924a7b8ed3b4ceab8cde26ece9de51 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,32 @@ go 1.21.6 require ( - github.com/BurntSushi/toml v1.3.2 - github.com/a-h/templ v0.2.543 +module go.jolheiser.com/blog github.com/alecthomas/chroma/v2 v2.12.0 +module go.jolheiser.com/blog github.com/yuin/goldmark v1.7.0 +module go.jolheiser.com/blog github.com/yuin/goldmark-emoji v1.0.2 -module go.jolheiser.com/blog + + module go.jolheiser.com/blog + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect module go.jolheiser.com/blog +go 1.21.6 + github.com/gorilla/feeds v1.1.2 // indirect + github.com/jolheiser/goldmark-meta v0.0.2 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect -module go.jolheiser.com/blog + github.com/a-h/templ v0.2.543 + github.com/alecthomas/chroma/v2 v2.12.0 -require ( + github.com/sergi/go-diff v1.2.0 // indirect -module go.jolheiser.com/blog + github.com/yuin/goldmark v1.7.0 // indirect go 1.21.6 +go 1.21.6 module go.jolheiser.com/blog -require ( - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect + golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c4b1bc6fb037c92e5fe68ac1efae39f51b36f823..62a81853e4095cb8c64512a1f9d8ff9e25a8fc83 100644 --- a/go.sum +++ b/go.sum @@ -1,46 +1,63 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk= -github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA= +github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw= github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw= +github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= +github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw= +github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= +github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.3.7 h1:NSaHgaeJFCtWXCBkBKXw0rhgMuJ0VoE9FB5mWldcrQ4= github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.15 h1:CFa84T0goNn/UIXYS+dmjjVxMyTAvpOmzld40N/nfK0= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA= github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= @@ -50,12 +67,18 @@ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA= +github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw= github.com/a-h/templ v0.2.543 h1:8YyLvyUtf0/IE2nIwZ62Z/m2o2NqwhnMynzOL78Lzbk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA= -github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= +github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw= github.com/a-h/templ v0.2.543/go.mod h1:jP908DQCwI08IrnTalhzSEH9WJqG/Q94+EODQcJGFUA= -github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/goldmark.go b/goldmark.go new file mode 100644 index 0000000000000000000000000000000000000000..195e16337b248d03814a8d54db027cad16c6ec19 --- /dev/null +++ b/goldmark.go @@ -0,0 +1,39 @@ +package blog + +import ( + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/yuin/goldmark" + emoji "github.com/yuin/goldmark-emoji" + highlighting "github.com/yuin/goldmark-highlighting/v2" + meta "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" +) + +// Markdown is the default markdown converter +// Set over this variable to alter how the other functions convert +var Markdown = goldmark.New( + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + extension.GFM, + meta.Meta, + emoji.Emoji, + highlighting.NewHighlighting( + highlighting.WithFormatOptions( + ChromaOpts..., + ), + ), + ), +) + +var ChromaOpts = []chromahtml.Option{ + chromahtml.WithClasses(true), + chromahtml.WithAllClasses(true), + chromahtml.WithLineNumbers(true), +} diff --git a/main.go b/main.go deleted file mode 100644 index f0c5c9ee09a29fe144c863df27be02a3a1bf7a3e..0000000000000000000000000000000000000000 --- a/main.go +++ /dev/null @@ -1,103 +0,0 @@ -//go:generate templ generate -//go:generate go run . -package main - -import ( - "context" - "embed" - "flag" - "fmt" - iofs "io/fs" - "os" - "path/filepath" - - "github.com/alecthomas/chroma/v2/styles" -) - -//go:embed articles/* -var articleFS embed.FS - -func maine() error { - fs := flag.NewFlagSet("blog", flag.ExitOnError) - outFlag := fs.String("out", "out", "Output directory") - fs.StringVar(outFlag, "o", *outFlag, "--out") - if err := fs.Parse(os.Args[1:]); err != nil { - return err - } - - files, err := articleFS.ReadDir("articles") - if err != nil { - return err - } - - articles := make(Articles) - for _, file := range files { - if filepath.Ext(file.Name()) != ".md" { - continue - } - content, err := iofs.ReadFile(articleFS, fmt.Sprintf("articles/%s", file.Name())) - if err != nil { - return err - } - article, err := Parse(string(content)) - if err != nil { - return err - } - articles[article.Category] = append(articles[article.Category], article) - if err := writeArticle(*outFlag, article); err != nil { - return err - } - } - - if err := writeCSS(*outFlag); err != nil { - return err - } - - fi, err := os.Create(filepath.Join(*outFlag, "index.html")) - if err != nil { - return err - } - defer fi.Close() - - return IndexTemplate(articles).Render(context.Background(), fi) -} - -func writeArticle(out string, article Article) error { - dest := filepath.Join(out, article.Slug(), "index.html") - if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { - return err - } - - fi, err := os.Create(dest) - if err != nil { - return err - } - defer fi.Close() - - return ArticleTemplate(article).Render(context.Background(), fi) -} - -func writeCSS(out string) error { - fi, err := os.Create(filepath.Join(out, "chroma.css")) - if err != nil { - return err - } - defer fi.Close() - - if err := CSS.WriteCSS(fi, styles.Get("catpuccin-latte")); err != nil { - return err - } - fi.WriteString("@media (prefers-color-scheme: dark) {") - if err := CSS.WriteCSS(fi, styles.Get("catppuccin-mocha")); err != nil { - return err - } - fi.WriteString("}") - return nil -} - -func main() { - if err := maine(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/templates.go b/templates.go deleted file mode 100644 index 8af412b693c9a8150dd0e116a5d93f31a59570d1..0000000000000000000000000000000000000000 --- a/templates.go +++ /dev/null @@ -1,119 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "strings" - "time" - - "github.com/BurntSushi/toml" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/yuin/goldmark" - emoji "github.com/yuin/goldmark-emoji" - highlighting "github.com/yuin/goldmark-highlighting/v2" - meta "github.com/yuin/goldmark-meta" - "github.com/yuin/goldmark/extension" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer/html" -) - -// Articles by Category -type Articles map[string][]Article - -type Article struct { - Content string - Meta -} - -func (a Article) Slug() string { - slug := strings.NewReplacer(" ", "-").Replace(strings.ToLower(a.Meta.Title)) - return fmt.Sprintf("%s/%s/", a.Meta.Date.Format("2006"), slug) -} - -type Meta struct { - Title string - Summary string - Date time.Time - Category string -} - -type Author struct { - Name string - Email string -} - -func Parse(content string) (Article, error) { - lines := strings.Split(content, "\n") - - start, end := -1, -1 - for idx, line := range lines { - if strings.TrimSpace(line) == "" { - continue - } - - if isTOMLSeparator(line) { - if start == -1 { - start = idx - continue - } - end = idx - break - } - } - - var meta Meta - body := content - if start != -1 && end != -1 { - body = strings.Join(lines[end+1:], "\n") - if err := toml.Unmarshal([]byte(strings.Join(lines[start+1:end], "\n")), &meta); err != nil { - return Article{}, fmt.Errorf("could not parse frontmatter: %w", err) - } - } - - var buf bytes.Buffer - err := Markdown.Convert([]byte(body), &buf) - if err != nil { - return Article{}, fmt.Errorf("could not convert article: %w", err) - } - - return Article{ - Content: buf.String(), - Meta: meta, - }, nil -} - -func isTOMLSeparator(line string) bool { - for _, char := range line { - if char != '+' { - return false - } - } - return true -} - -var ( - opts = []chromahtml.Option{ - chromahtml.WithClasses(true), - chromahtml.WithAllClasses(true), - chromahtml.WithLineNumbers(true), - } - Markdown = goldmark.New( - goldmark.WithParserOptions( - parser.WithAutoHeadingID(), - ), - goldmark.WithRendererOptions( - html.WithUnsafe(), - ), - goldmark.WithExtensions( - extension.GFM, - meta.Meta, - emoji.Emoji, - highlighting.NewHighlighting( - highlighting.WithFormatOptions( - opts..., - ), - ), - ), - ) - CSS = chromahtml.New(opts...) -) diff --git a/templates.templ b/templates.templ deleted file mode 100644 index daee1e81f364b2c7d571ffe38b7e3eb24d24def0..0000000000000000000000000000000000000000 --- a/templates.templ +++ /dev/null @@ -1,45 +0,0 @@ -package main - -templ baseTemplate(title, description string) { - - - - - - { title } - - - - - - - { children... } - - -} - -templ IndexTemplate(articles Articles) { - @baseTemplate("jolheiser's blog", "Hahaha yes.....YES!") { -
- for category, articles := range articles { -

{ category }

- - } -
- } -} - -templ ArticleTemplate(article Article) { - @baseTemplate(article.Title, article.Summary) { -
-

{ article.Title }

-

{ article.Date.Format("01/02/2006") }

-
-
@templ.Raw(article.Content)
- } -} - diff --git a/templates_templ.go b/templates_templ.go deleted file mode 100644 index eb1a5286ff88f24036184a633dcb98ac78aa66d5..0000000000000000000000000000000000000000 --- a/templates_templ.go +++ /dev/null @@ -1,219 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: 0.2.480 -package main - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import "context" -import "io" -import "bytes" - -func baseTemplate(title, description string) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string = title - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } - return templ_7745c5c3_Err - }) -} - -func IndexTemplate(articles Articles) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var3 := templ.GetChildren(ctx) - if templ_7745c5c3_Var3 == nil { - templ_7745c5c3_Var3 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var4 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for category, articles := range articles { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string = category - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) - } - return templ_7745c5c3_Err - }) - templ_7745c5c3_Err = baseTemplate("jolheiser's blog", "Hahaha yes.....YES!").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } - return templ_7745c5c3_Err - }) -} - -func ArticleTemplate(article Article) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var9 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) - if !templ_7745c5c3_IsBuffer { - templ_7745c5c3_Buffer = templ.GetBuffer() - defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var10 string = article.Title - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var11 string = article.Date.Format("01/02/2006") - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(article.Content).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) - } - return templ_7745c5c3_Err - }) - templ_7745c5c3_Err = baseTemplate(article.Title, article.Summary).Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if !templ_7745c5c3_IsBuffer { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) - } - return templ_7745c5c3_Err - }) -}