diff --git a/README.md b/README.md index d38d7f5..f9989eb 100644 --- a/README.md +++ b/README.md @@ -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_ +## Build + +```bash shell +$ go build -v -o ./bin/cabret ./cmd/cabret +``` + +## Introduction + ```yaml build: - pipeline: diff --git a/cabret.go b/cabret.go index 2b92eb1..8c870ad 100644 --- a/cabret.go +++ b/cabret.go @@ -1,7 +1,6 @@ package cabret import ( - "fmt" "log" ) @@ -35,7 +34,7 @@ type Content struct { } type Operation interface { - Load(config map[string]any) error + Configure(config map[string]any) error } type ListOperation interface { @@ -47,28 +46,3 @@ type ItemOperation interface { Operation 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) - } -} diff --git a/cmd/cabret/main.go b/cmd/cabret/main.go index 3cdcde0..101cd22 100644 --- a/cmd/cabret/main.go +++ b/cmd/cabret/main.go @@ -4,7 +4,7 @@ import ( "log" "github.com/aziis98/cabret/config" - "github.com/aziis98/cabret/exec" + "github.com/aziis98/cabret/runner" ) func main() { @@ -15,9 +15,7 @@ func main() { log.Fatal(err) } - // repr.Println(cabretfile) - - if err := exec.Execute(cabretfile); err != nil { + if err := runner.RunConfig(cabretfile); err != nil { log.Fatal(err) } } diff --git a/examples/basic/layouts/tag.html b/examples/basic/layouts/tag.html index 3aff810..af22cb6 100644 --- a/examples/basic/layouts/tag.html +++ b/examples/basic/layouts/tag.html @@ -5,8 +5,7 @@ diff --git a/exec/exec.go b/exec/exec.go deleted file mode 100644 index b622da8..0000000 --- a/exec/exec.go +++ /dev/null @@ -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 -} diff --git a/operation/categorize.go b/operation/categorize.go index bf22ba2..2af5b70 100644 --- a/operation/categorize.go +++ b/operation/categorize.go @@ -37,7 +37,7 @@ func getKey[T any](m map[string]any, key string, defaultValue ...T) (T, error) { return value, nil } -func (op *Categorize) Load(config map[string]any) error { +func (op *Categorize) Configure(config map[string]any) error { var err error op.Key, err = getKey[string](config, "key") diff --git a/operation/frontmatter.go b/operation/frontmatter.go index 97a9587..6674d77 100644 --- a/operation/frontmatter.go +++ b/operation/frontmatter.go @@ -19,7 +19,7 @@ type Frontmatter struct { Options map[string]any } -func (op *Frontmatter) Load(config map[string]any) error { +func (op *Frontmatter) Configure(config map[string]any) error { return nil } diff --git a/operation/layout.go b/operation/layout.go index bbb1a2a..41f0f72 100644 --- a/operation/layout.go +++ b/operation/layout.go @@ -24,7 +24,7 @@ type Layout struct { 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 { globPatternsStr, ok := v.(string) if !ok { diff --git a/operation/markdown.go b/operation/markdown.go index 5477ab1..2a69e10 100644 --- a/operation/markdown.go +++ b/operation/markdown.go @@ -20,7 +20,7 @@ type Markdown struct { Options map[string]any } -func (op *Markdown) Load(config map[string]any) error { +func (op *Markdown) Configure(config map[string]any) error { return nil } diff --git a/operation/registry.go b/operation/registry.go index 74f90e8..c2cbe97 100644 --- a/operation/registry.go +++ b/operation/registry.go @@ -19,16 +19,11 @@ func registerType(name string, op cabret.Operation) { 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] if !ok { return nil, fmt.Errorf(`no registered operation named %q`, name) } - op := reflect.New(typ).Interface().(cabret.Operation) - if err := op.Load(options); err != nil { - return nil, err - } - - return op, nil + return reflect.New(typ).Interface().(cabret.Operation), nil } diff --git a/operation/registry_test.go b/operation/registry_test.go deleted file mode 100644 index c73bd65..0000000 --- a/operation/registry_test.go +++ /dev/null @@ -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", - }) -} diff --git a/operation/source.go b/operation/source.go index 3b9fcaf..94fecc0 100644 --- a/operation/source.go +++ b/operation/source.go @@ -20,20 +20,30 @@ type Source struct { Patterns []string } -func (op *Source) Load(config map[string]any) error { - if v, ok := config[ShortFormValueKey]; ok { +func (op *Source) Configure(config map[string]any) error { + if v, ok := config["source"]; ok { pattern, ok := v.(string) 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} return nil } if v, ok := config["paths"]; ok { - patterns, ok := v.([]string) + aPatterns, ok := v.([]any) 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 diff --git a/operation/source_test.go b/operation/source_test.go new file mode 100644 index 0000000..ae00fe9 --- /dev/null +++ b/operation/source_test.go @@ -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`) + }) +} diff --git a/operation/target.go b/operation/target.go index baf41ef..954a486 100644 --- a/operation/target.go +++ b/operation/target.go @@ -22,11 +22,11 @@ type Target struct { PathTemplate string } -func (op *Target) Load(config map[string]any) error { - if v, ok := config[ShortFormValueKey]; ok { +func (op *Target) Configure(config map[string]any) error { + if v, ok := config["target"]; ok { template, ok := v.(string) 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 diff --git a/operation/target_test.go b/operation/target_test.go new file mode 100644 index 0000000..6fed55f --- /dev/null +++ b/operation/target_test.go @@ -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`) + }) +} diff --git a/pipeline/pipeline.go b/parse/parse.go similarity index 70% rename from pipeline/pipeline.go rename to parse/parse.go index a19f879..2682b29 100644 --- a/pipeline/pipeline.go +++ b/parse/parse.go @@ -1,4 +1,4 @@ -package pipeline +package parse import ( "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{} for _, opConfig := range p.Pipeline { @@ -33,8 +33,9 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) { } 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 } @@ -47,8 +48,9 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) { } 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 } @@ -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) } - op, err := operation.Build(name, opConfig) + op, err := ParseOperation(name, opConfig) if err != nil { return nil, err } @@ -75,14 +77,15 @@ func Parse(p config.Pipeline) ([]cabret.Operation, error) { return ops, nil } -func Process(contents []cabret.Content, ops []cabret.Operation) ([]cabret.Content, error) { - for _, op := range ops { - var err error - contents, err = cabret.ProcessOperation(op, contents) - if err != nil { - return nil, err - } +func ParseOperation(name string, options map[string]any) (cabret.Operation, error) { + op, err := operation.NewWithName(name) + if err != nil { + return nil, err + } + + if err := op.Configure(options); err != nil { + return nil, err } - return contents, nil + return op, nil } diff --git a/runner/runner.go b/runner/runner.go new file mode 100644 index 0000000..0eb4731 --- /dev/null +++ b/runner/runner.go @@ -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) + } +} diff --git a/util/util.go b/util/util.go index 0cb8ba7..53c1e56 100644 --- a/util/util.go +++ b/util/util.go @@ -1,5 +1,12 @@ 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 { m2 := map[K]V{} for k, v := range m1 { @@ -7,3 +14,29 @@ func CloneMap[K comparable, V any](m1 map[K]V) map[K]V { } 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 +}