refactor: some architecture rework, added a single processing context that will be used to track dependencies
This commit is contained in:
@@ -40,12 +40,16 @@ type Operation interface {
|
|||||||
Configure(options map[string]any) error
|
Configure(options map[string]any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
Files []string
|
||||||
|
}
|
||||||
|
|
||||||
type ListOperation interface {
|
type ListOperation interface {
|
||||||
Operation
|
Operation
|
||||||
ProcessList(items []Content) ([]Content, error)
|
ProcessList(ctx *Context, items []Content) ([]Content, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemOperation interface {
|
type ItemOperation interface {
|
||||||
Operation
|
Operation
|
||||||
ProcessItem(item Content) (*Content, error)
|
ProcessItem(ctx *Context, item Content) (*Content, error)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -6,11 +6,9 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 Pipeline struct {
|
type Pipeline struct {
|
||||||
Pipeline []Operation `yaml:"pipeline"`
|
// Pipeline is a list of operations, each one should have at least one key in "source", "use", "target". The remaining keys are options for that operation
|
||||||
|
Pipeline []map[string]any `yaml:"pipeline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuildOptions struct {
|
type BuildOptions struct {
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ func (op *Categorize) Configure(config map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Categorize) ProcessList(contents []cabret.Content) ([]cabret.Content, error) {
|
var _ cabret.ListOperation = &Categorize{}
|
||||||
|
|
||||||
|
func (op *Categorize) ProcessList(ctx *cabret.Context, contents []cabret.Content) ([]cabret.Content, error) {
|
||||||
key := strcase.ToCamel(op.Key)
|
key := strcase.ToCamel(op.Key)
|
||||||
|
|
||||||
categories := map[string][]cabret.Content{}
|
categories := map[string][]cabret.Content{}
|
||||||
|
|||||||
+3
-1
@@ -30,7 +30,9 @@ func (op *Chunk) Configure(options map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Chunk) ProcessList(items []cabret.Content) ([]cabret.Content, error) {
|
var _ cabret.ListOperation = &Chunk{}
|
||||||
|
|
||||||
|
func (op *Chunk) ProcessList(ctx *cabret.Context, items []cabret.Content) ([]cabret.Content, error) {
|
||||||
totalChunks := len(items) / op.Size
|
totalChunks := len(items) / op.Size
|
||||||
|
|
||||||
chunks := make([][]cabret.Content, totalChunks, totalChunks+1)
|
chunks := make([][]cabret.Content, totalChunks, totalChunks+1)
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ func (op *Frontmatter) Configure(config map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Frontmatter) ProcessItem(content cabret.Content) (*cabret.Content, error) {
|
var _ cabret.ItemOperation = &Frontmatter{}
|
||||||
|
|
||||||
|
func (op *Frontmatter) ProcessItem(ctx *cabret.Context, content cabret.Content) (*cabret.Content, error) {
|
||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
meta.Meta,
|
meta.Meta,
|
||||||
|
|||||||
+7
-5
@@ -64,7 +64,9 @@ func (op *Layout) Configure(config map[string]any) error {
|
|||||||
return fmt.Errorf(`invalid config`)
|
return fmt.Errorf(`invalid config`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Layout) ProcessItem(content cabret.Content) (*cabret.Content, error) {
|
var _ cabret.ItemOperation = &Layout{}
|
||||||
|
|
||||||
|
func (op Layout) ProcessItem(ctx *cabret.Context, content cabret.Content) (*cabret.Content, error) {
|
||||||
// expand glob patterns
|
// expand glob patterns
|
||||||
tmplFiles := []string{}
|
tmplFiles := []string{}
|
||||||
for _, pat := range op.TemplatePatterns {
|
for _, pat := range op.TemplatePatterns {
|
||||||
@@ -82,17 +84,17 @@ func (op Layout) ProcessItem(content cabret.Content) (*cabret.Content, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := util.CloneMap(content.Metadata)
|
metadata := util.CloneMap(content.Metadata)
|
||||||
|
|
||||||
if content.Type == HtmlMimeType {
|
if content.Type == HtmlMimeType {
|
||||||
ctx["Content"] = goHtmlTemplate.HTML(content.Data)
|
metadata["Content"] = goHtmlTemplate.HTML(content.Data)
|
||||||
} else {
|
} else {
|
||||||
ctx["Content"] = content.Data
|
metadata["Content"] = content.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf(`[operation.Layout] rendering into layout "%s"`, strings.Join(op.TemplatePatterns, ", "))
|
log.Printf(`[operation.Layout] rendering into layout "%s"`, strings.Join(op.TemplatePatterns, ", "))
|
||||||
|
|
||||||
data, err := tmpl.Render(ctx)
|
data, err := tmpl.Render(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ func (op *Markdown) Configure(config map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Markdown) ProcessItem(content cabret.Content) (*cabret.Content, error) {
|
var _ cabret.ItemOperation = &Markdown{}
|
||||||
|
|
||||||
|
func (op Markdown) ProcessItem(ctx *cabret.Context, content cabret.Content) (*cabret.Content, error) {
|
||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.GFM,
|
extension.GFM,
|
||||||
|
|||||||
@@ -107,7 +107,9 @@ func (op *Program) Configure(options map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Program) ProcessItem(item cabret.Content) (*cabret.Content, error) {
|
var _ cabret.ItemOperation = &Program{}
|
||||||
|
|
||||||
|
func (op *Program) ProcessItem(ctx *cabret.Context, item cabret.Content) (*cabret.Content, error) {
|
||||||
ioFmt, ok := ioProgramFormats[op.IOFormat]
|
ioFmt, ok := ioProgramFormats[op.IOFormat]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf(`unknown io format "%s"`, op.IOFormat)
|
return nil, fmt.Errorf(`unknown io format "%s"`, op.IOFormat)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package operation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/aziis98/cabret"
|
"github.com/aziis98/cabret"
|
||||||
@@ -13,6 +14,13 @@ const ShortFormValueKey = "value"
|
|||||||
var registry = map[string]reflect.Type{}
|
var registry = map[string]reflect.Type{}
|
||||||
|
|
||||||
func registerType(name string, op cabret.Operation) {
|
func registerType(name string, op cabret.Operation) {
|
||||||
|
switch op.(type) {
|
||||||
|
case cabret.ItemOperation:
|
||||||
|
case cabret.ListOperation:
|
||||||
|
default:
|
||||||
|
log.Fatal(`operation must also satisfy one of cabret.ListOperation or cabret.ItemOperation`)
|
||||||
|
}
|
||||||
|
|
||||||
typ := reflect.TypeOf(op).Elem()
|
typ := reflect.TypeOf(op).Elem()
|
||||||
registry[name] = typ
|
registry[name] = typ
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -36,7 +36,9 @@ func (op *Slice) Configure(options map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Slice) ProcessList(items []cabret.Content) ([]cabret.Content, error) {
|
var _ cabret.ListOperation = &Slice{}
|
||||||
|
|
||||||
|
func (op *Slice) ProcessList(ctx *cabret.Context, items []cabret.Content) ([]cabret.Content, error) {
|
||||||
from := op.From
|
from := op.From
|
||||||
to := op.To
|
to := op.To
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -53,22 +53,24 @@ func (op *Source) Configure(config map[string]any) error {
|
|||||||
return fmt.Errorf(`invalid config for "source": %#v`, config)
|
return fmt.Errorf(`invalid config for "source": %#v`, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Source) ProcessList(contents []cabret.Content) ([]cabret.Content, error) {
|
var _ cabret.ListOperation = &Source{}
|
||||||
files, err := cabret.FindFiles([]string{})
|
|
||||||
|
func (op Source) ProcessList(ctx *cabret.Context, contents []cabret.Content) ([]cabret.Content, error) {
|
||||||
|
files, err := cabret.FindFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := []cabret.MatchResult{}
|
matches := []cabret.MatchResult{}
|
||||||
|
|
||||||
for _, patternStr := range op.Patterns {
|
for _, rawPattern := range op.Patterns {
|
||||||
pat, err := path.ParsePattern(patternStr)
|
pattern, err := path.ParsePattern(rawPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
if ok, captures, _ := pat.Match(f); ok {
|
if ok, captures, _ := pattern.Match(f); ok {
|
||||||
matches = append(matches, cabret.MatchResult{
|
matches = append(matches, cabret.MatchResult{
|
||||||
File: f,
|
File: f,
|
||||||
Captures: captures,
|
Captures: captures,
|
||||||
|
|||||||
+3
-1
@@ -36,7 +36,9 @@ func (op *Target) Configure(config map[string]any) error {
|
|||||||
return fmt.Errorf(`invalid config for "target": %#v`, config)
|
return fmt.Errorf(`invalid config for "target": %#v`, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op Target) ProcessItem(c cabret.Content) (*cabret.Content, error) {
|
var _ cabret.ItemOperation = &Target{}
|
||||||
|
|
||||||
|
func (op Target) ProcessItem(ctx *cabret.Context, c cabret.Content) (*cabret.Content, error) {
|
||||||
log.Printf(`[operation.Target] expanding pattern "%s"`, op.PathTemplate)
|
log.Printf(`[operation.Target] expanding pattern "%s"`, op.PathTemplate)
|
||||||
|
|
||||||
target := op.PathTemplate
|
target := op.PathTemplate
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ func (op *Template) Configure(options map[string]any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Template) ProcessList(items []cabret.Content) ([]cabret.Content, error) {
|
var _ cabret.ListOperation = &Template{}
|
||||||
|
|
||||||
|
func (op *Template) ProcessList(ctx *cabret.Context, items []cabret.Content) ([]cabret.Content, error) {
|
||||||
var t bytes.Buffer
|
var t bytes.Buffer
|
||||||
|
|
||||||
// concatenate all templates
|
// concatenate all templates
|
||||||
|
|||||||
+21
-20
@@ -8,11 +8,12 @@ import (
|
|||||||
"github.com/aziis98/cabret/operation"
|
"github.com/aziis98/cabret/operation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func switchMap(m map[string]any, v *any) func(k string) bool {
|
// switchMapHasKey returns a function that returns true if the map "m" contains the given key and binds the corresponding value to the provided "target" pointer, useful for writing map key checks using a switch instead of an if chain
|
||||||
|
func switchMapHasKey(m map[string]any, target *any) func(k string) bool {
|
||||||
return func(k string) bool {
|
return func(k string) bool {
|
||||||
val, ok := m[k]
|
val, ok := m[k]
|
||||||
if ok {
|
if ok {
|
||||||
*v = val
|
*target = val
|
||||||
}
|
}
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@@ -21,48 +22,48 @@ func switchMap(m map[string]any, v *any) func(k string) bool {
|
|||||||
func ParsePipeline(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 _, operationConfig := range p.Pipeline {
|
||||||
var v any
|
var rawValue any
|
||||||
has := switchMap(opConfig, &v)
|
hasKey := switchMapHasKey(operationConfig, &rawValue)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case has("source"):
|
case hasKey("source"):
|
||||||
value, ok := v.(string)
|
value, ok := rawValue.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
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`, rawValue, rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
opConfig[operation.ShortFormValueKey] = value
|
operationConfig[operation.ShortFormValueKey] = value
|
||||||
|
|
||||||
op, err := ParseOperation("source", opConfig)
|
op, err := ParseOperation("source", operationConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ops = append(ops, op)
|
ops = append(ops, op)
|
||||||
|
|
||||||
case has("target"):
|
case hasKey("target"):
|
||||||
value, ok := v.(string)
|
value, ok := rawValue.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
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`, rawValue, rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
opConfig[operation.ShortFormValueKey] = value
|
operationConfig[operation.ShortFormValueKey] = value
|
||||||
|
|
||||||
op, err := ParseOperation("target", opConfig)
|
op, err := ParseOperation("target", operationConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ops = append(ops, op)
|
ops = append(ops, op)
|
||||||
|
|
||||||
case has("use"):
|
case hasKey("use"):
|
||||||
name, ok := v.(string)
|
name, ok := rawValue.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
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`, rawValue, rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
op, err := ParseOperation(name, opConfig)
|
op, err := ParseOperation(name, operationConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -70,7 +71,7 @@ func ParsePipeline(p config.Pipeline) ([]cabret.Operation, error) {
|
|||||||
ops = append(ops, op)
|
ops = append(ops, op)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf(`pipeline entry is missing one of "use", "source" or "target", got %#v`, opConfig)
|
return nil, fmt.Errorf(`pipeline entry is missing one of "use", "source" or "target", got %#v`, operationConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-7
@@ -9,13 +9,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RunConfig(cfg *config.Cabretfile) error {
|
func RunConfig(cfg *config.Cabretfile) error {
|
||||||
|
files, err := cabret.FindFiles(cfg.Options.Excludes...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &cabret.Context{
|
||||||
|
Files: files,
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range cfg.Build {
|
for _, p := range cfg.Build {
|
||||||
ops, err := parse.ParsePipeline(p)
|
ops, err := parse.ParsePipeline(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := RunPipeline([]cabret.Content{}, ops); err != nil {
|
if _, err := RunPipeline(ops, ctx, []cabret.Content{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -23,10 +32,11 @@ func RunConfig(cfg *config.Cabretfile) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunPipeline(contents []cabret.Content, ops []cabret.Operation) ([]cabret.Content, error) {
|
// RunPipeline runs the given pipeline "ops" with the context ctx and initial contents "contents"
|
||||||
for _, op := range ops {
|
func RunPipeline(operations []cabret.Operation, ctx *cabret.Context, contents []cabret.Content) ([]cabret.Content, error) {
|
||||||
|
for _, op := range operations {
|
||||||
var err error
|
var err error
|
||||||
contents, err = RunOperation(op, contents)
|
contents, err = RunOperation(op, ctx, contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -35,15 +45,15 @@ func RunPipeline(contents []cabret.Content, ops []cabret.Operation) ([]cabret.Co
|
|||||||
return contents, nil
|
return contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunOperation(op cabret.Operation, inputs []cabret.Content) ([]cabret.Content, error) {
|
func RunOperation(op cabret.Operation, ctx *cabret.Context, inputs []cabret.Content) ([]cabret.Content, error) {
|
||||||
switch op := op.(type) {
|
switch op := op.(type) {
|
||||||
case cabret.ListOperation:
|
case cabret.ListOperation:
|
||||||
return op.ProcessList(inputs)
|
return op.ProcessList(ctx, inputs)
|
||||||
|
|
||||||
case cabret.ItemOperation:
|
case cabret.ItemOperation:
|
||||||
outputs := []cabret.Content{}
|
outputs := []cabret.Content{}
|
||||||
for _, item := range inputs {
|
for _, item := range inputs {
|
||||||
result, err := op.ProcessItem(item)
|
result, err := op.ProcessItem(ctx, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,30 +8,30 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FindFiles(excludes []string) ([]string, error) {
|
func FindFiles(excludePatterns ...string) ([]string, error) {
|
||||||
paths := []string{}
|
// TODO: Use [filepath.Glob] instead of [*path.Pattern]
|
||||||
|
|
||||||
excludeMatchers := []*path.Pattern{}
|
concreteExcludePatterns := []*path.Pattern{}
|
||||||
for _, p := range excludes {
|
for _, rawPattern := range excludePatterns {
|
||||||
m, err := path.ParsePattern(p)
|
pattern, err := path.ParsePattern(rawPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
excludeMatchers = append(excludeMatchers, m)
|
concreteExcludePatterns = append(concreteExcludePatterns, pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paths := []string{}
|
||||||
|
|
||||||
if err := filepath.Walk(".", func(p string, info fs.FileInfo, err error) error {
|
if err := filepath.Walk(".", func(p string, info fs.FileInfo, err error) error {
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
excluded := slices.ContainsFunc(excludeMatchers, func(excludePattern *path.Pattern) bool {
|
if slices.ContainsFunc(concreteExcludePatterns, func(pattern *path.Pattern) bool {
|
||||||
ok, _, _ := excludePattern.Match(p)
|
ok, _, _ := pattern.Match(p)
|
||||||
return ok
|
return ok
|
||||||
})
|
}) {
|
||||||
|
|
||||||
if excluded {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user