add: pipeline categorization by metadata key

main
Antonio De Lucreziis 2 years ago
parent 46715cf033
commit 165fffe91b

@ -63,8 +63,7 @@ build:
to: end to: end
- use: chunk # paginate items - use: chunk # paginate items
size: 10 size: 10
pipeline: # this pipeline gets called with every [size * n, size * n + size] range of items - use: layout # aggregate this items chunk in a single item
- use: template # aggregate this items chunk in a single item
path: layouts/list.html path: layouts/list.html
- use: layout - use: layout
path: layouts/base.html path: layouts/base.html
@ -75,8 +74,7 @@ build:
- use: frontmatter - use: frontmatter
- use: categorize - use: categorize
key: tags # each post contains a metadata field called "tags" key: tags # each post contains a metadata field called "tags"
pipeline: # this pipeline gets called with all posts relative to one category - use: layout
- use: template
path: layouts/tag.html path: layouts/tag.html
- use: layout - use: layout
path: layouts/base.html path: layouts/base.html

@ -10,5 +10,14 @@ build:
- use: layout - use: layout
path: layouts/base.html path: layouts/base.html
- target: dist/posts/{id}/index.html - target: dist/posts/{id}/index.html
- pipeline:
- source: posts/{id}.md
- use: frontmatter
- use: categorize
key: tags
- use: layout
path: layouts/tag.html
- use: layout
path: layouts/base.html
- target: dist/tags/{{ .Category }}/index.html

@ -7,6 +7,6 @@
<title>{{- if .Title -}}{{ .Title }} | {{ else }}{{ end -}} My Blog</title> <title>{{- if .Title -}}{{ .Title }} | {{ else }}{{ end -}} My Blog</title>
</head> </head>
<body> <body>
{{- .Content -}} {{ .Content }}
</body> </body>
</html> </html>

@ -0,0 +1,12 @@
<h1>Tag #{{ .Category }}</h1>
<p>Here are the posts with this tag</p>
<ul>
{{ range .Items }}
<li>
<p>{{ .Metadata.Title }}</p>
<pre><code>{{ .Metadata.MatchResult }}</code></pre>
</li>
{{ end }}
</ul>

@ -2,6 +2,7 @@
title: First Post title: First Post
description: Lorem ipsum dolor sit amet consectetur adipisicing elit. description: Lorem ipsum dolor sit amet consectetur adipisicing elit.
publish_date: 2022-12-22 11:00 publish_date: 2022-12-22 11:00
tags: [a, b]
--- ---
# First Post # First Post

@ -2,6 +2,7 @@
title: Second Post title: Second Post
description: Lorem ipsum dolor sit amet consectetur adipisicing elit. description: Lorem ipsum dolor sit amet consectetur adipisicing elit.
publish_date: 2022-12-22 11:00 publish_date: 2022-12-22 11:00
tags: [b, c]
--- ---
# Second Post # Second Post

@ -2,6 +2,7 @@
title: Third Post title: Third Post
description: Lorem ipsum dolor sit amet consectetur adipisicing elit. description: Lorem ipsum dolor sit amet consectetur adipisicing elit.
publish_date: 2022-12-22 11:00 publish_date: 2022-12-22 11:00
tags: [a]
--- ---
# Third Post # Third Post

