mark/macro/macro.go

210 lines
3.8 KiB
Go
Raw Permalink Normal View History

2019-08-02 22:58:08 +03:00
package macro
import (
"bytes"
"fmt"
"regexp"
"strings"
"text/template"
2024-09-26 15:24:39 +02:00
"github.com/kovetskiy/mark/includes"
2019-08-02 22:58:08 +03:00
"github.com/reconquest/karma-go"
2020-11-03 17:12:51 +03:00
"github.com/reconquest/pkg/log"
2019-08-08 23:41:26 +03:00
"github.com/reconquest/regexputil-go"
"gopkg.in/yaml.v3"
2019-08-02 22:58:08 +03:00
)
var reMacroDirective = regexp.MustCompile(
2019-08-08 23:41:26 +03:00
// <!-- Macro: <regexp>
// Template: <template path>
// <optional yaml data> -->
`(?s)` + // dot capture newlines
/**/ `<!--\s*Macro:\s*(?P<expr>[^\n]+)\n` +
/* */ `\s*Template:\s*(?P<template>.+?)\s*` +
2020-04-13 16:56:06 +03:00
/* */ `(?P<config>\n.*?)?-->`,
2019-08-02 22:58:08 +03:00
)
type Macro struct {
Regexp *regexp.Regexp
Template *template.Template
2020-04-13 16:56:06 +03:00
Config string
2019-08-02 22:58:08 +03:00
}
func (macro *Macro) Apply(
content []byte,
) ([]byte, error) {
var err error
content = macro.Regexp.ReplaceAllFunc(
content,
func(match []byte) []byte {
2020-04-13 16:56:06 +03:00
config := map[string]interface{}{}
err = yaml.Unmarshal([]byte(macro.Config), &config)
if err != nil {
err = karma.Format(
err,
"unable to unmarshal macros config template",
)
}
2019-08-02 22:58:08 +03:00
var buffer bytes.Buffer
2020-04-13 16:56:06 +03:00
err = macro.Template.Execute(&buffer, macro.configure(
config,
macro.Regexp.FindSubmatch(match),
))
2019-08-02 22:58:08 +03:00
if err != nil {
err = karma.Format(
err,
"unable to execute macros template",
)
}
return buffer.Bytes()
},
)
return content, err
}
func (macro *Macro) configure(node interface{}, groups [][]byte) interface{} {
switch node := node.(type) {
case map[interface{}]interface{}:
for key, value := range node {
node[key] = macro.configure(value, groups)
}
return node
case map[string]interface{}:
for key, value := range node {
node[key] = macro.configure(value, groups)
}
return node
case []interface{}:
for key, value := range node {
node[key] = macro.configure(value, groups)
}
return node
case string:
for i, group := range groups {
node = strings.ReplaceAll(
node,
fmt.Sprintf("${%d}", i),
string(group),
)
}
return node
}
return node
}
2019-08-08 23:41:26 +03:00
func ExtractMacros(
base string,
includePath string,
2019-08-02 22:58:08 +03:00
contents []byte,
templates *template.Template,
) ([]Macro, []byte, error) {
var err error
var macros []Macro
contents = reMacroDirective.ReplaceAllFunc(
contents,
func(spec []byte) []byte {
if err != nil {
return spec
}
2019-08-08 23:41:26 +03:00
groups := reMacroDirective.FindStringSubmatch(string(spec))
2019-08-02 22:58:08 +03:00
var (
2019-08-08 23:41:26 +03:00
expr = regexputil.Subexp(reMacroDirective, groups, "expr")
template = regexputil.Subexp(
reMacroDirective,
groups,
"template",
)
config = regexputil.Subexp(reMacroDirective, groups, "config")
2019-08-02 22:58:08 +03:00
)
2022-06-07 14:21:11 +06:00
var macro Macro
2022-06-07 06:26:15 +02:00
if strings.HasPrefix(template, "#") {
cfg := map[string]interface{}{}
2019-08-02 22:58:08 +03:00
2022-06-07 06:26:15 +02:00
err = yaml.Unmarshal([]byte(config), &cfg)
if err != nil {
err = karma.Format(
err,
"unable to unmarshal macros config template",
)
2022-06-07 14:21:11 +06:00
return nil
}
body, ok := cfg[template[1:]].(string)
if !ok {
err = fmt.Errorf(
"the template config doesn't have '%s' field",
template[1:],
)
2022-06-07 06:26:15 +02:00
return nil
}
2022-06-07 14:21:11 +06:00
2022-06-07 06:26:15 +02:00
macro.Template, err = templates.New(template).Parse(body)
if err != nil {
err = karma.Format(
err,
"unable to parse template",
)
2022-06-07 14:21:11 +06:00
2022-06-07 06:26:15 +02:00
return nil
}
} else {
macro.Template, err = includes.LoadTemplate(base, includePath, template, "{{", "}}", templates)
2022-06-07 06:26:15 +02:00
if err != nil {
err = karma.Format(err, "unable to load template")
2022-06-07 14:21:11 +06:00
2022-06-07 06:26:15 +02:00
return nil
}
2019-08-02 22:58:08 +03:00
}
facts := karma.
2019-08-08 23:41:26 +03:00
Describe("template", template).
Describe("expr", expr)
2019-08-02 22:58:08 +03:00
2019-08-08 23:41:26 +03:00
macro.Regexp, err = regexp.Compile(expr)
2019-08-02 22:58:08 +03:00
if err != nil {
err = facts.
Format(
err,
"unable to compile macros regexp",
)
return nil
}
2020-04-13 16:56:06 +03:00
macro.Config = config
2019-08-02 22:58:08 +03:00
log.Tracef(
facts.Describe("config", macro.Config),
"loaded macro %q",
expr,
)
macros = append(macros, macro)
return []byte{}
},
)
return macros, contents, err
}