From 280c3aaef4562bb757adfe68d72ca38d3682a192 Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Sat, 25 Dec 2021 23:43:06 +0100 Subject: [PATCH] Refactor: Isolata in un pacchetto autonomo la struct che gestisce il registro degli articoli --- articles.go | 204 ----------------------------------- articles/article.go | 113 +++++++++++++++++++ articles/articles.go | 77 +++++++++++++ articles/markdown.go | 60 +++++++++++ main.go | 9 +- news/2021-12-22-notizia-1.md | 1 + news/2021-12-23-notizia-2.md | 1 + news/2021-12-24-notizia-3.md | 1 + 8 files changed, 258 insertions(+), 208 deletions(-) delete mode 100644 articles.go create mode 100644 articles/article.go create mode 100644 articles/articles.go create mode 100644 articles/markdown.go diff --git a/articles.go b/articles.go deleted file mode 100644 index 49ada2b..0000000 --- a/articles.go +++ /dev/null @@ -1,204 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "os" - "path" - "sort" - "strings" - "time" - - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/renderer/html" - "github.com/yuin/goldmark/util" - - chromahtml "github.com/alecthomas/chroma/formatters/html" - mathjax "github.com/litao91/goldmark-mathjax" - highlighting "github.com/yuin/goldmark-highlighting" - - "gopkg.in/yaml.v3" -) - -var md goldmark.Markdown - -// https://github.com/yuin/goldmark-highlighting/blob/9216f9c5aa010c549cc9fc92bb2593ab299f90d4/highlighting_test.go#L27 -func customCodeBlockWrapper(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) { - lang, ok := c.Language() - if entering { - if ok { - w.WriteString(fmt.Sprintf(`
`, lang))
-			return
-		}
-		w.WriteString("
")
-	} else {
-		if ok {
-			w.WriteString("
") - return - } - w.WriteString("
") - } -} - -func init() { - md = goldmark.New( - goldmark.WithExtensions( - extension.GFM, - extension.Typographer, - // Questo pacchetto ha un nome stupido perché in realtà si occupa solo del parsing lato server del Markdown mentre lato client usiamo KaTeX. - mathjax.NewMathJax(), - highlighting.NewHighlighting( - highlighting.WithStyle("github"), - highlighting.WithWrapperRenderer(customCodeBlockWrapper), - highlighting.WithFormatOptions( - chromahtml.PreventSurroundingPre(true), - ), - ), - ), - goldmark.WithParserOptions( - parser.WithAutoHeadingID(), - ), - goldmark.WithRendererOptions( - html.WithHardWraps(), - html.WithXHTML(), - ), - ) -} - -type ArticleFrontMatter struct { - Title string `yaml:"title"` - Description string `yaml:"description"` - Tags string `yaml:"tags"` - PublishDate string `yaml:"publish_date"` -} - -type Article struct { - Id string - Title string - Description string - Tags []string - PublishDate time.Time - - MarkdownSource string - renderedHTML string -} - -func (article *Article) HasTag(tag string) bool { - for _, t := range article.Tags { - if t == tag { - return true - } - } - - return false -} - -func (article *Article) Render() (string, error) { - if article.renderedHTML == "" { - var buf bytes.Buffer - if err := md.Convert([]byte(article.MarkdownSource), &buf); err != nil { - return "", err - } - - article.renderedHTML = buf.String() - } - - return article.renderedHTML, nil -} - -type ArticleRenderer struct { - RootPath string -} - -func trimAll(vs []string) []string { - r := []string{} - for _, v := range vs { - r = append(r, strings.TrimSpace(v)) - } - return r -} - -func removeBlanks(v []string) []string { - r := []string{} - - for _, s := range v { - if len(strings.TrimSpace(s)) > 0 { - r = append(r, s) - } - } - - return r -} - -func (registry *ArticleRenderer) Load(articlePath string) (*Article, error) { - fileBytes, err := os.ReadFile(path.Join(registry.RootPath, articlePath+".md")) - if err != nil { - return nil, err - } - - source := string(fileBytes) - - // TODO: Ehm bugia esiste "https://github.com/yuin/goldmark-meta" però boh per ora funge tutto - parts := removeBlanks(strings.Split(source, "---")) - - frontMatterSource := parts[0] - markdownSource := strings.Join(parts[1:], "---") - - var frontMatter ArticleFrontMatter - if err := yaml.Unmarshal([]byte(frontMatterSource), &frontMatter); err != nil { - return nil, err - } - - publishDate, err := time.Parse("2006/01/02 15:04", frontMatter.PublishDate) - if err != nil { - return nil, err - } - - tags := trimAll(strings.Split(frontMatter.Tags, ",")) - - article := &Article{ - Id: articlePath, - Title: frontMatter.Title, - Description: frontMatter.Description, - Tags: tags, - PublishDate: publishDate, - MarkdownSource: markdownSource, - } - - return article, nil -} - -func (registry *ArticleRenderer) LoadAll() ([]*Article, error) { - entries, err := os.ReadDir(registry.RootPath) - if err != nil { - return nil, err - } - - articles := []*Article{} - - for _, entry := range entries { - if !entry.IsDir() { - name := strings.TrimRight(entry.Name(), ".md") - article, err := registry.Load(name) - if err != nil { - return nil, err - } - - articles = append(articles, article) - } - } - - sort.Slice(articles, func(i, j int) bool { - return articles[i].PublishDate.After(articles[j].PublishDate) - }) - - return articles, nil -} - -func NewArticleRenderer(rootPath string) *ArticleRenderer { - return &ArticleRenderer{ - rootPath, - } -} diff --git a/articles/article.go b/articles/article.go new file mode 100644 index 0000000..d914ff9 --- /dev/null +++ b/articles/article.go @@ -0,0 +1,113 @@ +package articles + +import ( + "bytes" + "os" + "strings" + "time" + + "github.com/phc-dm/phc-server/config" + "gopkg.in/yaml.v3" +) + +type Article struct { + Id string + Title string + Description string + Tags []string + PublishDate time.Time + + ArticlePath string + markdownSource string + renderedHTML string +} + +func (article *Article) HasTag(tag string) bool { + for _, t := range article.Tags { + if t == tag { + return true + } + } + + return false +} + +func NewArticle(articlePath string) (*Article, error) { + article := &Article{ + ArticlePath: articlePath, + } + + err := article.load() + if err != nil { + return nil, err + } + + return article, nil +} + +func trimAll(vs []string) []string { + r := []string{} + for _, v := range vs { + r = append(r, strings.TrimSpace(v)) + } + return r +} + +func (article *Article) load() error { + fileBytes, err := os.ReadFile(article.ArticlePath) + if err != nil { + return err + } + + source := string(fileBytes) + + // TODO: Ehm bugia pare che esista "https://github.com/yuin/goldmark-meta" però non penso valga la pena aggiungerlo + parts := strings.SplitN(source, "---", 3)[1:] + + frontMatterSource := parts[0] + markdownSource := parts[1] + + var frontMatter struct { + Id string `yaml:"id"` + Title string `yaml:"title"` + Description string `yaml:"description"` + Tags string `yaml:"tags"` + PublishDate string `yaml:"publish_date"` + } + if err := yaml.Unmarshal([]byte(frontMatterSource), &frontMatter); err != nil { + return err + } + + publishDate, err := time.Parse("2006/01/02 15:04", frontMatter.PublishDate) + if err != nil { + return err + } + + article.Id = frontMatter.Id + article.Title = frontMatter.Title + article.Description = frontMatter.Description + article.Tags = trimAll(strings.Split(frontMatter.Tags, ",")) + article.PublishDate = publishDate + + article.markdownSource = markdownSource + article.renderedHTML = "" + + return nil +} + +func (article *Article) Render() (string, error) { + if config.Mode == "development" { + article.load() + } + + if article.renderedHTML == "" { + var buf bytes.Buffer + if err := articleMarkdown.Convert([]byte(article.markdownSource), &buf); err != nil { + return "", err + } + + article.renderedHTML = buf.String() + } + + return article.renderedHTML, nil +} diff --git a/articles/articles.go b/articles/articles.go new file mode 100644 index 0000000..46140e5 --- /dev/null +++ b/articles/articles.go @@ -0,0 +1,77 @@ +package articles + +import ( + "fmt" + "os" + "path" + "sort" +) + +type Registry struct { + RootPath string + ArticleCache map[string]*Article +} + +func NewRegistry(rootPath string) *Registry { + return &Registry{ + rootPath, + map[string]*Article{}, + } +} + +func (registry *Registry) loadArticles() error { + entries, err := os.ReadDir(registry.RootPath) + if err != nil { + return err + } + + for _, entry := range entries { + if !entry.IsDir() { + article, err := NewArticle(path.Join(registry.RootPath, entry.Name())) + if err != nil { + return err + } + + registry.ArticleCache[article.Id] = article + } + } + + return nil +} + +func (registry *Registry) GetArticle(id string) (*Article, error) { + article, present := registry.ArticleCache[id] + if !present { + err := registry.loadArticles() + if err != nil { + return nil, err + } + + article, present := registry.ArticleCache[id] + if !present { + return nil, fmt.Errorf(`no article with id "%s"`, id) + } + + return article, nil + } + + return article, nil +} + +func (registry *Registry) GetArticles() ([]*Article, error) { + err := registry.loadArticles() + if err != nil { + return nil, err + } + + articles := []*Article{} + for _, article := range registry.ArticleCache { + articles = append(articles, article) + } + + sort.Slice(articles, func(i, j int) bool { + return articles[i].PublishDate.After(articles[j].PublishDate) + }) + + return articles, nil +} diff --git a/articles/markdown.go b/articles/markdown.go new file mode 100644 index 0000000..f081395 --- /dev/null +++ b/articles/markdown.go @@ -0,0 +1,60 @@ +package articles + +import ( + "fmt" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/util" + + chromahtml "github.com/alecthomas/chroma/formatters/html" + mathjax "github.com/litao91/goldmark-mathjax" + highlighting "github.com/yuin/goldmark-highlighting" +) + +var articleMarkdown goldmark.Markdown + +// https://github.com/yuin/goldmark-highlighting/blob/9216f9c5aa010c549cc9fc92bb2593ab299f90d4/highlighting_test.go#L27 +func customCodeBlockWrapper(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) { + lang, ok := c.Language() + if entering { + if ok { + w.WriteString(fmt.Sprintf(`
`, lang))
+			return
+		}
+		w.WriteString("
")
+	} else {
+		if ok {
+			w.WriteString("
") + return + } + w.WriteString("
") + } +} + +func init() { + articleMarkdown = goldmark.New( + goldmark.WithExtensions( + extension.GFM, + extension.Typographer, + // Questo pacchetto ha un nome stupido perché in realtà si occupa solo del parsing lato server del Markdown mentre lato client usiamo KaTeX. + mathjax.NewMathJax(), + highlighting.NewHighlighting( + highlighting.WithStyle("github"), + highlighting.WithWrapperRenderer(customCodeBlockWrapper), + highlighting.WithFormatOptions( + chromahtml.PreventSurroundingPre(true), + ), + ), + ), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithHardWraps(), + html.WithXHTML(), + ), + ) +} diff --git a/main.go b/main.go index 8360911..2965146 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" + "github.com/phc-dm/phc-server/articles" "github.com/phc-dm/phc-server/config" "github.com/phc-dm/phc-server/templates" "github.com/phc-dm/phc-server/util" @@ -31,7 +32,7 @@ func main() { templates.File("./views/base.html"), templates.Pattern("./views/partials/*.html"), ) - newsArticlesRegistry := NewArticleRenderer("./news") + newsArticlesRegistry := articles.NewRegistry("./news") // Routes @@ -71,7 +72,7 @@ func main() { }) r.Get("/news", func(w http.ResponseWriter, r *http.Request) { - articles, err := newsArticlesRegistry.LoadAll() + articles, err := newsArticlesRegistry.GetArticles() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -86,9 +87,9 @@ func main() { }) r.Get("/news/{article}", func(w http.ResponseWriter, r *http.Request) { - articleName := chi.URLParam(r, "article") + articleID := chi.URLParam(r, "article") - article, err := newsArticlesRegistry.Load(articleName) + article, err := newsArticlesRegistry.GetArticle(articleID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/news/2021-12-22-notizia-1.md b/news/2021-12-22-notizia-1.md index a6c63b0..d7660f4 100644 --- a/news/2021-12-22-notizia-1.md +++ b/news/2021-12-22-notizia-1.md @@ -1,4 +1,5 @@ --- +id: 2021-12-22-notizia-1 title: "Notizia 1" tags: important, prova, test, foo, bar publish_date: 2021/12/22 22:00 diff --git a/news/2021-12-23-notizia-2.md b/news/2021-12-23-notizia-2.md index 5b970f9..64523ce 100644 --- a/news/2021-12-23-notizia-2.md +++ b/news/2021-12-23-notizia-2.md @@ -1,4 +1,5 @@ --- +id: 2021-12-23-notizia-2 title: "Notizia 2" tags: prova, test, foo, bar publish_date: 2021/12/23 22:00 diff --git a/news/2021-12-24-notizia-3.md b/news/2021-12-24-notizia-3.md index ee576fb..8132c17 100644 --- a/news/2021-12-24-notizia-3.md +++ b/news/2021-12-24-notizia-3.md @@ -1,4 +1,5 @@ --- +id: 2021-12-24-notizia-3 title: "Notizia 3" tags: prova, test, foo, bar publish_date: 2021/12/24 18:00