mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
perf(mustache): Removed lambda support.
It really wasn't benefitting us in any way.
This commit is contained in:
@@ -416,7 +416,6 @@ Spec-compliant implementation at `mustache/`. See `mustache/SPEC.md` for the imp
|
|||||||
| `diagnostic.odin` | Rust-style error formatter: `format_error` (multi-line context, ANSI colors via `core:terminal/ansi`, `colorize` param), `format_render_error` (formats `Error`), `line_col`, `line_text`, `context_extent`, `count_lines`, `digit_count`, `should_colorize`. |
|
| `diagnostic.odin` | Rust-style error formatter: `format_error` (multi-line context, ANSI colors via `core:terminal/ansi`, `colorize` param), `format_render_error` (formats `Error`), `line_col`, `line_text`, `context_extent`, `count_lines`, `digit_count`, `should_colorize`. |
|
||||||
| `suggest.odin` | Strict-warning helpers: `validate_key_path` (walks dotted path, crosses maps silently), `suggest_correction` (Levenshtein via `core:strings/levenshtein_distance`), `collect_struct_keys` (via reflection, recurses into `using`), `struct_has_field` (distinguishes missing field from nil value — needed for `Maybe(bool)`), `collect_partial_names`, `collect_block_names`. |
|
| `suggest.odin` | Strict-warning helpers: `validate_key_path` (walks dotted path, crosses maps silently), `suggest_correction` (Levenshtein via `core:strings/levenshtein_distance`), `collect_struct_keys` (via reflection, recurses into `using`), `struct_has_field` (distinguishes missing field from nil value — needed for `Maybe(bool)`), `collect_partial_names`, `collect_block_names`. |
|
||||||
| `spec_test.odin` | JSON spec test runner — loads `spec/specs/*.json`, runs each test case. Uses `log.nil_logger()` to suppress expected warnings. |
|
| `spec_test.odin` | JSON spec test runner — loads `spec/specs/*.json`, runs each test case. Uses `log.nil_logger()` to suppress expected warnings. |
|
||||||
| `lambda_test.odin` | Spec lambda tests |
|
|
||||||
| `pipes_test.odin` | Pipe filter tests (`group_by` + `format`) |
|
| `pipes_test.odin` | Pipe filter tests (`group_by` + `format`) |
|
||||||
| `diagnostic_test.odin` | Golden-output tests for `format_error` (multi-line context, edge cases, alignment, caret position, hint) + parser error message brace-escaping |
|
| `diagnostic_test.odin` | Golden-output tests for `format_error` (multi-line context, edge cases, alignment, caret position, hint) + parser error message brace-escaping |
|
||||||
| `suggest_test.odin` | Tests for `validate_key_path`, `suggest_correction`, `struct_has_field` with `Maybe(bool)` and `using`-promoted fields |
|
| `suggest_test.odin` | Tests for `validate_key_path`, `suggest_correction`, `struct_has_field` with `Maybe(bool)` and `using`-promoted fields |
|
||||||
@@ -460,13 +459,6 @@ Rust-style error messages with multi-line source context, caret underlines, and
|
|||||||
|
|
||||||
**Block override source tracking**: `Block_Override.source: Template` ensures warnings inside block overrides point at the override's source file (e.g., `page.html`), not the parent template (`base.html`).
|
**Block override source tracking**: `Block_Override.source: Template` ensures warnings inside block overrides point at the override's source file (e.g., `page.html`), not the parent template (`base.html`).
|
||||||
|
|
||||||
### Lambdas
|
|
||||||
|
|
||||||
Spec-compliant. Stored as `any` values in the data context.
|
|
||||||
|
|
||||||
- **Interpolation lambdas**: `proc() -> string`, `proc() -> int`, `proc() -> bool` — called via `call_interp_lambda`, result stringified and escaped.
|
|
||||||
- **Section lambdas**: `proc(string) -> string`, `proc(string) -> int`, `proc(string) -> bool` — called via `call_section_lambda` with the raw section text (`node.content`). String result is re-parsed as mustache and rendered against the current context stack.
|
|
||||||
|
|
||||||
### Pipes
|
### Pipes
|
||||||
|
|
||||||
`{{key | op args…}}` for interpolation, `{{#key | op args…}}…{{/key}}` for sections. Stored as `[dynamic; MAX_PIPES]Pipe_Filter` on each `Node`. Applied in the renderer via `apply_pipeline` before truthiness/interpolation. Implemented filters:
|
`{{key | op args…}}` for interpolation, `{{#key | op args…}}…{{/key}}` for sections. Stored as `[dynamic; MAX_PIPES]Pipe_Filter` on each `Node`. Applied in the renderer via `apply_pipeline` before truthiness/interpolation. Implemented filters:
|
||||||
|
|||||||
@@ -121,6 +121,9 @@
|
|||||||
- [ ] configure opt-out of automatic sections being added to menu.
|
- [ ] configure opt-out of automatic sections being added to menu.
|
||||||
- [ ] nested menus (i.e. `parent` support)
|
- [ ] nested menus (i.e. `parent` support)
|
||||||
- [ ] get rid of the global variables in the `treesitter` package.
|
- [ ] get rid of the global variables in the `treesitter` package.
|
||||||
|
- [ ] enforce heading structure.
|
||||||
|
- [ ] Either frontmatter.title set, or 1 h1 tag at top, not both
|
||||||
|
- [ ] No skipping.
|
||||||
- [ ] Consider using `or_else` when applying default values to structs. i.e.
|
- [ ] Consider using `or_else` when applying default values to structs. i.e.
|
||||||
```odin
|
```odin
|
||||||
package main
|
package main
|
||||||
|
|||||||
@@ -175,32 +175,6 @@ is_truthy :: proc(a: any) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
call_interp_lambda :: proc(val: any) -> (result: string, ok: bool) {
|
|
||||||
switch v in val {
|
|
||||||
case proc() -> string:
|
|
||||||
return v(), true
|
|
||||||
case proc() -> int:
|
|
||||||
return fmt.tprintf("%d", v()), true
|
|
||||||
case proc() -> bool:
|
|
||||||
return "true" if v() else "false", true
|
|
||||||
case:
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
call_section_lambda :: proc(val: any, text: string) -> (result: string, ok: bool) {
|
|
||||||
switch v in val {
|
|
||||||
case proc(_: string) -> string:
|
|
||||||
return v(text), true
|
|
||||||
case proc(_: string) -> int:
|
|
||||||
return fmt.tprintf("%d", v(text)), true
|
|
||||||
case proc(_: string) -> bool:
|
|
||||||
return "true" if v(text) else "false", true
|
|
||||||
case:
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// list_info returns element type info, count, and data pointer for a list value.
|
// list_info returns element type info, count, and data pointer for a list value.
|
||||||
// Returns elem_info=nil if the value is not a list.
|
// Returns elem_info=nil if the value is not a list.
|
||||||
list_info :: proc(a: any) -> (elem_info: ^runtime.Type_Info, count: int, data: rawptr) {
|
list_info :: proc(a: any) -> (elem_info: ^runtime.Type_Info, count: int, data: rawptr) {
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
#+test
|
|
||||||
package mustache
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
import "core:testing"
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// --- Spec test 1: Interpolation ---
|
|
||||||
// A lambda's return value should be interpolated.
|
|
||||||
|
|
||||||
Interp_Data :: struct {
|
|
||||||
lambda: proc() -> string,
|
|
||||||
planet: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
Interp_Data_Int :: struct {
|
|
||||||
lambda: proc() -> int,
|
|
||||||
planet: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_interpolation :: proc(t: ^testing.T) {
|
|
||||||
data := Interp_Data {
|
|
||||||
lambda = proc() -> string {return "world"},
|
|
||||||
}
|
|
||||||
tpl, _ := parse("Hello, {{lambda}}!", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "Hello, world!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 2: Interpolation - Expansion ---
|
|
||||||
// A lambda's return value should be parsed.
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_interpolation_expansion :: proc(t: ^testing.T) {
|
|
||||||
data := Interp_Data {
|
|
||||||
lambda = proc() -> string {return "{{planet}}"},
|
|
||||||
planet = "world",
|
|
||||||
}
|
|
||||||
tpl, _ := parse("Hello, {{lambda}}!", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "Hello, world!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 4: Interpolation - Multiple Calls ---
|
|
||||||
// Interpolated lambdas should not be cached.
|
|
||||||
|
|
||||||
counter_lambda :: proc() -> int {
|
|
||||||
@(static) call_count := 0
|
|
||||||
call_count += 1
|
|
||||||
return call_count
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_interpolation_multiple_calls :: proc(t: ^testing.T) {
|
|
||||||
data := Interp_Data_Int {
|
|
||||||
lambda = counter_lambda,
|
|
||||||
}
|
|
||||||
tpl, _ := parse("{{lambda}} == {{{lambda}}} == {{lambda}}", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "1 == 2 == 3")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 5: Escaping ---
|
|
||||||
// Lambda results should be appropriately escaped.
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_escaping :: proc(t: ^testing.T) {
|
|
||||||
data := Interp_Data {
|
|
||||||
lambda = proc() -> string {return ">"},
|
|
||||||
}
|
|
||||||
tpl, _ := parse("<{{lambda}}{{{lambda}}}", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "<>>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 6: Section ---
|
|
||||||
// Lambdas used for sections should receive the raw section string.
|
|
||||||
|
|
||||||
Section_Data :: struct {
|
|
||||||
lambda: proc(_: string) -> string,
|
|
||||||
x: string,
|
|
||||||
planet: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_section :: proc(t: ^testing.T) {
|
|
||||||
data := Section_Data {
|
|
||||||
lambda = proc(text: string) -> string {
|
|
||||||
if text == "{{x}}" {return "yes"} else {return "no"}
|
|
||||||
},
|
|
||||||
x = "Error!",
|
|
||||||
}
|
|
||||||
tpl, _ := parse("<{{#lambda}}{{x}}{{/lambda}}>", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "<yes>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 7: Section - Expansion ---
|
|
||||||
// Lambdas used for sections should have their results parsed.
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_section_expansion :: proc(t: ^testing.T) {
|
|
||||||
data := Section_Data {
|
|
||||||
lambda = proc(text: string) -> string {
|
|
||||||
return fmt.tprintf("%s{{{{planet}}}}%s", text, text)
|
|
||||||
},
|
|
||||||
planet = "Earth",
|
|
||||||
}
|
|
||||||
tpl, _ := parse("<{{#lambda}}-{{/lambda}}>", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "<-Earth->")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 9: Section - Multiple Calls ---
|
|
||||||
// Lambdas used for sections should not be cached.
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_section_multiple_calls :: proc(t: ^testing.T) {
|
|
||||||
data := Section_Data {
|
|
||||||
lambda = proc(text: string) -> string {
|
|
||||||
return fmt.tprintf("__%s__", text)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tpl, _ := parse(
|
|
||||||
"{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}",
|
|
||||||
"<test>",
|
|
||||||
context.temp_allocator,
|
|
||||||
)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "__FILE__ != __LINE__")
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Spec test 10: Inverted Section ---
|
|
||||||
// Lambdas used for inverted sections should be considered truthy.
|
|
||||||
|
|
||||||
Inverted_Data :: struct {
|
|
||||||
lambda: proc(_: string) -> bool,
|
|
||||||
static: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_lambda_inverted_section :: proc(t: ^testing.T) {
|
|
||||||
data := Inverted_Data {
|
|
||||||
lambda = proc(text: string) -> bool {return false},
|
|
||||||
static = "static",
|
|
||||||
}
|
|
||||||
tpl, _ := parse("<{{^lambda}}{{static}}{{/lambda}}>", "<test>", context.temp_allocator)
|
|
||||||
result, _ := render(tpl, data, {}, context.temp_allocator)
|
|
||||||
testing.expect_value(t, result, "<>")
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
+1
-65
@@ -625,30 +625,7 @@ render_nodes :: proc(
|
|||||||
}
|
}
|
||||||
val = transformed
|
val = transformed
|
||||||
}
|
}
|
||||||
if result_str, ok := call_interp_lambda(val); ok {
|
|
||||||
sub_tpl, perr := parse(
|
|
||||||
result_str,
|
|
||||||
fmt.tprintf("<lambda output from '%s'>", node.key),
|
|
||||||
context.temp_allocator,
|
|
||||||
context.temp_allocator,
|
|
||||||
)
|
|
||||||
if perr == nil {
|
|
||||||
temp: strings.Builder
|
|
||||||
strings.builder_init(&temp, context.temp_allocator)
|
|
||||||
render_nodes(
|
|
||||||
sub_tpl,
|
|
||||||
sub_tpl.nodes[:],
|
|
||||||
ctx,
|
|
||||||
partials,
|
|
||||||
&temp,
|
|
||||||
blocks,
|
|
||||||
nil,
|
|
||||||
) or_return
|
|
||||||
write_value(b, strings.to_string(temp), escape = true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
write_value(b, val, escape = true)
|
write_value(b, val, escape = true)
|
||||||
}
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
case .Unescaped:
|
case .Unescaped:
|
||||||
@@ -667,30 +644,7 @@ render_nodes :: proc(
|
|||||||
}
|
}
|
||||||
val = transformed
|
val = transformed
|
||||||
}
|
}
|
||||||
if result_str, ok := call_interp_lambda(val); ok {
|
|
||||||
sub_tpl, perr := parse(
|
|
||||||
result_str,
|
|
||||||
fmt.tprintf("<lambda output from '%s'>", node.key),
|
|
||||||
context.temp_allocator,
|
|
||||||
context.temp_allocator,
|
|
||||||
)
|
|
||||||
if perr == nil {
|
|
||||||
temp: strings.Builder
|
|
||||||
strings.builder_init(&temp, context.temp_allocator)
|
|
||||||
render_nodes(
|
|
||||||
sub_tpl,
|
|
||||||
sub_tpl.nodes[:],
|
|
||||||
ctx,
|
|
||||||
partials,
|
|
||||||
&temp,
|
|
||||||
blocks,
|
|
||||||
nil,
|
|
||||||
) or_return
|
|
||||||
write_value(b, strings.to_string(temp), escape = false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
write_value(b, val, escape = false)
|
write_value(b, val, escape = false)
|
||||||
}
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
case .Section:
|
case .Section:
|
||||||
@@ -705,25 +659,7 @@ render_nodes :: proc(
|
|||||||
}
|
}
|
||||||
val = transformed
|
val = transformed
|
||||||
}
|
}
|
||||||
if result_str, ok := call_section_lambda(val, node.content); ok {
|
if is_truthy(val) {
|
||||||
sub_tpl, perr := parse(
|
|
||||||
result_str,
|
|
||||||
fmt.tprintf("<lambda output from '%s'>", node.key),
|
|
||||||
context.temp_allocator,
|
|
||||||
context.temp_allocator,
|
|
||||||
)
|
|
||||||
if perr == nil {
|
|
||||||
render_nodes(
|
|
||||||
sub_tpl,
|
|
||||||
sub_tpl.nodes[:],
|
|
||||||
ctx,
|
|
||||||
partials,
|
|
||||||
b,
|
|
||||||
blocks,
|
|
||||||
nil,
|
|
||||||
) or_return
|
|
||||||
}
|
|
||||||
} else if is_truthy(val) {
|
|
||||||
children := node.children
|
children := node.children
|
||||||
elem_info, count, data := list_info(val)
|
elem_info, count, data := list_info(val)
|
||||||
if elem_info != nil {
|
if elem_info != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user