From 46715cf0334b999349b1f3eac040aaf47140c6ce Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Sat, 31 Dec 2022 15:31:07 +0100 Subject: [PATCH] Rewrite to new yaml format --- .gitignore | 3 + cabret.go | 61 ++++++++----- cmd/cabret/main.go | 8 +- config/config.go | 17 ++-- examples/basic/Cabretfile.yaml | 18 ++-- examples/basic/index.html | 3 +- exec/exec.go | 113 ++----------------------- operation/categorize.go | 39 +++++++++ operation/group.go | 12 --- operation/layout.go | 83 ++++++++++++------ operation/layout/layout.go | 5 -- operation/markdown.go | 13 ++- operation/read.go | 40 --------- operation/registry.go | 34 ++++++++ operation/registry_test.go | 18 ++++ operation/source.go | 88 +++++++++++++++++++ operation/target.go | 27 +++++- operation/{layout => template}/html.go | 15 ++-- operation/template/template.go | 17 ++++ operation/{layout => template}/text.go | 15 ++-- pipeline/pipeline.go | 88 +++++++++++++++++++ 21 files changed, 460 insertions(+), 257 deletions(-) create mode 100644 operation/categorize.go delete mode 100644 operation/group.go delete mode 100644 operation/layout/layout.go delete mode 100644 operation/read.go create mode 100644 operation/registry.go create mode 100644 operation/registry_test.go create mode 100644 operation/source.go rename operation/{layout => template}/html.go (58%) create mode 100644 operation/template/template.go rename operation/{layout => template}/text.go (58%) create mode 100644 pipeline/pipeline.go diff --git a/.gitignore b/.gitignore index 0749f3d..6a54ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +.env *.local* bin/ dist/ + +.vscode/ diff --git a/cabret.go b/cabret.go index f932bd5..2b92eb1 100644 --- a/cabret.go +++ b/cabret.go @@ -1,6 +1,20 @@ package cabret -const MatchResult = "MatchResult" +import ( + "fmt" + "log" +) + +func init() { + log.SetFlags(0) +} + +const MatchResultKey = "MatchResult" + +type MatchResult struct { + File string + Captures map[string]string +} type Map map[string]any @@ -20,32 +34,41 @@ type Content struct { Metadata Map } +type Operation interface { + Load(config map[string]any) error +} + type ListOperation interface { - MapAll(contents []Content) ([]Content, error) + Operation + ProcessList(contents []Content) ([]Content, error) } type ItemOperation interface { - FlatMap(content Content) (*Content, error) + Operation + ProcessItem(content Content) (*Content, error) } -type FlatMapToMapAll struct{ FlatMapOperation } - -func (op FlatMapToMapAll) MapAll(contents []Content) ([]Content, error) { - mapped := []Content{} +func ProcessOperation(op Operation, inputs []Content) ([]Content, error) { + switch op := op.(type) { + case ListOperation: + return op.ProcessList(inputs) - for _, item := range contents { - result, err := op.FlatMap(item) - if err != nil { - return nil, err - } + 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 + // skip terminal operations + if result == nil { + continue + } + outputs = append(outputs, *result) } - - mapped = append(mapped, *result) + return outputs, nil + default: + return nil, fmt.Errorf(`invalid operation type %T`, op) } - - return mapped, nil } diff --git a/cmd/cabret/main.go b/cmd/cabret/main.go index c5d2bb6..3cdcde0 100644 --- a/cmd/cabret/main.go +++ b/cmd/cabret/main.go @@ -9,17 +9,15 @@ import ( func main() { log.SetFlags(0) - log.Printf("Rendering current project") - site, err := config.ReadCabretfile("./Cabretfile.yaml") + cabretfile, err := config.ReadCabretfile("./Cabretfile.yaml") if err != nil { log.Fatal(err) } - // repr.Println(site) + // repr.Println(cabretfile) - if err := exec.Execute(site); err != nil { + if err := exec.Execute(cabretfile); err != nil { log.Fatal(err) } - } diff --git a/config/config.go b/config/config.go index 2e19220..4c57c76 100644 --- a/config/config.go +++ b/config/config.go @@ -6,24 +6,21 @@ import ( "gopkg.in/yaml.v3" ) -// Operation is an enum of various operations +// Operation should have at least one key in "source", "use", "target". The remaining keys are options for that operation type Operation map[string]any -type EntryPoint struct { - Source string `yaml:",omitempty"` - Pipeline []Operation `yaml:",omitempty"` +type Pipeline struct { + Pipeline []Operation `yaml:"pipeline"` } -type Options struct { +type BuildOptions struct { + // Excludes lists files and folders to globally exclude from compilation Excludes []string `yaml:",omitempty"` - // Include []string `yaml:",omitempty"` - Output string `yaml:",omitempty"` } -// Cabretfile has some configuration for the type Cabretfile struct { - Options Options `yaml:",omitempty"` - EntryPoints []*EntryPoint `yaml:"entryPoints"` + Options BuildOptions + Build []Pipeline } func ReadCabretfile(file string) (*Cabretfile, error) { diff --git a/examples/basic/Cabretfile.yaml b/examples/basic/Cabretfile.yaml index f9a3fb7..19c3b09 100644 --- a/examples/basic/Cabretfile.yaml +++ b/examples/basic/Cabretfile.yaml @@ -1,12 +1,14 @@ -entryPoints: - - source: index.html - pipeline: - - layout: layouts/base.html +build: + - pipeline: + - source: index.html + - use: layout + path: layouts/base.html - target: dist/index.html - - source: posts/{id}.md - pipeline: - - plugin: markdown - - layout: layouts/base.html + - pipeline: + - source: posts/{id}.md + - use: markdown + - use: layout + path: layouts/base.html - target: dist/posts/{id}/index.html diff --git a/examples/basic/index.html b/examples/basic/index.html index bf2bf55..d9ab390 100644 --- a/examples/basic/index.html +++ b/examples/basic/index.html @@ -1,5 +1,6 @@

My Website

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Minima, eveniet, dolorum amet, cupiditate quae excepturi aspernatur dolor voluptatem obcaecati ratione quas? Et explicabo illum iure eius porro, dolor quos doloremque! + Lorem ipsum dolor sit amet consectetur adipisicing elit. Minima, eveniet, dolorum amet, cupiditate quae excepturi aspernatur dolor + voluptatem obcaecati ratione quas? Et explicabo illum iure eius porro, dolor quos doloremque!

diff --git a/exec/exec.go b/exec/exec.go index 2c284b6..b622da8 100644 --- a/exec/exec.go +++ b/exec/exec.go @@ -1,123 +1,20 @@ package exec import ( - "log" - "mime" - "os" - gopath "path" - - "github.com/alecthomas/repr" "github.com/aziis98/cabret" "github.com/aziis98/cabret/config" - "github.com/aziis98/cabret/operation" - "github.com/aziis98/cabret/path" + "github.com/aziis98/cabret/pipeline" ) -type matchResult struct { - file string - context map[string]string -} - -func BuildOperation(op config.Operation) cabret.Operation { - if path, ok := op["read"]; ok { - return cabret.FlatMapToMapAll{&operation.Target{ - PathTemplate: path.(string), - }} - } - if path, ok := op["write"]; ok { - return cabret.FlatMapToMapAll{&operation.Target{ - PathTemplate: path.(string), - }} - } - if name, ok := op["plugin"]; ok { - switch name { - case "layout": - path := op["path"].(string) - delete(op, "path") - - return cabret.FlatMapToMapAll{&operation.Layout{ - TemplateFilesPattern: path, - Options: op, - }} - case "markdown": - return cabret.FlatMapToMapAll{&operation.Markdown{ - Options: op, - }} - default: - log.Fatalf(`invalid operation: %s`, name) - } - } - - return nil -} - func Execute(cfg *config.Cabretfile) error { - files, err := cabret.FindFiles([]string{}) - if err != nil { - return err - } - - // the first index is the entrypoint ID, the second is for the array of matched files for this entrypoint - entryPointMatches := make([][]matchResult, len(cfg.EntryPoints)) - - // load all files to process - for id, ep := range cfg.EntryPoints { - pat, err := path.ParsePattern(ep.Source) + for _, p := range cfg.Build { + ops, err := pipeline.Parse(p) if err != nil { return err } - matchedFiles := []matchResult{} - for _, f := range files { - if ok, ctx, _ := pat.Match(f); ok { - log.Printf(`[Preload] [EntryPoint %d] Found "%s": %#v`, id, f, ctx) - - matchedFiles = append(matchedFiles, matchResult{f, ctx}) - } - } - - entryPointMatches[id] = matchedFiles - } - - // TODO: preload all metadata... - - // process all entrypoints - for id, ep := range cfg.EntryPoints { - log.Printf(`[EntryPoint %d] starting to process %d file(s)`, id, len(entryPointMatches[id])) - - for _, m := range entryPointMatches[id] { - log.Printf(`[EntryPoint %d] ["%s"] reading file`, id, m.file) - data, err := os.ReadFile(m.file) - if err != nil { - return err - } - - content := cabret.Content{ - Type: mime.TypeByExtension(gopath.Ext(m.file)), - Data: data, - Metadata: cabret.Map{ - cabret.MatchResult: m.context, - }, - } - - for i, opConfig := range ep.Pipeline { - op := BuildOperation(opConfig) - - log.Printf(`[EntryPoint %d] ["%s"] [Operation(%d)] applying %s`, id, m.file, i, repr.String(op)) - - newContent, err := op.Process(content) - if err != nil { - return err - } - if newContent == nil { - break - } - - // log.Printf(`[EntryPoint %d] ["%s"] [Operation(%d)] [Metadata] %s`, id, m.file, i, repr.String(newContent.Metadata, repr.Indent(" "))) - content = *newContent - } - - log.Printf(`[EntryPoint %d] ["%s"] done`, id, m.file) + if _, err := pipeline.Process([]cabret.Content{}, ops); err != nil { + return err } } diff --git a/operation/categorize.go b/operation/categorize.go new file mode 100644 index 0000000..3bf9910 --- /dev/null +++ b/operation/categorize.go @@ -0,0 +1,39 @@ +package operation + +import ( + "fmt" + + "github.com/aziis98/cabret" +) + +func init() { + registerType("categorize", &Categorize{}) +} + +type Categorize struct { + Key string + + // Operation to be executed for each category + Operation cabret.Operation +} + +func (op *Categorize) Load(config map[string]any) error { + { + v, ok := config["key"] + if !ok { + return fmt.Errorf(`missing "key" field`) + } + key, ok := v.(string) + if !ok { + return fmt.Errorf(`expected string but got "%v" of type %T`, v, v) + } + + op.Key = key + } + + return nil +} + +func (op *Categorize) Process(content cabret.Content) (*cabret.Content, error) { + return nil, nil +} diff --git a/operation/group.go b/operation/group.go deleted file mode 100644 index a5b590c..0000000 --- a/operation/group.go +++ /dev/null @@ -1,12 +0,0 @@ -package operation - -import "github.com/aziis98/cabret" - -type GroupBy struct { - Key string -} - -func (op GroupBy) Process(content cabret.Content) (*cabret.Content, error) { - - return nil, nil -} diff --git a/operation/layout.go b/operation/layout.go index 71cbc19..319c0ef 100644 --- a/operation/layout.go +++ b/operation/layout.go @@ -1,38 +1,73 @@ package operation import ( - "html/template" + "fmt" + goHtmlTemplate "html/template" "log" "mime" "path/filepath" "strings" - gopath "path" - - "github.com/alecthomas/repr" "github.com/aziis98/cabret" - "github.com/aziis98/cabret/operation/layout" + "github.com/aziis98/cabret/operation/template" "github.com/aziis98/cabret/util" ) var HtmlMimeType = mime.TypeByExtension(".html") -var _ cabret.FlatMapOperation = Layout{} +func init() { + registerType("layout", &Layout{}) +} type Layout struct { - // TemplateFilesPattern is a comma separated list of unix glob patterns - TemplateFilesPattern string - - Options map[string]any + // TemplatePatterns is a list of glob patterns of templates that will be loaded + TemplatePatterns []string } -func (op Layout) FlatMap(content cabret.Content) (*cabret.Content, error) { - var tmpl layout.Template +func (op *Layout) 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) + } - patterns := strings.Split(op.TemplateFilesPattern, ",") + op.TemplatePatterns = []string{strings.TrimSpace(globPatternStr)} + return nil + } + + return fmt.Errorf(`invalid config`) +} + +func (op Layout) ProcessItem(content cabret.Content) (*cabret.Content, error) { + // expand glob patterns tmplFiles := []string{} - for _, pat := range patterns { + for _, pat := range op.TemplatePatterns { files, err := filepath.Glob(strings.TrimSpace(pat)) if err != nil { return nil, err @@ -41,28 +76,22 @@ func (op Layout) FlatMap(content cabret.Content) (*cabret.Content, error) { tmplFiles = append(tmplFiles, files...) } - log.Printf(`[Layout] template pattern "%s" expanded to %s`, op.TemplateFilesPattern, repr.String(tmplFiles)) - - if gopath.Ext(tmplFiles[0]) == ".html" { - var err error - if tmpl, err = layout.NewHtmlTemplate(tmplFiles...); err != nil { - return nil, err - } - } else { - var err error - if tmpl, err = layout.NewTextTemplate(tmplFiles...); err != nil { - return nil, err - } + // create template + tmpl, err := template.ParseFiles(tmplFiles...) + if err != nil { + return nil, err } ctx := util.CloneMap(content.Metadata) if content.Type == HtmlMimeType { - ctx["Content"] = template.HTML(content.Data) + ctx["Content"] = goHtmlTemplate.HTML(content.Data) } else { ctx["Content"] = content.Data } + log.Printf(`[operation.Layout] rendering into layout "%s"`, strings.Join(op.TemplatePatterns, ", ")) + data, err := tmpl.Render(ctx) if err != nil { return nil, err diff --git a/operation/layout/layout.go b/operation/layout/layout.go deleted file mode 100644 index edd2c10..0000000 --- a/operation/layout/layout.go +++ /dev/null @@ -1,5 +0,0 @@ -package layout - -type Template interface { - Render(ctx map[string]any) ([]byte, error) -} diff --git a/operation/markdown.go b/operation/markdown.go index 3e13733..5477ab1 100644 --- a/operation/markdown.go +++ b/operation/markdown.go @@ -2,6 +2,7 @@ package operation import ( "bytes" + "log" "github.com/aziis98/cabret" "github.com/iancoleman/strcase" @@ -11,13 +12,19 @@ import ( "github.com/yuin/goldmark/parser" ) -var _ cabret.FlatMapOperation = Markdown{} +func init() { + registerType("markdown", &Markdown{}) +} type Markdown struct { Options map[string]any } -func (op Markdown) FlatMap(content cabret.Content) (*cabret.Content, error) { +func (op *Markdown) Load(config map[string]any) error { + return nil +} + +func (op Markdown) ProcessItem(content cabret.Content) (*cabret.Content, error) { md := goldmark.New( goldmark.WithExtensions( extension.GFM, @@ -30,6 +37,8 @@ func (op Markdown) FlatMap(content cabret.Content) (*cabret.Content, error) { var buf bytes.Buffer + log.Printf(`[operation.Markdown] rendering markdown`) + context := parser.NewContext() if err := md.Convert(content.Data, &buf, parser.WithContext(context)); err != nil { panic(err) diff --git a/operation/read.go b/operation/read.go deleted file mode 100644 index 3719941..0000000 --- a/operation/read.go +++ /dev/null @@ -1,40 +0,0 @@ -package operation - -import ( - "mime" - "os" - gopath "path" - "path/filepath" - - "github.com/aziis98/cabret" -) - -var _ cabret.ListOperation = Read{} - -type Read struct { - Patterns []string -} - -func (op Read) MapAll(contents []cabret.Content) ([]cabret.Content, error) { - for _, pattern := range op.Patterns { - files, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - - for _, file := range files { - data, err := os.ReadFile(file) - if err != nil { - return nil, err - } - - contents = append(contents, cabret.Content{ - Type: mime.TypeByExtension(gopath.Ext(file)), - Data: data, - Metadata: cabret.Map{}, - }) - } - } - - return contents, nil -} diff --git a/operation/registry.go b/operation/registry.go new file mode 100644 index 0000000..74f90e8 --- /dev/null +++ b/operation/registry.go @@ -0,0 +1,34 @@ +package operation + +import ( + "fmt" + "log" + "reflect" + + "github.com/aziis98/cabret" +) + +// ShortFormValueKey is used by some operations that support an inline form +const ShortFormValueKey = "value" + +var registry = map[string]reflect.Type{} + +func registerType(name string, op cabret.Operation) { + typ := reflect.TypeOf(op).Elem() + log.Printf(`[operation] registered type "%v"`, typ) + registry[name] = typ +} + +func Build(name string, options map[string]any) (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 +} diff --git a/operation/registry_test.go b/operation/registry_test.go new file mode 100644 index 0000000..c73bd65 --- /dev/null +++ b/operation/registry_test.go @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000..3b9fcaf --- /dev/null +++ b/operation/source.go @@ -0,0 +1,88 @@ +package operation + +import ( + "fmt" + "log" + "mime" + "os" + gopath "path" + + "github.com/aziis98/cabret" + "github.com/aziis98/cabret/path" +) + +func init() { + registerType("source", &Source{}) +} + +// Source is a ListOperation that appends the matched files to the processing items +type Source struct { + Patterns []string +} + +func (op *Source) Load(config map[string]any) error { + if v, ok := config[ShortFormValueKey]; ok { + pattern, ok := v.(string) + if !ok { + return fmt.Errorf(`expected 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) + if !ok { + return fmt.Errorf(`expected list of patterns but got "%v" of type %T`, v, v) + } + + op.Patterns = patterns + return nil + } + + return fmt.Errorf(`invalid config for "source": %#v`, config) +} + +func (op Source) ProcessList(contents []cabret.Content) ([]cabret.Content, error) { + files, err := cabret.FindFiles([]string{}) + if err != nil { + return nil, err + } + + matches := []cabret.MatchResult{} + + for _, patternStr := range op.Patterns { + pat, err := path.ParsePattern(patternStr) + if err != nil { + return nil, err + } + + for _, f := range files { + if ok, captures, _ := pat.Match(f); ok { + matches = append(matches, cabret.MatchResult{ + File: f, + Captures: captures, + }) + } + } + } + + for _, m := range matches { + log.Printf(`[operation.Source] reading "%s"`, m.File) + + data, err := os.ReadFile(m.File) + if err != nil { + return nil, err + } + + contents = append(contents, cabret.Content{ + Type: mime.TypeByExtension(gopath.Ext(m.File)), + Data: data, + Metadata: cabret.Map{ + cabret.MatchResultKey: m.Captures, + }, + }) + } + + return contents, nil +} diff --git a/operation/target.go b/operation/target.go index b401726..4f676e5 100644 --- a/operation/target.go +++ b/operation/target.go @@ -2,6 +2,7 @@ package operation import ( "fmt" + "log" "os" gopath "path" @@ -10,20 +11,38 @@ import ( "github.com/aziis98/cabret/path" ) -var _ cabret.FlatMapOperation = Target{} +func init() { + registerType("target", &Target{}) +} type Target struct { PathTemplate string } -func (op Target) FlatMap(c cabret.Content) (*cabret.Content, error) { - mr, ok := c.Metadata[cabret.MatchResult].(map[string]string) +func (op *Target) Load(config map[string]any) error { + if v, ok := config[ShortFormValueKey]; ok { + template, ok := v.(string) + if !ok { + return fmt.Errorf(`expected pattern but got "%v" of type %T`, v, v) + } + + op.PathTemplate = template + return nil + } + + return fmt.Errorf(`invalid config for "target": %#v`, config) +} + +func (op Target) ProcessItem(c cabret.Content) (*cabret.Content, error) { + mr, ok := c.Metadata[cabret.MatchResultKey].(map[string]string) if !ok { - return nil, fmt.Errorf(`invalid match result type %T`, c.Metadata[cabret.MatchResult]) + return nil, fmt.Errorf(`invalid match result type %T`, c.Metadata[cabret.MatchResultKey]) } target := path.RenderTemplate(op.PathTemplate, mr) + log.Printf(`[operation.Target] writing "%s"`, target) + if err := os.MkdirAll(gopath.Dir(target), 0777); err != nil { return nil, err } diff --git a/operation/layout/html.go b/operation/template/html.go similarity index 58% rename from operation/layout/html.go rename to operation/template/html.go index b253e56..54baec2 100644 --- a/operation/layout/html.go +++ b/operation/template/html.go @@ -1,18 +1,18 @@ -package layout +package template import ( "bytes" - "html/template" + goHtmlTemplate "html/template" ) var _ Template = HtmlTemplate{} type HtmlTemplate struct { - *template.Template + *goHtmlTemplate.Template } func NewHtmlTemplate(files ...string) (*HtmlTemplate, error) { - t, err := template.ParseFiles(files...) + t, err := goHtmlTemplate.ParseFiles(files...) if err != nil { return nil, err } @@ -21,11 +21,10 @@ func NewHtmlTemplate(files ...string) (*HtmlTemplate, error) { } func (t HtmlTemplate) Render(ctx map[string]any) ([]byte, error) { - var b bytes.Buffer - - if err := t.Template.Execute(&b, ctx); err != nil { + var buf bytes.Buffer + if err := t.Template.Execute(&buf, ctx); err != nil { return nil, err } - return b.Bytes(), nil + return buf.Bytes(), nil } diff --git a/operation/template/template.go b/operation/template/template.go new file mode 100644 index 0000000..bea5885 --- /dev/null +++ b/operation/template/template.go @@ -0,0 +1,17 @@ +package template + +import ( + "path/filepath" +) + +type Template interface { + Render(ctx map[string]any) ([]byte, error) +} + +func ParseFiles(files ...string) (Template, error) { + if filepath.Ext(files[0]) == ".html" { + return NewHtmlTemplate(files...) + } + + return NewTextTemplate(files...) +} diff --git a/operation/layout/text.go b/operation/template/text.go similarity index 58% rename from operation/layout/text.go rename to operation/template/text.go index 566112b..203649a 100644 --- a/operation/layout/text.go +++ b/operation/template/text.go @@ -1,18 +1,18 @@ -package layout +package template import ( "bytes" - "text/template" + goTextTemplate "text/template" ) var _ Template = TextTemplate{} type TextTemplate struct { - *template.Template + *goTextTemplate.Template } func NewTextTemplate(files ...string) (*TextTemplate, error) { - t, err := template.ParseFiles(files...) + t, err := goTextTemplate.ParseFiles(files...) if err != nil { return nil, err } @@ -21,11 +21,10 @@ func NewTextTemplate(files ...string) (*TextTemplate, error) { } func (t TextTemplate) Render(ctx map[string]any) ([]byte, error) { - var b bytes.Buffer - - if err := t.Template.Execute(&b, ctx); err != nil { + var buf bytes.Buffer + if err := t.Template.Execute(&buf, ctx); err != nil { return nil, err } - return b.Bytes(), nil + return buf.Bytes(), nil } diff --git a/pipeline/pipeline.go b/pipeline/pipeline.go new file mode 100644 index 0000000..a19f879 --- /dev/null +++ b/pipeline/pipeline.go @@ -0,0 +1,88 @@ +package pipeline + +import ( + "fmt" + + "github.com/aziis98/cabret" + "github.com/aziis98/cabret/config" + "github.com/aziis98/cabret/operation" +) + +func switchMap(m map[string]any, v *any) func(k string) bool { + return func(k string) bool { + val, ok := m[k] + if ok { + *v = val + } + return ok + } +} + +func Parse(p config.Pipeline) ([]cabret.Operation, error) { + ops := []cabret.Operation{} + + for _, opConfig := range p.Pipeline { + var v any + has := switchMap(opConfig, &v) + + switch { + case has("source"): + value, ok := v.(string) + if !ok { + return nil, fmt.Errorf(`expected string but got "%v" of type %T`, v, v) + } + + opConfig[operation.ShortFormValueKey] = value + op := &operation.Source{} + if err := op.Load(opConfig); err != nil { + return nil, err + } + + ops = append(ops, op) + + case has("target"): + value, ok := v.(string) + if !ok { + return nil, fmt.Errorf(`expected string but got "%v" of type %T`, v, v) + } + + opConfig[operation.ShortFormValueKey] = value + op := &operation.Target{} + if err := op.Load(opConfig); err != nil { + return nil, err + } + + ops = append(ops, op) + + case has("use"): + name, ok := v.(string) + if !ok { + return nil, fmt.Errorf(`expected string but got "%v" of type %T`, v, v) + } + + op, err := operation.Build(name, opConfig) + if err != nil { + return nil, err + } + + ops = append(ops, op) + + default: + return nil, fmt.Errorf(`pipeline entry is missing one of "use", "source" or "target", got %#v`, opConfig) + } + } + + 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 + } + } + + return contents, nil +}