From 3ff4f1c06c4c8094c1ac515e684f9c2ad197df4a Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Mon, 2 Jan 2023 00:44:56 +0100 Subject: [PATCH] feat: added the program and template operations --- cmd/cabret/main.go | 6 +- go.mod | 1 + go.sum | 2 + operation/program.go | 130 +++++++++++++++++++++++++++++++++++++ operation/registry.go | 2 - operation/template.go | 87 +++++++++++++++++++++++++ operation/template/html.go | 16 +++++ 7 files changed, 241 insertions(+), 3 deletions(-) create mode 100644 operation/program.go create mode 100644 operation/template.go diff --git a/cmd/cabret/main.go b/cmd/cabret/main.go index 101cd22..35f070e 100644 --- a/cmd/cabret/main.go +++ b/cmd/cabret/main.go @@ -5,12 +5,16 @@ import ( "github.com/aziis98/cabret/config" "github.com/aziis98/cabret/runner" + "github.com/spf13/pflag" ) func main() { log.SetFlags(0) - cabretfile, err := config.ReadCabretfile("./Cabretfile.yaml") + optConfig := pflag.StringP("config", "c", "Cabretfile.yaml", `which configuration file to use`) + pflag.Parse() + + cabretfile, err := config.ReadCabretfile(*optConfig) if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index 7b72764..e33d014 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rakyll/gotest v0.0.6 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/yuin/goldmark v1.5.3 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect golang.org/x/exp v0.0.0-20221217163422-3c43f8badb15 // indirect diff --git a/go.sum b/go.sum index dab9158..a7d2042 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rakyll/gotest v0.0.6 h1:hBTqkO3jiuwYW/M9gL4bu0oTYcm8J6knQAAPUsJsz1I= github.com/rakyll/gotest v0.0.6/go.mod h1:SkoesdNCWmiD4R2dljIUcfSnNdVZ12y8qK4ojDkc2Sc= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M= github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= diff --git a/operation/program.go b/operation/program.go new file mode 100644 index 0000000..722c1a0 --- /dev/null +++ b/operation/program.go @@ -0,0 +1,130 @@ +package operation + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os/exec" + + "github.com/aziis98/cabret" +) + +func init() { + registerType("program", &Program{}) +} + +// Program is a [cabret.ItemOperation] that passes the incoming item as input to the given command. +// Options are the following: +// +// command: +// io: # optional, by default is "raw" +// +// The io format can be one of the following +// +// - "raw" will just pass the item data to the program +// - "json" will pass the whole item as JSON to the given program, this should be useful for +// making external plugins compatible with cabret. +type Program struct { + IOFormat string + ShellCommand string +} + +type Format interface { + Input(item cabret.Content) (stdin io.Reader, err error) + Output(item cabret.Content, stdout io.Reader) (*cabret.Content, error) +} + +var ioProgramFormats = map[string]Format{} + +func init() { + ioProgramFormats["json"] = JsonFormat{} + ioProgramFormats["raw"] = RawFormat{} +} + +type JsonFormat struct{} + +func (JsonFormat) Input(item cabret.Content) (stdin io.Reader, err error) { + buf := &bytes.Buffer{} + + if err := json.NewEncoder(buf).Encode(map[string]any{ + "type": item.Type, + "metadata": item.Metadata, + "data": item.Data, + }); err != nil { + return nil, err + } + + return buf, nil +} + +func (JsonFormat) Output(item cabret.Content, stdout io.Reader) (*cabret.Content, error) { + var result struct { + Type string `json:"type"` + Metadata map[string]any `json:"metadata"` + Data string `json:"data"` + } + + if err := json.NewDecoder(stdout).Decode(&result); err != nil { + return nil, err + } + + return &cabret.Content{ + Type: result.Type, + Metadata: result.Metadata, + Data: []byte(result.Data), + }, nil +} + +type RawFormat struct{} + +func (RawFormat) Input(item cabret.Content) (stdin io.Reader, err error) { + return bytes.NewReader(item.Data), nil +} + +func (RawFormat) Output(item cabret.Content, stdout io.Reader) (*cabret.Content, error) { + data, err := io.ReadAll(stdout) + if err != nil { + return nil, err + } + + item.Data = data + return &item, nil +} + +func (op *Program) Configure(options map[string]any) error { + var err error + op.IOFormat, err = getKey(options, "io", "raw") + if err != nil { + return err + } + op.ShellCommand, err = getKey[string](options, "command") + if err != nil { + return err + } + + return nil +} + +func (op *Program) ProcessItem(item cabret.Content) (*cabret.Content, error) { + ioFmt, ok := ioProgramFormats[op.IOFormat] + if !ok { + return nil, fmt.Errorf(`unknown io format "%s"`, op.IOFormat) + } + + r, err := ioFmt.Input(item) + if err != nil { + return nil, err + } + + cmd := exec.Command("sh", "-c", op.ShellCommand) + cmd.Stdin = r + var buf bytes.Buffer + cmd.Stdout = &buf + + if err := cmd.Run(); err != nil { + return nil, err + } + + return ioFmt.Output(item, &buf) +} diff --git a/operation/registry.go b/operation/registry.go index c2cbe97..5df09f0 100644 --- a/operation/registry.go +++ b/operation/registry.go @@ -2,7 +2,6 @@ package operation import ( "fmt" - "log" "reflect" "github.com/aziis98/cabret" @@ -15,7 +14,6 @@ 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 } diff --git a/operation/template.go b/operation/template.go new file mode 100644 index 0000000..26cf228 --- /dev/null +++ b/operation/template.go @@ -0,0 +1,87 @@ +package operation + +import ( + "bytes" + "fmt" + "html/template" + "io" + + "github.com/aziis98/cabret" +) + +func init() { + registerType("template", &Template{}) +} + +type Template struct { + Engine string +} + +func (op *Template) Configure(options map[string]any) error { + var err error + op.Engine, err = getKey[string](options, "engine") + if err != nil { + return err + } + + return nil +} + +func (op *Template) ProcessList(items []cabret.Content) ([]cabret.Content, error) { + var t bytes.Buffer + + // concatenate all templates + for _, item := range items { + t.Write(item.Data) + } + + tmpl := t.String() + + var data bytes.Buffer + switch op.Engine { + case "html": + if err := op.RenderHtml(tmpl, items, &data); err != nil { + return nil, err + } + case "text": + if err := op.RenderText(tmpl, items, &data); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf(`unknown format "%s"`, op.Engine) + } + + return []cabret.Content{ + { + Type: items[0].Type, + Metadata: cabret.Map{}, + Data: data.Bytes(), + }, + }, nil +} + +func (op *Template) RenderHtml(tmpl string, items []cabret.Content, w io.Writer) error { + t, err := template.New("template").Parse(tmpl) + if err != nil { + return err + } + + if err := t.ExecuteTemplate(w, "template", map[string]any{"Items": items}); err != nil { + return err + } + + return nil +} + +func (op *Template) RenderText(tmpl string, items []cabret.Content, w io.Writer) error { + t, err := template.New("template").Parse(tmpl) + if err != nil { + return err + } + + if err := t.ExecuteTemplate(w, "template", map[string]any{"Items": items}); err != nil { + return err + } + + return nil +} diff --git a/operation/template/html.go b/operation/template/html.go index 410ce5b..63b9dcc 100644 --- a/operation/template/html.go +++ b/operation/template/html.go @@ -17,6 +17,22 @@ type HtmlTemplate struct { *goHtmlTemplate.Template } +// func NewHtmlTemplateFromReader(r io.Reader) (*HtmlTemplate, error) { +// t := goHtmlTemplate.New("") + +// data, err := io.ReadAll(r) +// if err != nil { +// return nil, err +// } + +// tmpl, err := t.Parse(string(data)) +// if err != nil { +// return nil, err +// } + +// return &HtmlTemplate{tmpl}, nil +// } + func NewHtmlTemplate(files ...string) (*HtmlTemplate, error) { t, err := goHtmlTemplate.ParseFiles(files...) if err != nil {