From b779cd68a58df85465f1389774ab588113f0bff8 Mon Sep 17 00:00:00 2001 From: Spencer Brower <6729162+sbrow@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:25:25 -0400 Subject: [PATCH] feat: .Warning added to Error_Kind. --- TODOS.md | 5 +++ mustache/mustache.odin | 71 ++++++++++++++++++++++++------------------ mustache/pipes.odin | 55 +++++++++++++++++++------------- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/TODOS.md b/TODOS.md index 51abae5..718f913 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,5 +1,10 @@ # TODOs +## Priority Unclear (Evaluate Later) + +- [ ] instead of `warnings: [dynamic]Error` we should use `warnings: [dynamic; 8]Error` + - when limit reached, the template fails and stops rendering. + ## General Remember to polish existing features before moving on to new ones. diff --git a/mustache/mustache.odin b/mustache/mustache.odin index 9bced01..537ab91 100644 --- a/mustache/mustache.odin +++ b/mustache/mustache.odin @@ -23,8 +23,9 @@ Context_Stack :: [dynamic]any // --------------------------------------------------------------------------- Error_Kind :: enum { - Syntax, // parse-time: malformed template - Data, // render-time: template fine, data wrong (e.g. filter misuse) + Syntax, // parse-time: malformed template + Data, // render-time: template fine, data wrong (e.g. filter misuse) + Warning, // non-fatal: operation succeeded but user should know } Error_Body :: struct { @@ -176,6 +177,7 @@ render :: proc( data: any, partials: map[string]Template = nil, allocator := context.allocator, + warnings: ^[dynamic]Error = nil, ) -> ( result: string, err: Error, @@ -201,7 +203,7 @@ render :: proc( } all_nodes := tmpl.nodes[:] - err = render_nodes(tmpl, all_nodes, &ctx, partials, &builder) + err = render_nodes(tmpl, all_nodes, &ctx, partials, &builder, warnings = warnings) if err != nil { return result, err } @@ -545,6 +547,7 @@ render_template :: proc( b: ^strings.Builder, blocks: map[string]Block_Override, indent: string, + warnings: ^[dynamic]Error = nil, ) -> Error { if len(indent) > 0 { state := Indent_State { @@ -552,9 +555,9 @@ render_template :: proc( at_line_start = false, } strings.write_string(b, indent) // first line always gets indent - return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, &state) + return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, &state, warnings) } - return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, nil) + return render_nodes(pt, pt.nodes[:], ctx, partials, b, blocks, nil, warnings) } write_indented :: proc( @@ -598,6 +601,7 @@ render_nodes :: proc( b: ^strings.Builder, blocks: map[string]Block_Override = nil, indent_state: ^Indent_State = nil, + warnings: ^[dynamic]Error = nil, ) -> Error { i := 0 for i < len(nodes) { @@ -621,7 +625,7 @@ render_nodes :: proc( warn_unknown_key(current, ctx[:], node) } if len(node.filters) > 0 { - transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) + transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings) if perr != nil { return tag_error(perr, current) } @@ -640,7 +644,7 @@ render_nodes :: proc( warn_unknown_key(current, ctx[:], node) } if len(node.filters) > 0 { - transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) + transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings) if perr != nil { return tag_error(perr, current) } @@ -655,7 +659,7 @@ render_nodes :: proc( warn_unknown_key(current, ctx[:], node) } if len(node.filters) > 0 { - transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) + transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings) if perr != nil { return tag_error(perr, current) } @@ -669,15 +673,16 @@ render_nodes :: proc( elem := extract_list_element(elem_info, data, j) context_push(ctx, elem, current, node) defer pop(ctx) - render_nodes( - current, - children, - ctx, - partials, - b, - blocks, - indent_state, - ) or_return + render_nodes( + current, + children, + ctx, + partials, + b, + blocks, + indent_state, + warnings, + ) or_return } } else { context_push(ctx, val, current, node) @@ -690,6 +695,7 @@ render_nodes :: proc( b, blocks, indent_state, + warnings, ) or_return } } @@ -701,23 +707,24 @@ render_nodes :: proc( warn_unknown_key(current, ctx[:], node) } if len(node.filters) > 0 { - transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current) + transformed, perr := apply_pipeline(val, node.filters[:], node.pos, ctx[:], current, warnings) if perr != nil { return tag_error(perr, current) } val = transformed } - if !is_truthy(val) { - render_nodes( - current, - node.children, - ctx, - partials, - b, - blocks, - indent_state, - ) or_return - } + if !is_truthy(val) { + render_nodes( + current, + node.children, + ctx, + partials, + b, + blocks, + indent_state, + warnings, + ) or_return + } i += 1 + len(node.children) case .Partial: @@ -730,7 +737,7 @@ render_nodes :: proc( if !found { warn_missing_partial(current, partials, node, name) } else { - render_template(pt, ctx, partials, b, nil, node.indent) or_return + render_template(pt, ctx, partials, b, nil, node.indent, warnings) or_return if indent_state != nil { indent_state.at_line_start = false } @@ -765,6 +772,7 @@ render_nodes :: proc( &temp, content_blocks, nil, + warnings, ) or_return at_ls := true write_indented(b, node.indent, strings.to_string(temp), &at_ls) @@ -777,6 +785,7 @@ render_nodes :: proc( b, content_blocks, indent_state, + warnings, ) or_return } i += 1 + len(node.children) @@ -789,7 +798,7 @@ render_nodes :: proc( warn_missing_partial(current, partials, node, node.key) } else { warn_unmatched_block_overrides(current, pt, parent_children) - render_template(pt, ctx, partials, b, merged, node.indent) or_return + render_template(pt, ctx, partials, b, merged, node.indent, warnings) or_return if indent_state != nil { indent_state.at_line_start = false } diff --git a/mustache/pipes.odin b/mustache/pipes.odin index 36f7962..9bf35f7 100644 --- a/mustache/pipes.odin +++ b/mustache/pipes.odin @@ -221,14 +221,26 @@ apply_pipeline :: proc( pos: int, ctx: []any, tmpl: Template, + warnings: ^[dynamic]Error = nil, ) -> ( current: any, err: Error, ) { current = value for &filter in filters { - current = apply_filter(current, &filter, pos, ctx, tmpl) or_return - log.debugf("applied: filter=%v before=%s after=%s pos=%d", filter, value, current, pos) + result, ferr := apply_filter(current, &filter, pos, ctx, tmpl) + if ferr != nil { + b := body(ferr) + if b.kind != .Warning { + return result, ferr + } + if warnings != nil { + append(warnings, ferr) + } else { + log.warnf("%s", format_render_error(ferr, tmpl)) + } + } + current = result } return } @@ -438,6 +450,7 @@ take :: proc( elem_info, count, data := list_info(value) is_list := elem_info != nil from_start := from_start + warning: Error = nil str: string if !is_list { @@ -457,15 +470,14 @@ take :: proc( n := 1 if !is_list && len(args) == 0 { - path := tmpl.path != "" ? tmpl.path : "" - diag := format_tag_error( - path, - tmpl.source, - op_pos, - "defaulting to n=1 for string", - "consider specifying n explicitly", - ) - log.warnf("%s", diag) + warning = Error_Body { + msg = "defaulting to n=1 for string", + pos = tag_pos, + kind = .Warning, + source = tmpl.source, + path = tmpl.path, + hint = "consider specifying n explicitly", + } } if len(args) > 0 { parsed, ok := strconv.parse_int(args[0]) @@ -494,15 +506,14 @@ take :: proc( if n < 0 { op_name := from_start ? "first" : "last" alt_name := from_start ? "last" : "first" - path := tmpl.path != "" ? tmpl.path : "" - diag := format_tag_error( - path, - tmpl.source, - op_pos, - fmt.tprintf("%s %d is equivalent to %s %d", op_name, n, alt_name, 0 - n), - "consider using the clearer form", - ) - log.warnf("%s", diag) + warning = Error_Body { + msg = fmt.tprintf("%s %d is equivalent to %s %d", op_name, n, alt_name, 0 - n), + pos = tag_pos, + kind = .Warning, + source = tmpl.source, + path = tmpl.path, + hint = "consider using the clearer form", + } from_start = !from_start n = 0 - n } @@ -522,9 +533,9 @@ take :: proc( for j in start ..< end { append(&result, extract_list_element(elem_info, data, j)) } - return result, nil + return any{new_clone(result, context.temp_allocator), typeid_of([dynamic]any)}, warning } else { - return str[start:end], nil + return any{new_clone(str[start:end], context.temp_allocator), typeid_of(string)}, warning } }