feat: added keyword codec (partially works)
This commit is contained in:
69
renderer/encoders/encoders.go
Normal file
69
renderer/encoders/encoders.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Package encoders contains functions for rendering template.Block to a string.
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.max-richter.dev/max/marka/renderer/utils"
|
||||
"git.max-richter.dev/max/marka/template"
|
||||
)
|
||||
|
||||
const emptyBlock = "\uE000"
|
||||
|
||||
func fixRenderedBlock(input string) string {
|
||||
input = strings.ReplaceAll(input, "'@type':", "@type:")
|
||||
input = strings.ReplaceAll(input, "'@context':", "@context:")
|
||||
if len(input) == 0 {
|
||||
return emptyBlock
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
func Render(data map[string]any, blocks []template.Block) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
for _, block := range blocks {
|
||||
renderedContent, err := RenderBlock(block, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to render block for path '%s': %w", block.Path, err)
|
||||
}
|
||||
renderedContent = fixRenderedBlock(renderedContent)
|
||||
buffer.WriteString(renderedContent)
|
||||
}
|
||||
|
||||
outputString := buffer.String()
|
||||
outputString = strings.ReplaceAll(outputString, "\n\n"+emptyBlock+"\n\n", "\n\n")
|
||||
outputString = strings.ReplaceAll(outputString, "\n"+emptyBlock+"\n", "\n")
|
||||
outputString = strings.ReplaceAll(outputString, emptyBlock, "")
|
||||
if !strings.HasSuffix(outputString, "\n") {
|
||||
outputString += "\n"
|
||||
}
|
||||
|
||||
return outputString, nil
|
||||
}
|
56
renderer/encoders/list.go
Normal file
56
renderer/encoders/list.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package encoders
|
||||
|
||||
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/encoders/yaml.go
Normal file
83
renderer/encoders/yaml.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package encoders
|
||||
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user