feat: renderer

This commit is contained in:
2025-08-19 17:20:24 +02:00
parent 6db87db325
commit 210b31aef8
38 changed files with 727 additions and 299 deletions

34
renderer/codec/codec.go Normal file
View File

@@ -0,0 +1,34 @@
// Package codec contains functions for rendering template.Block to a string.
package codec
import (
"fmt"
"git.max-richter.dev/max/marka/renderer/utils"
"git.max-richter.dev/max/marka/template"
)
// RenderBlock renders a single template block using the provided data.
func RenderBlock(block template.Block, data map[string]any) (string, error) {
if block.Type == template.MatchingBlock {
return block.GetContent(), nil
}
value, found := utils.GetValueFromPath(data, block.Path)
if !found {
return "", nil // If not found and not required, return empty string
}
switch block.Codec {
case template.CodecText:
return fmt.Sprintf("%v", value), nil
case template.CodecYaml:
return RenderYaml(data, block)
case template.CodecList:
return RenderList(value, block)
case template.CodecConst:
return fmt.Sprintf("%v", block.Value), nil
default:
return "", fmt.Errorf("unknown codec: %s for path '%s'", block.Codec, block.Path)
}
}

56
renderer/codec/list.go Normal file
View File

@@ -0,0 +1,56 @@
package codec
import (
"bytes"
"fmt"
"strings"
"git.max-richter.dev/max/marka/template"
)
func RenderList(value any, block template.Block) (string, error) {
list, ok := value.([]any)
if !ok {
return "", fmt.Errorf("expected list for path '%s', got %T", block.Path, value)
}
var renderedItems []string
// Compile the list template for each item
listBlocks, err := template.CompileTemplate(block.ListTemplate)
if err != nil {
return "", fmt.Errorf("failed to compile list template for path '%s': %w", block.Path, err)
}
for i, item := range list {
itemMap, isMap := item.(map[string]any)
if !isMap {
// If it's not a map, treat it as a simple value for the '.' path
itemMap = map[string]any{".": item}
}
var itemBuffer bytes.Buffer
for _, lb := range listBlocks {
if lb.Type == template.MatchingBlock {
itemBuffer.WriteString(lb.GetContent())
} else if lb.Type == template.DataBlock {
// Special handling for @index in list items
if lb.Path == "@index" {
itemBuffer.WriteString(fmt.Sprintf("%d", i+1))
continue
}
// Special handling for '.' path when item is not a map
if lb.Path == "." && !isMap {
itemBuffer.WriteString(fmt.Sprintf("%v", item))
continue
}
renderedItemPart, err := RenderBlock(lb, itemMap)
if err != nil {
return "", fmt.Errorf("failed to render list item part for path '%s': %w", lb.Path, err)
}
itemBuffer.WriteString(renderedItemPart)
}
}
renderedItems = append(renderedItems, itemBuffer.String())
}
return strings.Join(renderedItems, "\n"), nil
}

83
renderer/codec/yaml.go Normal file
View File

@@ -0,0 +1,83 @@
package codec
import (
"fmt"
"strings"
parserUtils "git.max-richter.dev/max/marka/parser/utils"
"git.max-richter.dev/max/marka/renderer/utils"
"git.max-richter.dev/max/marka/template"
"go.yaml.in/yaml/v4"
)
func flattenInto(in map[string]any) map[string]any {
out := make(map[string]any)
var recur func(prefix string, m map[string]any)
recur = func(prefix string, m map[string]any) {
for k, v := range m {
key := k
if prefix != "" {
key = prefix + "." + k
}
switch vv := v.(type) {
case map[string]any:
if len(vv) == 1 {
recur(key, vv)
} else {
out[key] = vv
}
case map[any]any:
tmp := make(map[string]any, len(vv))
for kk, vv2 := range vv {
if ks, ok := kk.(string); ok {
tmp[ks] = vv2
}
}
if len(tmp) == 1 {
recur(key, tmp)
} else {
out[key] = tmp
}
default:
out[key] = v
}
}
}
recur("", in)
return out
}
func RenderYaml(data map[string]any, block template.Block) (string, error) {
renderedMap := make(map[string]any)
for _, field := range block.Fields {
if field.Hidden {
continue
}
var fieldValue any
var found bool
if field.CodecType == template.CodecConst {
fieldValue = field.Value
found = true
} else {
fieldValue, found = utils.GetValueFromPath(data, field.Path)
}
if found {
renderedMap = parserUtils.SetPathValue(field.Path, fieldValue, renderedMap).(map[string]any)
}
}
renderedMap = flattenInto(renderedMap)
b, err := yaml.Marshal(renderedMap)
if err != nil {
return "", fmt.Errorf("failed to marshal YAML for path '%s': %w", block.Path, err)
}
return strings.TrimSuffix(string(b), "\n"), nil
}