Refactor: Isolata in un pacchetto autonomo la struct che gestisce il registro degli articoli
parent
447e87b691
commit
280c3aaef4
@ -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(`<pre class="%s"><code>`, lang))
|
||||
return
|
||||
}
|
||||
w.WriteString("<pre><code>")
|
||||
} else {
|
||||
if ok {
|
||||
w.WriteString("</pre></code>")
|
||||
return
|
||||
}
|
||||
w.WriteString("</pre></code>")
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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(`<pre class="%s"><code>`, lang))
|
||||
return
|
||||
}
|
||||
w.WriteString("<pre><code>")
|
||||
} else {
|
||||
if ok {
|
||||
w.WriteString("</pre></code>")
|
||||
return
|
||||
}
|
||||
w.WriteString("</pre></code>")
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
),
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue