refactor: changed the structure of things

main
Antonio De Lucreziis 2 years ago
parent 81490ac758
commit 0ee9389910

@ -2,6 +2,14 @@
Cabret is a yaml based static site generator, ideally with the same features as Hugo but with a simpler model. Here is a simple example of a _Cabretfile.yaml_ Cabret is a yaml based static site generator, ideally with the same features as Hugo but with a simpler model. Here is a simple example of a _Cabretfile.yaml_
## Build
```bash shell
$ go build -v -o ./bin/cabret ./cmd/cabret
```
## Introduction
```yaml ```yaml
build: build:
- pipeline: - pipeline:

@ -1,7 +1,6 @@
package cabret package cabret
import ( import (
"fmt"
"log" "log"
) )
@ -35,7 +34,7 @@ type Content struct {
} }
type Operation interface { type Operation interface {
Load(config map[string]any) error Configure(config map[string]any) error
} }
type ListOperation interface { type ListOperation interface {
@ -47,28 +46,3 @@ type ItemOperation interface {
Operation Operation
ProcessItem(content Content) (*Content, error) ProcessItem(content Content) (*Content, error)
} }
func ProcessOperation(op Operation, inputs []Content) ([]Content, error) {
switch op := op.(type) {
case ListOperation:
return op.ProcessList(inputs)
case ItemOperation:
outputs := []Content{}
for _, item := range inputs {
result, err := op.ProcessItem(item)
if err != nil {
return nil, err
}
// skip terminal operations
if result == nil {
continue
}
outputs = append(outputs, *result)
}
return outputs, nil
default:
return nil, fmt.Errorf(`invalid operation type %T`, op)
}
}

@ -4,7 +4,7 @@ import (
"log" "log"
"github.com/aziis98/cabret/config" "github.com/aziis98/cabret/config"
"github.com/aziis98/cabret/exec" "github.com/aziis98/cabret/runner"
) )
func main() { func main() {
@ -15,9 +15,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
// repr.Println(cabretfile) if err := runner.RunConfig(cabretfile); err != nil {
if err := exec.Execute(cabretfile); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

@ -5,8 +5,7 @@
<ul> <ul>
{{ range .Items }} {{ range .Items }}
<li> <li>
<p>{{ .Metadata.Title }}</p> <a href="/posts/{{ .Metadata.MatchResult.id }}">{{ .Metadata.Title }}</a>
<pre><code>{{ .Metadata.MatchResult }}</code></pre>
</li> </li>
{{ end }} {{ end }}
</ul> </ul>

@ -1,22 +0,0 @@
package exec
import (
"github.com/aziis98/cabret"
"github.com/aziis98/cabret/config"
"github.com/aziis98/cabret/pipeline"
)
func Execute(cfg *config.Cabretfile) error {
for _, p := range cfg.Build {
ops, err := pipeline.Parse(p)
if err != nil {
return err
}
if _, err := pipeline.Process([]cabret.Content{}, ops); err != nil {
return err
}
}
return nil
}

@ -37,7 +37,7 @@ func getKey[T any](m map[string]any, key string, defaultValue ...T) (T, error) {
return value, nil return value, nil
} }
func (op *Categorize) Load(config map[string]any) error { func (op *Categorize) Configure(config map[string]any) error {
var err error var err error
op.Key, err = getKey[string](config, "key") op.Key, err = getKey[string](config, "key")

@ -19,7 +19,7 @@ type Frontmatter struct {
Options map[string]any Options map[string]any
} }
func (op *Frontmatter) Load(config map[string]any) error { func (op *Frontmatter) Configure(config map[string]any) error {
return nil return nil
} }

@ -24,7 +24,7 @@ type Layout struct {
TemplatePatterns []string TemplatePatterns []string
} }
func (op *Layout) Load(config map[string]any) error { func (op *Layout) Configure(config map[string]any) error {
if v, ok := config[ShortFormValueKey]; ok { if v, ok := config[ShortFormValueKey]; ok {
globPatternsStr, ok := v.(string) globPatternsStr, ok := v.(string)
if !ok { if !ok {

@ -20,7 +20,7 @@ type Markdown struct {
Options map[string]any Options map[string]any
} }
func (op *Markdown) Load(config map[string]any) error { func (op *Markdown) Configure(config map[string]any) error {
return nil return nil
} }

@ -19,16 +19,11 @@ func registerType(name string, op cabret.Operation) {
registry[name] = typ registry[name] = typ
} }
func Build(name string, options map[string]any) (cabret.Operation, error) { func NewWithName(name string) (cabret.Operation, error) {
typ, ok := registry[name] typ, ok := registry[name]
if !ok { if !ok {
return nil, fmt.Errorf(`no registered operation named %q`, name) return nil, fmt.Errorf(`no registered operation named %q`, name)
} }
op := reflect.New(typ).Interface().(cabret.Operation) return reflect.New(typ).Interface().(cabret.Operation), nil
if err := op.Load(options); err != nil {
return nil, err
}
return op, nil
} }

@ -1,18 +0,0 @@
package operation
import (
"testing"
"gotest.tools/assert"
)
func TestBuild(t *testing.T) {
op, err := Build("categorize", map[string]any{
"key": "tags",
})
assert.NilError(t, err)
assert.DeepEqual(t, op, &Categorize{
Key: "tags",
})
}

@ -20,20 +20,30 @@ type Source struct {
Patterns []string Patterns []string
} }
func (op *Source) Load(config map[string]any) error { func (op *Source) Configure(config map[string]any) error {
if v, ok := config[ShortFormValueKey]; ok { if v, ok := config["source"]; ok {
pattern, ok := v.(string) pattern, ok := v.(string)
if !ok { if !ok {
return fmt.Errorf(`expected pattern but got "%v" of type %T`, v, v) return fmt.Errorf(`expected a path pattern but got "%v" of type %T`, v, v)
} }
op.Patterns = []string{pattern} op.Patterns = []string{pattern}
return nil return nil
} }
if v, ok := config["paths"]; ok { if v, ok := config["paths"]; ok {
patterns, ok := v.([]string) aPatterns, ok := v.([]any)
if !ok { if !ok {
return fmt.Errorf(`expected list of patterns but got "%v" of type %T`, v, v) return fmt.Errorf(`expected a list of path patterns but got "%v" of type %T`, v, v)
}
patterns := []string{}
for _, aPat := range aPatterns {
p, ok := aPat.(string)
if !ok {
return fmt.Errorf(`expected a string but got "%v" of type %T`, aPat, aPat)
}
patterns = append(patterns, p)
} }
op.Patterns = patterns op.Patterns = patterns

@ -0,0 +1,68 @@
package operation_test
import (
"testing"
"github.com/aziis98/cabret/operation"
"github.com/aziis98/cabret/util"
"gotest.tools/assert"
)
func TestSourceShortForm(t *testing.T) {
t.Run("correct usage", func(t *testing.T) {
op := &operation.Source{}
err := op.Configure(util.ParseYAML(`
source: foo/bar/baz.txt
`))
assert.NilError(t, err)
assert.DeepEqual(t, op, &operation.Source{
Patterns: []string{
"foo/bar/baz.txt",
},
})
})
t.Run("wrong usage", func(t *testing.T) {
op := &operation.Source{}
err := op.Configure(util.ParseYAML(`
source: 123
`))
assert.Error(t, err, `expected a path pattern but got "123" of type int`)
})
}
func TestSourceWithPaths(t *testing.T) {
t.Run("correct usage", func(t *testing.T) {
op := &operation.Source{}
err := op.Configure(util.ParseYAML(`
use: source
paths:
- foo/bar/baz-1.txt
- foo/bar/baz-2.txt
- foo/bar/baz-3.txt
`))
assert.NilError(t, err)
assert.DeepEqual(t, op, &operation.Source{
Patterns: []string{
"foo/bar/baz-1.txt",
"foo/bar/baz-2.txt",
"foo/bar/baz-3.txt",
},
})
})
t.Run("wrong usage", func(t *testing.T) {
op := &operation.Source{}
err := op.Configure(util.ParseYAML(`
use: source
paths:
- foo/bar/baz-1.txt
- foo/bar/baz-2.txt
- 123
`))
assert.Error(t, err, `expected a string but got "123" of type int`)
})
}

@ -22,11 +22,11 @@ type Target struct {
PathTemplate string PathTemplate string
} }
func (op *Target) Load(config map[string]any) error { func (op *Target) Configure(config map[string]any) error {
if v, ok := config[ShortFormValueKey]; ok { if v, ok := config["target"]; ok {
template, ok := v.(string) template, ok := v.(string)
if !ok { if !ok {
return fmt.Errorf(`expected pattern but got "%v" of type %T`, v, v) return fmt.Errorf(`expected a path template but got "%v" of type %T`, v, v)
} }
op.PathTemplate = template op.PathTemplate = template

@ -0,0 +1,31 @@
package operation_test
import (
"testing"
"github.com/aziis98/cabret/operation"
"github.com/aziis98/cabret/util"
"gotest.tools/assert"
)
func TestTargetShortForm(t *testing.T) {
t.Run("correct usage", func(t *testing.T) {
op := &operation.Target{}
err := op.Configure(util.ParseYAML(`
target: foo/bar/baz.txt
`))
assert.NilError(t, err)
assert.DeepEqual(t, op, &operation.Target{
PathTemplate: "foo/bar/baz.txt",
})
})
t.Run("wrong usage", func(t *testing.T) {
op := &operation.Target{}
err := op.Configure(util.ParseYAML(`
target: 123
`))
assert.Error(t, err, `expected a path template but got "123" of type int`)
})
}

@ -1,4 +1,4 @@
package pipeline package parse
import ( import (
"fmt" "fmt"
@ -18,7 +18,7 @@ func switchMap(m map[string]any, v *any) func(k string) bool {
} }
} }
func Parse(p config.Pipeline) ([]cabret.Operation, error) { func ParsePipeline(p config.Pipeline) ([]cabret.Operation, error) {
ops := []cabret.Operation{} ops := []cabret.Operation{}
for _, opConfig := range p.Pipeline { for _, opConfig := range p.Pipeline {
@ -33,8 +33,9 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) {
} }
opConfig[operation.ShortFormValueKey] = value opConfig[operation.ShortFormValueKey] = value
op := &operation.Source{}
if err := op.Load(opConfig); err != nil { op, err := ParseOperation("source", opConfig)
if err != nil {
return nil, err return nil, err
} }
@ -47,8 +48,9 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) {
} }
opConfig[operation.ShortFormValueKey] = value opConfig[operation.ShortFormValueKey] = value
op := &operation.Target{}
if err := op.Load(opConfig); err != nil { op, err := ParseOperation("target", opConfig)
if err != nil {
return nil, err return nil, err
} }
@ -60,7 +62,7 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) {
return nil, fmt.Errorf(`expected string but got "%v" of type %T`, v, v) return nil, fmt.Errorf(`expected string but got "%v" of type %T`, v, v)
} }
op, err := operation.Build(name, opConfig) op, err := ParseOperation(name, opConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -75,14 +77,15 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) {
return ops, nil return ops, nil
} }
func Process(contents []cabret.Content, ops []cabret.Operation) ([]cabret.Content, error) { func ParseOperation(name string, options map[string]any) (cabret.Operation, error) {
for _, op := range ops { op, err := operation.NewWithName(name)
var err error
contents, err = cabret.ProcessOperation(op, contents)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := op.Configure(options); err != nil {
return nil, err
} }
return contents, nil return op, nil
} }

@ -0,0 +1,61 @@
package runner
import (
"fmt"
"github.com/aziis98/cabret"
"github.com/aziis98/cabret/config"
"github.com/aziis98/cabret/parse"
)
func RunConfig(cfg *config.Cabretfile) error {
for _, p := range cfg.Build {
ops, err := parse.ParsePipeline(p)
if err != nil {
return err
}
if _, err := RunPipeline([]cabret.Content{}, ops); err != nil {
return err
}
}
return nil
}
func RunPipeline(contents []cabret.Content, ops []cabret.Operation) ([]cabret.Content, error) {
for _, op := range ops {
var err error
contents, err = RunOperation(op, contents)
if err != nil {
return nil, err
}
}
return contents, nil
}
func RunOperation(op cabret.Operation, inputs []cabret.Content) ([]cabret.Content, error) {
switch op := op.(type) {
case cabret.ListOperation:
return op.ProcessList(inputs)
case cabret.ItemOperation:
outputs := []cabret.Content{}
for _, item := range inputs {
result, err := op.ProcessItem(item)
if err != nil {
return nil, err
}
// skip terminal operations
if result == nil {
continue
}
outputs = append(outputs, *result)
}
return outputs, nil
default:
return nil, fmt.Errorf(`invalid operation type %T`, op)
}
}

@ -1,5 +1,12 @@
package util package util
import (
"strings"
"github.com/alecthomas/repr"
"gopkg.in/yaml.v2"
)
func CloneMap[K comparable, V any](m1 map[K]V) map[K]V { func CloneMap[K comparable, V any](m1 map[K]V) map[K]V {
m2 := map[K]V{} m2 := map[K]V{}
for k, v := range m1 { for k, v := range m1 {
@ -7,3 +14,29 @@ func CloneMap[K comparable, V any](m1 map[K]V) map[K]V {
} }
return m2 return m2
} }
func Dedent(s string) string {
lines := strings.Split(strings.TrimLeft(s, "\n"), "\n")
repr.Println(lines)
k := len(lines[0]) - len(strings.TrimLeft(lines[0], "\t "))
for i, line := range lines {
if k <= len(line) {
lines[i] = line[k:]
} else {
lines[i] = ""
}
}
return strings.Join(lines, "\n")
}
func ParseYAML(multiline string) map[string]any {
var m map[string]any
if err := yaml.Unmarshal([]byte(Dedent(multiline)), &m); err != nil {
panic(err)
}
return m
}

Loading…
Cancel
Save