big tings

This commit is contained in:
Max Richter
2025-08-17 15:16:17 +02:00
parent 40b9be887d
commit c687eff53d
958 changed files with 32279 additions and 704 deletions

View File

@@ -0,0 +1,41 @@
// Package decoders contains functions for parsing template.Block to a string.
package decoders
import (
"fmt"
"git.max-richter.dev/max/marka/parser/matcher"
"git.max-richter.dev/max/marka/parser/utils"
"git.max-richter.dev/max/marka/template"
)
func ParseBlock(input string, block template.Block) (any, error) {
switch block.Codec {
case template.CodecText:
return input, nil
case template.CodecYaml:
return Yaml(input, block)
case template.CodecList:
return List(input, block)
}
return nil, fmt.Errorf("unknown codec: %s", block.Codec)
}
func Parse(matches []matcher.Block) (any, error) {
var result any
for _, m := range matches {
if m.Block.Path == "@index" {
continue
}
input := m.GetContent()
value, err := ParseBlock(input, m.Block)
if err != nil {
return nil, fmt.Errorf("failed to parse block(%s): %w", m.Block.Path, err)
}
result = utils.SetPathValue(m.Block.Path, value, result)
}
return result, nil
}

View File

@@ -0,0 +1,56 @@
package decoders_test
import (
"encoding/json"
"fmt"
"testing"
"git.max-richter.dev/max/marka/parser/decoders"
"git.max-richter.dev/max/marka/parser/matcher"
"git.max-richter.dev/max/marka/parser/utils"
"git.max-richter.dev/max/marka/registry"
"git.max-richter.dev/max/marka/template"
)
func TestParseBaguette(t *testing.T) {
recipeMd := utils.ReadTestDataFile(t, "baguette.md")
templateContent, err := registry.GetTemplate("Recipe")
if err != nil {
t.Fatalf("Err: %s", err)
}
blocks, err := template.CompileTemplate(templateContent)
if err != nil {
t.Fatalf("Err: %s", err)
}
matches := matcher.MatchBlocksFuzzy(recipeMd, blocks, 0.3)
parsed, err := decoders.Parse(matches)
if err != nil {
t.Fatalf("Err: %s", err)
}
expected := map[string]any{
"name": "Baguette",
"description": "My favourite baguette recipe",
"recipeIngredient": []string{"Flour", "Water", "Salt"},
"recipeInstructions": []string{
"Mix Flour Water and Salt",
"Bake the bread",
},
}
out, _ := json.MarshalIndent(parsed, "", " ")
fmt.Printf("Parsed: \n%s\n", string(out))
outMap, ok := parsed.(map[string]any)
if !ok {
t.Fatalf("expected parsed to be map[string]any, got %T", parsed)
}
for k, v := range expected {
if fmt.Sprintf("%v", outMap[k]) != fmt.Sprintf("%v", v) {
t.Errorf("Expected %v but got %v", v, outMap[k])
}
}
}

31
parser/decoders/list.go Normal file
View File

@@ -0,0 +1,31 @@
package decoders
import (
"fmt"
"strings"
"git.max-richter.dev/max/marka/parser/matcher"
"git.max-richter.dev/max/marka/template"
)
func List(input string, block template.Block) (value any, error error) {
blocks, err := template.CompileTemplate(block.ListTemplate)
if err != nil {
return nil, fmt.Errorf("cannot extract blocks: %w", err)
}
var out []any
for line := range strings.SplitSeq(strings.TrimSuffix(input, "\n"), "\n") {
matches := matcher.MatchBlocksFuzzy(line, blocks, 0.3)
res, err := Parse(matches)
if err != nil {
return nil, fmt.Errorf("could not match blocks: %w", err)
}
out = append(out, res)
}
return out, nil
}

View File

@@ -0,0 +1,90 @@
package decoders_test
import (
"reflect"
"testing"
"git.max-richter.dev/max/marka/parser/decoders"
"git.max-richter.dev/max/marka/template"
)
func TestDecodeListObject(t *testing.T) {
templateBlock := template.Block{
Path: "ingredients",
Codec: template.CodecList,
ListTemplate: "- { amount } { type }",
}
input := "- 10g flour\n- 1/2cup water\n- 1tsp salt"
parsed, err := decoders.List(input, templateBlock)
if err != nil {
t.Fatalf("Err: %s", err)
}
want := []any{
map[string]any{
"amount": "10g",
"type": "flour",
},
map[string]any{
"amount": "1/2cup",
"type": "water",
},
map[string]any{
"amount": "1tsp",
"type": "salt",
},
}
if !reflect.DeepEqual(parsed, want) {
t.Fatalf("unexpected result.\n got: %#v\nwant: %#v", parsed, want)
}
}
func TestDecodeListString(t *testing.T) {
templateBlock := template.Block{
Path: "ingredients",
Codec: template.CodecList,
ListTemplate: "- { . }",
}
input := "- flour\n- water\n- salt"
parsed, err := decoders.List(input, templateBlock)
if err != nil {
t.Fatalf("Err: %s", err)
}
want := []any{
"flour",
"water",
"salt",
}
if !reflect.DeepEqual(parsed, want) {
t.Fatalf("unexpected result.\n got: %#v\nwant: %#v", parsed, want)
}
}
func TestDecodeNumberedListString(t *testing.T) {
templateBlock := template.Block{
Path: "ingredients",
Codec: template.CodecList,
ListTemplate: "{ @index } { . }",
}
input := "1. Wash and dry the lettuce.\n2. Halve the cherry tomatoes.\n3. Toss with olive oil and salt."
parsed, err := decoders.List(input, templateBlock)
if err != nil {
t.Fatalf("Err: %s", err)
}
want := []any{
"Wash and dry the lettuce.",
"Halve the cherry tomatoes.",
"Toss with olive oil and salt.",
}
if !reflect.DeepEqual(parsed, want) {
t.Fatalf("unexpected result.\n got: %#v\nwant: %#v", parsed, want)
}
}

32
parser/decoders/yaml.go Normal file
View File

@@ -0,0 +1,32 @@
package decoders
import (
"fmt"
"git.max-richter.dev/max/marka/parser/utils"
"git.max-richter.dev/max/marka/template"
"go.yaml.in/yaml/v4"
)
func Yaml(input string, block template.Block) (value any, error error) {
res := make(map[string]any)
err := yaml.Unmarshal([]byte(input), &res)
if err != nil {
return nil, fmt.Errorf("failed to parse yaml '%q': %w", input, err)
}
var out any
for _, f := range block.Fields {
if f.CodecType == template.CodecConst {
if f.Value != nil {
out = utils.SetPathValue(f.Path, f.Value, out)
}
} else {
if value, ok := res[f.Path]; ok {
out = utils.SetPathValue(f.Path, value, out)
}
}
}
return out, nil
}