@ -2,8 +2,10 @@ package operation
import ( import (
"fmt" "fmt"
"log"
"github.com/aziis98/cabret" "github.com/aziis98/cabret"
"github.com/iancoleman/strcase"
) )
func init() { func init() {
@ -12,9 +14,6 @@ func init() {
type Categorize struct { type Categorize struct {
Key string Key string
// Operation to be executed for each category
Operation cabret.Operation
} }
func (op *Categorize) Load(config map[string]any) error { func (op *Categorize) Load(config map[string]any) error {
@ -34,6 +33,47 @@ func (op *Categorize) Load(config map[string]any) error {
return nil return nil
} }
func (op *Categorize) Process(content cabret.Content) (*cabret.Content, error) { func (op *Categorize) ProcessList(contents []cabret.Content) ([]cabret.Content, error) {
return nil, nil key := strcase.ToCamel(op.Key)
categories := map[string][]cabret.Content{}
for _, content := range contents {
v, ok := content.Metadata[key]
if !ok {
log.Printf(`[operation.Categorize] item has no categories`)
continue
}
cats, ok := v.([]any)
if !ok {
return nil, fmt.Errorf(`expected a list but got "%v" of type %T`, v, v)
}
for _, v := range cats {
cat, ok := v.(string)
if !ok {
return nil, fmt.Errorf(`expected a string but got "%v" of type %T`, v, v)
}
log.Printf(`[operation.Categorize] found item with category "%s"`, cat)
categories[cat] = append(categories[cat], content)
}
}
result := []cabret.Content{}
for name, contents := range categories {
log.Printf(`[operation.Categorize] generating category with %v item(s)`, len(contents))
result = append(result, cabret.Content{
Type: "metadata-only",
Metadata: cabret.Map{
"Category": name,
"Items": contents,
},
})
}
return result, nil
} }

@ -0,0 +1,46 @@
package operation
import (
"io"
"log"
"github.com/aziis98/cabret"
"github.com/iancoleman/strcase"
"github.com/yuin/goldmark"
meta "github.com/yuin/goldmark-meta"
"github.com/yuin/goldmark/parser"
)
func init() {
registerType("frontmatter", &Frontmatter{})
}
type Frontmatter struct {
Options map[string]any
}
func (op *Frontmatter) Load(config map[string]any) error {
return nil
}
func (op *Frontmatter) ProcessItem(content cabret.Content) (*cabret.Content, error) {
md := goldmark.New(
goldmark.WithExtensions(
meta.Meta,
),
)
log.Printf(`[operation.Frontmatter] reading frontmatter`)
context := parser.NewContext()
if err := md.Convert(content.Data, io.Discard, parser.WithContext(context)); err != nil {
panic(err)
}
frontmatter := meta.Get(context)
for k, v := range frontmatter {
content.Metadata[strcase.ToCamel(k)] = v
}
return &content, nil
}

@ -97,6 +97,7 @@ func (op Layout) ProcessItem(content cabret.Content) (*cabret.Content, error) {
return nil, err return nil, err
} }
content.Type = mime.TypeByExtension(filepath.Ext(tmplFiles[0]))
content.Data = data content.Data = data
return &content, nil return &content, nil
} }

@ -1,9 +1,12 @@
package operation package operation
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
txtTemplate "text/template"
gopath "path" gopath "path"
@ -34,12 +37,32 @@ func (op *Target) Load(config map[string]any) error {
} }
func (op Target) ProcessItem(c cabret.Content) (*cabret.Content, error) { func (op Target) ProcessItem(c cabret.Content) (*cabret.Content, error) {
mr, ok := c.Metadata[cabret.MatchResultKey].(map[string]string) log.Printf(`[operation.Target] expanding pattern "%s"`, op.PathTemplate)
target := op.PathTemplate
if strings.Contains(target, "{{") {
t, err := txtTemplate.New("path").Parse(target)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := t.Execute(&buf, c.Metadata); err != nil {
return nil, err
}
target = buf.String()
}
if v, ok := c.Metadata[cabret.MatchResultKey]; ok {
context, ok := v.(map[string]string)
if !ok { if !ok {
return nil, fmt.Errorf(`invalid match result type %T`, c.Metadata[cabret.MatchResultKey]) return nil, fmt.Errorf(`invalid match result type %T`, c.Metadata[cabret.MatchResultKey])
} }
target := path.RenderTemplate(op.PathTemplate, mr) target = path.RenderTemplate(target, context)
}
log.Printf(`[operation.Target] writing "%s"`, target) log.Printf(`[operation.Target] writing "%s"`, target)

@ -0,0 +1,97 @@
package operation
// import (
// "fmt"
// "log"
// "mime"
// "path/filepath"
// "strings"
// "github.com/aziis98/cabret"
// "github.com/aziis98/cabret/operation/template"
// )
// func init() {
// registerType("template", &Template{})
// }
// type Template struct {
// // TemplatePatterns is a list of glob patterns of templates that will be loaded
// TemplatePatterns []string
// }
// func (op *Template) Load(config map[string]any) error {
// if v, ok := config[ShortFormValueKey]; ok {
// globPatternsStr, ok := v.(string)
// if !ok {
// return fmt.Errorf(`expected a comma separated list of glob patterns but got "%v" of type %T`, v, v)
// }
// globPatterns := strings.Split(globPatternsStr, ",")
// for _, pat := range globPatterns {
// op.TemplatePatterns = append(op.TemplatePatterns, strings.TrimSpace(pat))
// }
// return nil
// }
// if v, ok := config["paths"]; ok {
// globPatterns, ok := v.([]string)
// if !ok {
// return fmt.Errorf(`expected a list of glob patterns but got "%v" of type %T`, v, v)
// }
// for _, pat := range globPatterns {
// op.TemplatePatterns = append(op.TemplatePatterns, strings.TrimSpace(pat))
// }
// return nil
// }
// if v, ok := config["path"]; ok {
// globPatternStr, ok := v.(string)
// if !ok {
// return fmt.Errorf(`expected a glob pattern but got "%v" of type %T`, v, v)
// }
// op.TemplatePatterns = []string{strings.TrimSpace(globPatternStr)}
// return nil
// }
// return fmt.Errorf(`invalid config for "template": %#v`, config)
// }
// func (op *Template) ProcessList(contents []cabret.Content) ([]cabret.Content, error) {
// // expand glob patterns
// tmplFiles := []string{}
// for _, pat := range op.TemplatePatterns {
// files, err := filepath.Glob(strings.TrimSpace(pat))
// if err != nil {
// return nil, err
// }
// tmplFiles = append(tmplFiles, files...)
// }
// // create template
// tmpl, err := template.ParseFiles(tmplFiles...)
// if err != nil {
// return nil, err
// }
// log.Printf(`[operation.Layout] rendering into layout "%s"`, strings.Join(op.TemplatePatterns, ", "))
// ctx := map[string]any{}
// ctx["Items"] = contents
// data, err := tmpl.Render(ctx)
// if err != nil {
// return nil, err
// }
// return []cabret.Content{
// {
// Type: mime.TypeByExtension(filepath.Ext(tmplFiles[0])),
// Data: data,
// Metadata: ctx,
// },
// }, nil
// }

@ -5,7 +5,13 @@ import (
goHtmlTemplate "html/template" goHtmlTemplate "html/template"
) )
var _ Template = HtmlTemplate{} func safeHTML(str string) goHtmlTemplate.HTML {
return goHtmlTemplate.HTML(str)
}
var fn = goHtmlTemplate.FuncMap{
"html": safeHTML,
}
type HtmlTemplate struct { type HtmlTemplate struct {
*goHtmlTemplate.Template *goHtmlTemplate.Template
@ -17,6 +23,8 @@ func NewHtmlTemplate(files ...string) (*HtmlTemplate, error) {
return nil, err return nil, err
} }
t.Funcs(fn)
return &HtmlTemplate{t}, nil return &HtmlTemplate{t}, nil
} }

Loading…
Cancel
Save