Wiki Template-First Rendering (#9)
Reviewed-on: https://gitea.westgate.pw/ShadowsOverWestgate/sow-tools/pulls/9 Co-authored-by: vickydotbat <vickydotbat@tutamail.com> Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit is contained in:
@@ -82,37 +82,186 @@ func builtinWikiTemplate(category string) string {
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderWikiTemplateString(path, source string, page wikiTemplatePage) (string, error) {
|
||||
state := &wikiTemplateRenderState{
|
||||
path: path,
|
||||
page: page,
|
||||
seenSections: map[string]struct{}{},
|
||||
active: true,
|
||||
}
|
||||
rendered, offset, closed, err := ctx.renderWikiTemplateRange(source, 0, "", state)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if closed {
|
||||
return "", fmt.Errorf("render wiki template %s for %s: unexpected closing block", path, page.PageID)
|
||||
}
|
||||
if offset != len(source) {
|
||||
return "", fmt.Errorf("render wiki template %s for %s: template parse stopped early", path, page.PageID)
|
||||
}
|
||||
return strings.TrimSpace(rendered), nil
|
||||
}
|
||||
|
||||
type wikiTemplateRenderState struct {
|
||||
path string
|
||||
page wikiTemplatePage
|
||||
row map[string]any
|
||||
dot any
|
||||
seenSections map[string]struct{}
|
||||
active bool
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderWikiTemplateRange(source string, offset int, stopBlock string, state *wikiTemplateRenderState) (string, int, bool, error) {
|
||||
var out strings.Builder
|
||||
offset := 0
|
||||
seenSections := map[string]struct{}{}
|
||||
for {
|
||||
start := strings.Index(source[offset:], "{{")
|
||||
if start < 0 {
|
||||
out.WriteString(source[offset:])
|
||||
break
|
||||
if stopBlock != "" {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: missing {{/%s}}", state.path, state.page.PageID, stopBlock)
|
||||
}
|
||||
if state.active {
|
||||
out.WriteString(source[offset:])
|
||||
}
|
||||
return out.String(), len(source), false, nil
|
||||
}
|
||||
start += offset
|
||||
end := strings.Index(source[start+2:], "}}")
|
||||
if end < 0 {
|
||||
return "", fmt.Errorf("render wiki template %s for %s: unclosed placeholder", path, page.PageID)
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: unclosed placeholder", state.path, state.page.PageID)
|
||||
}
|
||||
end += start + 2
|
||||
out.WriteString(source[offset:start])
|
||||
if state.active {
|
||||
out.WriteString(source[offset:start])
|
||||
}
|
||||
token := strings.TrimSpace(source[start+2 : end])
|
||||
rendered, sectionID, err := ctx.renderWikiTemplateToken(path, token, page)
|
||||
if strings.HasPrefix(token, "/") {
|
||||
name := strings.TrimSpace(strings.TrimPrefix(token, "/"))
|
||||
if stopBlock == "" || name != stopBlock {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: unexpected {{/%s}}", state.path, state.page.PageID, name)
|
||||
}
|
||||
return out.String(), end + 2, true, nil
|
||||
}
|
||||
if strings.HasPrefix(token, "#each ") {
|
||||
providerName := strings.TrimSpace(strings.TrimPrefix(token, "#each "))
|
||||
body, nextOffset, err := extractWikiTemplateBlock(source, end+2, "each", state.path, state.page.PageID)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if state.active {
|
||||
rows, err := ctx.wikiDataProviderRows(providerName, state.page)
|
||||
if err != nil {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: %w", state.path, state.page.PageID, err)
|
||||
}
|
||||
for _, row := range rows {
|
||||
child := *state
|
||||
child.row = row
|
||||
rendered, childOffset, closed, err := ctx.renderWikiTemplateRange(body, 0, "", &child)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if closed || childOffset != len(body) {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: malformed each body", state.path, state.page.PageID)
|
||||
}
|
||||
out.WriteString(rendered)
|
||||
}
|
||||
}
|
||||
offset = nextOffset
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(token, "#if ") {
|
||||
expr := strings.TrimSpace(strings.TrimPrefix(token, "#if "))
|
||||
body, nextOffset, err := extractWikiTemplateBlock(source, end+2, "if", state.path, state.page.PageID)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if state.active {
|
||||
value, err := ctx.evalWikiTemplateExpression(expr, state)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if truthyWikiTableValue(value) {
|
||||
child := *state
|
||||
rendered, childOffset, closed, err := ctx.renderWikiTemplateRange(body, 0, "", &child)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if closed || childOffset != len(body) {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: malformed if body", state.path, state.page.PageID)
|
||||
}
|
||||
out.WriteString(rendered)
|
||||
}
|
||||
}
|
||||
offset = nextOffset
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(token, "#with ") {
|
||||
expr := strings.TrimSpace(strings.TrimPrefix(token, "#with "))
|
||||
body, nextOffset, err := extractWikiTemplateBlock(source, end+2, "with", state.path, state.page.PageID)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if state.active {
|
||||
value, err := ctx.evalWikiTemplateExpression(expr, state)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if truthyWikiTableValue(value) {
|
||||
child := *state
|
||||
child.dot = value
|
||||
rendered, childOffset, closed, err := ctx.renderWikiTemplateRange(body, 0, "", &child)
|
||||
if err != nil {
|
||||
return "", offset, false, err
|
||||
}
|
||||
if closed || childOffset != len(body) {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: malformed with body", state.path, state.page.PageID)
|
||||
}
|
||||
out.WriteString(rendered)
|
||||
}
|
||||
}
|
||||
offset = nextOffset
|
||||
continue
|
||||
}
|
||||
if !state.active {
|
||||
offset = end + 2
|
||||
continue
|
||||
}
|
||||
rendered, sectionID, err := ctx.renderWikiTemplateTokenWithState(token, state)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", offset, false, err
|
||||
}
|
||||
if sectionID != "" {
|
||||
if _, exists := seenSections[sectionID]; exists {
|
||||
return "", fmt.Errorf("render wiki template %s for %s: duplicate preserved section %q", path, page.PageID, sectionID)
|
||||
if _, exists := state.seenSections[sectionID]; exists {
|
||||
return "", offset, false, fmt.Errorf("render wiki template %s for %s: duplicate preserved section %q", state.path, state.page.PageID, sectionID)
|
||||
}
|
||||
seenSections[sectionID] = struct{}{}
|
||||
state.seenSections[sectionID] = struct{}{}
|
||||
}
|
||||
out.WriteString(rendered)
|
||||
offset = end + 2
|
||||
}
|
||||
return strings.TrimSpace(out.String()), nil
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderWikiTemplateTokenWithState(token string, state *wikiTemplateRenderState) (string, string, error) {
|
||||
if strings.HasPrefix(token, "format:") || strings.HasPrefix(token, "table:") || strings.HasPrefix(token, "field:") || strings.HasPrefix(token, "section:") {
|
||||
rendered, sectionID, err := ctx.renderWikiTemplateToken(state.path, token, state.page)
|
||||
return rendered, sectionID, err
|
||||
}
|
||||
if strings.Contains(token, "|") || strings.Contains(token, "(") || token == "." || strings.HasPrefix(token, "page.") {
|
||||
value, err := ctx.evalWikiTemplateExpression(token, state)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return stringifyWikiTemplateOutput(value), "", nil
|
||||
}
|
||||
if state.row != nil {
|
||||
if _, ok := ctx.resolveWikiTemplateScopedValue(token, state); ok {
|
||||
value, err := ctx.evalWikiTemplateExpression(token, state)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return stringifyWikiTemplateOutput(value), "", nil
|
||||
}
|
||||
}
|
||||
rendered, sectionID, err := ctx.renderWikiTemplateToken(state.path, token, state.page)
|
||||
return rendered, sectionID, err
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderWikiTemplateToken(path, token string, page wikiTemplatePage) (string, string, error) {
|
||||
@@ -127,6 +276,13 @@ func (ctx *wikiContext) renderWikiTemplateToken(path, token string, page wikiTem
|
||||
if section, ok := wikiManualSectionForAlias(page.ManualSections, token); ok {
|
||||
return renderWikiManualSection(section), section.ID, nil
|
||||
}
|
||||
if strings.HasPrefix(token, "section:") {
|
||||
sectionName := strings.TrimSpace(strings.TrimPrefix(token, "section:"))
|
||||
if section, ok := wikiManualSectionForAlias(page.ManualSections, sectionName); ok {
|
||||
return renderWikiManualSection(section), section.ID, nil
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
if strings.HasPrefix(token, "format:") {
|
||||
rendered, err := ctx.renderWikiFormatter(strings.TrimSpace(strings.TrimPrefix(token, "format:")), page)
|
||||
return rendered, "", err
|
||||
|
||||
Reference in New Issue
Block a user