181 lines
4.0 KiB
Go
181 lines
4.0 KiB
Go
// Package template contains the logic for parsing template blocks.
|
|
package template
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
// TemplateType represents whether a template is short, long, or invalid.
|
|
type TemplateType int
|
|
|
|
const (
|
|
InvalidTemplate TemplateType = iota
|
|
ShortTemplate
|
|
ExtendedTemplate
|
|
)
|
|
|
|
// DetectTemplateType checks if the template is short or long.
|
|
func DetectTemplateType(tmpl string) TemplateType {
|
|
trimmed := strings.TrimSpace(tmpl)
|
|
|
|
// Short type: starts with "{" and ends with "}" on a single line,
|
|
// and contains "|" or "," inside for inline definition
|
|
// Matchs for example { name | text,required }
|
|
if strings.HasPrefix(trimmed, "{") &&
|
|
strings.HasSuffix(trimmed, "}") &&
|
|
!strings.Contains(trimmed, "\n") {
|
|
return ShortTemplate
|
|
}
|
|
|
|
// Long type: multiline and contains keys like "path:" or "codec:" inside
|
|
// Matches for example:
|
|
// {
|
|
// path: name
|
|
// codec: text
|
|
// required: true
|
|
// }
|
|
if strings.Contains(trimmed, "\n") &&
|
|
(strings.Contains(trimmed, "path:") || strings.Contains(trimmed, "codec:")) {
|
|
return ExtendedTemplate
|
|
}
|
|
|
|
return InvalidTemplate
|
|
}
|
|
|
|
func cleanTemplate(input string) string {
|
|
s := strings.TrimSpace(input)
|
|
s = strings.TrimPrefix(s, "{")
|
|
s = strings.TrimSuffix(s, "}")
|
|
s = strings.Trim(s, "\n")
|
|
return s
|
|
}
|
|
|
|
func parseShortTemplate(input string) (Block, error) {
|
|
split := strings.Split(cleanTemplate(input), "|")
|
|
if len(split) < 1 {
|
|
return Block{}, fmt.Errorf("invalid short template")
|
|
}
|
|
|
|
block := Block{
|
|
Type: DataBlock,
|
|
Path: strings.TrimSpace(split[0]),
|
|
Codec: CodecText,
|
|
content: input,
|
|
}
|
|
|
|
if len(split) > 1 {
|
|
optionSplit := strings.SplitSeq(split[1], ",")
|
|
for option := range optionSplit {
|
|
switch strings.TrimSpace(option) {
|
|
case "number":
|
|
block.Codec = CodecNumber
|
|
case "text":
|
|
block.Codec = CodecText
|
|
case "hashtags":
|
|
block.Codec = CodecHashtags
|
|
default:
|
|
return block, fmt.Errorf("unknown codec option: %s", option)
|
|
}
|
|
}
|
|
}
|
|
|
|
return block, nil
|
|
}
|
|
|
|
type yamlBlock struct {
|
|
Path string `yaml:"path"`
|
|
Codec string `yaml:"codec"`
|
|
Value any `yaml:"value,omitempty"`
|
|
Fields []yamlField `yaml:"fields"`
|
|
ListTemplate string `yaml:"listTemplate,omitempty"`
|
|
Hidden bool `yaml:"hidden,omitempty"`
|
|
}
|
|
|
|
type yamlField struct {
|
|
Path string `yaml:"path"`
|
|
Value any `yaml:"value,omitempty"`
|
|
Codec string `yaml:"codec"`
|
|
Hidden bool `yaml:"hidden,omitempty"`
|
|
}
|
|
|
|
func parseYamlTemplate(input string) (block Block, err error) {
|
|
var blk yamlBlock
|
|
|
|
cleaned := cleanTemplate(input)
|
|
|
|
dec := yaml.NewDecoder(strings.NewReader(cleaned))
|
|
dec.KnownFields(true)
|
|
|
|
if err := dec.Decode(&blk); err != nil {
|
|
return block, fmt.Errorf("content '%q': %w", cleaned, err)
|
|
}
|
|
|
|
if blk.Path == "" {
|
|
return block, fmt.Errorf("missing top-level 'path'")
|
|
}
|
|
|
|
if blk.Codec == "" {
|
|
blk.Codec = "text"
|
|
}
|
|
|
|
codec, err := parseCodecType(blk.Codec)
|
|
if err != nil {
|
|
return block, fmt.Errorf("failed to parse codec: %w", err)
|
|
}
|
|
|
|
var fields []BlockField
|
|
|
|
for _, field := range blk.Fields {
|
|
if field.Path == "" {
|
|
return block, fmt.Errorf("failed to parse field: %v", field)
|
|
}
|
|
|
|
if field.Codec == "" {
|
|
field.Codec = "text"
|
|
}
|
|
|
|
fieldCodec, err := parseCodecType(field.Codec)
|
|
if err != nil {
|
|
return block, fmt.Errorf("failed to parse codec: %w", err)
|
|
}
|
|
|
|
fields = append(fields, BlockField{
|
|
Path: field.Path,
|
|
CodecType: fieldCodec,
|
|
Value: field.Value,
|
|
Hidden: field.Hidden,
|
|
})
|
|
|
|
}
|
|
|
|
return Block{
|
|
Type: DataBlock,
|
|
Path: blk.Path,
|
|
Codec: codec,
|
|
Fields: fields,
|
|
ListTemplate: blk.ListTemplate,
|
|
content: input,
|
|
}, nil
|
|
}
|
|
|
|
func ParseTemplateBlock(template string, blockType BlockType) (block Block, err error) {
|
|
if blockType == MatchingBlock {
|
|
return Block{
|
|
Type: MatchingBlock,
|
|
content: template,
|
|
}, nil
|
|
}
|
|
|
|
switch DetectTemplateType(template) {
|
|
case ShortTemplate:
|
|
return parseShortTemplate(template)
|
|
case ExtendedTemplate:
|
|
return parseYamlTemplate(template)
|
|
}
|
|
|
|
return block, fmt.Errorf("invalid template")
|
|
}
|