mirror of
https://github.com/sbrow/thor.git
synced 2026-10-25 10:11:55 -04:00
test: Converted DIAGNOSTIC_TODOS.yaml into a test suite.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package mustache
|
||||
|
||||
import "core:os"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
|
||||
Diag_Case :: struct {
|
||||
name: string,
|
||||
enable: bool,
|
||||
should: string, // "error" | "warn" | "pass"
|
||||
expected: string, // exact formatted diagnostic output
|
||||
input: string,
|
||||
}
|
||||
|
||||
load_diag_cases :: proc(path: string, allocator := context.temp_allocator) -> []Diag_Case {
|
||||
dirs := [?]string{#directory, path}
|
||||
full_path, _ := filepath.join(dirs[:], allocator)
|
||||
raw_data, err := os.read_entire_file(full_path, allocator)
|
||||
if err != nil {
|
||||
return {}
|
||||
}
|
||||
|
||||
content := string(raw_data)
|
||||
lines := strings.split(content, "\n", allocator)
|
||||
|
||||
cases := make([dynamic]Diag_Case, 0, 16, allocator)
|
||||
|
||||
current: Diag_Case
|
||||
has_current := false
|
||||
|
||||
// Multiline block state
|
||||
in_block := false
|
||||
block_key: string
|
||||
block_indent := -1
|
||||
block_sb: strings.Builder
|
||||
|
||||
for line in lines {
|
||||
raw := strings.trim_right(line, "\r")
|
||||
|
||||
if in_block {
|
||||
// Check if this line belongs to the multiline block
|
||||
is_indented := len(raw) == 0 || raw[0] == ' ' || raw[0] == '\t'
|
||||
if is_indented {
|
||||
if block_indent < 0 && len(raw) > 0 {
|
||||
// Determine indentation from first content line
|
||||
block_indent = 0
|
||||
for block_indent < len(raw) && raw[block_indent] == ' ' {
|
||||
block_indent += 1
|
||||
}
|
||||
}
|
||||
// Strip block indentation
|
||||
stripped := raw
|
||||
if block_indent > 0 && len(stripped) >= block_indent {
|
||||
stripped = stripped[block_indent:]
|
||||
}
|
||||
if strings.builder_len(block_sb) > 0 {
|
||||
strings.write_string(&block_sb, "\n")
|
||||
}
|
||||
strings.write_string(&block_sb, stripped)
|
||||
continue
|
||||
} else {
|
||||
// End of multiline block
|
||||
in_block = false
|
||||
strings.write_string(&block_sb, "\n") // YAML | trailing newline
|
||||
value := strings.clone(strings.to_string(block_sb), allocator)
|
||||
strings.builder_destroy(&block_sb)
|
||||
set_field(¤t, block_key, value)
|
||||
block_indent = -1
|
||||
// Fall through to process this line
|
||||
}
|
||||
}
|
||||
|
||||
trimmed := strings.trim_space(raw)
|
||||
|
||||
// Entry separator
|
||||
if trimmed == "---" {
|
||||
if has_current {
|
||||
append(&cases, current)
|
||||
current = {}
|
||||
has_current = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip blank lines outside blocks
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Multiline block start: "key: |"
|
||||
if strings.has_suffix(trimmed, ": |") {
|
||||
colon := strings.last_index(trimmed, ":")
|
||||
if colon > 0 {
|
||||
block_key = strings.trim_space(trimmed[:colon])
|
||||
strings.builder_init(&block_sb, allocator)
|
||||
in_block = true
|
||||
has_current = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Simple key: value
|
||||
colon := strings.index(trimmed, ":")
|
||||
if colon > 0 {
|
||||
key := strings.trim_space(trimmed[:colon])
|
||||
value := strings.trim_space(trimmed[colon + 1:])
|
||||
// Strip matching outer quotes
|
||||
if len(value) >= 2 {
|
||||
if (value[0] == '"' && value[len(value) - 1] == '"') ||
|
||||
(value[0] == '\'' && value[len(value) - 1] == '\'') {
|
||||
value = value[1:len(value) - 1]
|
||||
}
|
||||
}
|
||||
set_field(¤t, key, value)
|
||||
has_current = true
|
||||
}
|
||||
}
|
||||
|
||||
// Flush trailing multiline block
|
||||
if in_block {
|
||||
strings.write_string(&block_sb, "\n") // YAML | trailing newline
|
||||
value := strings.clone(strings.to_string(block_sb), allocator)
|
||||
strings.builder_destroy(&block_sb)
|
||||
set_field(¤t, block_key, value)
|
||||
}
|
||||
|
||||
// Flush last entry
|
||||
if has_current {
|
||||
append(&cases, current)
|
||||
}
|
||||
|
||||
return cases[:]
|
||||
}
|
||||
|
||||
set_field :: proc(c: ^Diag_Case, key, value: string) {
|
||||
switch key {
|
||||
case "name":
|
||||
c.name = value
|
||||
case "enable":
|
||||
c.enable = value == "true"
|
||||
case "should":
|
||||
c.should = value
|
||||
case "expected":
|
||||
c.expected = value
|
||||
case "input":
|
||||
c.input = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#+test
|
||||
package mustache
|
||||
|
||||
import "core:encoding/json"
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
import "core:testing"
|
||||
|
||||
@@ -475,3 +478,243 @@ test_parse_error_pipe_parse_in_inverted_keeps_double_braces :: proc(t: ^testing.
|
||||
fmt.tprintf("msg should contain literal '{{^', got %q", b.msg),
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Data-driven diagnostic cases (from DIAGNOSTIC_TODOS.yaml)
|
||||
//
|
||||
// write_diagnostic_tests generates diagnostic_test_cases_test.odin from the
|
||||
// enabled cases in DIAGNOSTIC_TODOS.yaml. On the first run (or when the YAML
|
||||
// changes), it regenerates the file and fails. On the second run, each case
|
||||
// runs as an individual test via run_diag_case.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Diag_Context :: struct {
|
||||
items: [5]string,
|
||||
word: string,
|
||||
params: json.Object,
|
||||
now: string,
|
||||
}
|
||||
|
||||
diag_ctx :: proc() -> Diag_Context {
|
||||
params_raw := `{ "author": { "name": "foo bar"}}`
|
||||
params_val, err := json.parse(params_raw, allocator = context.temp_allocator)
|
||||
assert(err == nil)
|
||||
params, ok := params_val.(json.Object)
|
||||
assert(ok)
|
||||
return Diag_Context {
|
||||
items = {"a", "b", "c", "d", "e"},
|
||||
word = "Hello, World!",
|
||||
params = params,
|
||||
now = "2026-08-10T16:21:55-04:00",
|
||||
}
|
||||
}
|
||||
|
||||
diag_partials :: proc() -> map[string]Template {
|
||||
dirs := [?]string{#directory, "fixtures/layouts/partials"}
|
||||
partials_dir, _ := filepath.join(dirs[:], context.temp_allocator)
|
||||
return load_partials(partials_dir, allocator = context.temp_allocator)
|
||||
}
|
||||
|
||||
diag_slug :: proc(name: string) -> string {
|
||||
sb: strings.Builder
|
||||
strings.builder_init(&sb, context.temp_allocator)
|
||||
prev_underscore := false
|
||||
for r in name {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||||
strings.write_rune(&sb, r)
|
||||
prev_underscore = false
|
||||
} else if r >= 'A' && r <= 'Z' {
|
||||
strings.write_rune(&sb, r + 32)
|
||||
prev_underscore = false
|
||||
} else {
|
||||
if !prev_underscore && strings.builder_len(sb) > 0 {
|
||||
strings.write_rune(&sb, '_')
|
||||
prev_underscore = true
|
||||
}
|
||||
}
|
||||
}
|
||||
s := strings.to_string(sb)
|
||||
if len(s) > 0 && s[len(s) - 1] == '_' {
|
||||
s = s[:len(s) - 1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
run_diag_case :: proc(t: ^testing.T, name: string) {
|
||||
cases := load_diag_cases("../DIAGNOSTIC_TODOS.yaml")
|
||||
ctx := diag_ctx()
|
||||
partials := diag_partials()
|
||||
|
||||
for c in cases {
|
||||
if c.name != name do continue
|
||||
|
||||
switch c.should {
|
||||
case "error":
|
||||
tmpl, perr := parse(c.input, "<test>", allocator = context.temp_allocator)
|
||||
if perr != nil {
|
||||
formatted := format_render_error(perr, tmpl, colorize = false)
|
||||
testing.expect_value(t, formatted, c.expected)
|
||||
return
|
||||
}
|
||||
|
||||
_, rerr := render(tmpl, ctx, partials, context.temp_allocator)
|
||||
testing.expect(
|
||||
t,
|
||||
rerr != nil,
|
||||
fmt.tprintf("[%s] should error but render succeeded", c.name),
|
||||
)
|
||||
if rerr == nil do return
|
||||
|
||||
formatted := format_render_error(rerr, tmpl, colorize = false)
|
||||
testing.expect_value(t, formatted, c.expected)
|
||||
|
||||
case "pass":
|
||||
tmpl, perr := parse(c.input, "<test>", allocator = context.temp_allocator)
|
||||
testing.expect(
|
||||
t,
|
||||
perr == nil,
|
||||
fmt.tprintf(
|
||||
"[%s] should parse, got: %s",
|
||||
c.name,
|
||||
perr != nil ? format_render_error(perr, tmpl, colorize = false) : "",
|
||||
),
|
||||
)
|
||||
if perr != nil do return
|
||||
|
||||
_, rerr := render(tmpl, ctx, partials, context.temp_allocator)
|
||||
testing.expect(
|
||||
t,
|
||||
rerr == nil,
|
||||
fmt.tprintf(
|
||||
"[%s] should not error, got: %s",
|
||||
c.name,
|
||||
rerr != nil ? format_render_error(rerr, tmpl, colorize = false) : "",
|
||||
),
|
||||
)
|
||||
|
||||
case "ok":
|
||||
tmpl, perr := parse(c.input, "<test>", allocator = context.temp_allocator)
|
||||
testing.expect(
|
||||
t,
|
||||
perr == nil,
|
||||
fmt.tprintf(
|
||||
"[%s] should parse, got: %s",
|
||||
c.name,
|
||||
perr != nil ? format_render_error(perr, tmpl, colorize = false) : "",
|
||||
),
|
||||
)
|
||||
if perr != nil do return
|
||||
|
||||
result, rerr := render(tmpl, ctx, partials, context.temp_allocator)
|
||||
testing.expect(
|
||||
t,
|
||||
rerr == nil,
|
||||
fmt.tprintf(
|
||||
"[%s] should not error, got: %s",
|
||||
c.name,
|
||||
rerr != nil ? format_render_error(rerr, tmpl, colorize = false) : "",
|
||||
),
|
||||
)
|
||||
if rerr != nil do return
|
||||
|
||||
testing.expect_value(t, result, c.expected)
|
||||
|
||||
case "warn":
|
||||
tmpl, perr := parse(c.input, "<test>", allocator = context.temp_allocator)
|
||||
testing.expect(
|
||||
t,
|
||||
perr == nil,
|
||||
fmt.tprintf(
|
||||
"[%s] should parse, got: %s",
|
||||
c.name,
|
||||
perr != nil ? format_render_error(perr, tmpl, colorize = false) : "",
|
||||
),
|
||||
)
|
||||
if perr != nil do return
|
||||
|
||||
warnings := make([dynamic]Error, 0, 4, context.temp_allocator)
|
||||
_, rerr := render(tmpl, ctx, partials, context.temp_allocator, &warnings)
|
||||
testing.expect(
|
||||
t,
|
||||
rerr == nil,
|
||||
fmt.tprintf(
|
||||
"[%s] should not error, got: %s",
|
||||
c.name,
|
||||
rerr != nil ? format_render_error(rerr, tmpl, colorize = false) : "",
|
||||
),
|
||||
)
|
||||
if rerr != nil do return
|
||||
|
||||
testing.expect(
|
||||
t,
|
||||
len(warnings) > 0,
|
||||
fmt.tprintf("[%s] should produce a warning", c.name),
|
||||
)
|
||||
if len(warnings) == 0 do return
|
||||
|
||||
formatted := format_render_error(warnings[0], tmpl, colorize = false)
|
||||
testing.expect_value(t, formatted, c.expected)
|
||||
|
||||
case:
|
||||
testing.expectf(t, false, "[%s] unknown should value: '%s'", c.name, c.should)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
testing.expectf(t, false, "case '%s' not found in DIAGNOSTIC_TODOS.yaml", name)
|
||||
}
|
||||
|
||||
@(test)
|
||||
write_diagnostic_tests :: proc(t: ^testing.T) {
|
||||
cases := load_diag_cases("../DIAGNOSTIC_TODOS.yaml")
|
||||
testing.expect(t, len(cases) > 0, "should load diagnostic cases")
|
||||
|
||||
sb: strings.Builder
|
||||
strings.builder_init(&sb, context.temp_allocator)
|
||||
strings.write_string(&sb, "#+test\n")
|
||||
strings.write_string(&sb, "package mustache\n\n")
|
||||
strings.write_string(&sb, "import \"core:testing\"\n\n")
|
||||
|
||||
count := 0
|
||||
for c in cases {
|
||||
if !c.enable do continue
|
||||
if c.should == "" {
|
||||
testing.expectf(t, false, "[%s] has enable: true but no should value", c.name)
|
||||
continue
|
||||
}
|
||||
count += 1
|
||||
slug := diag_slug(c.name)
|
||||
strings.write_string(&sb, "@(test)\n")
|
||||
strings.write_string(&sb, "test_diag_")
|
||||
strings.write_string(&sb, slug)
|
||||
strings.write_string(&sb, " :: proc(t: ^testing.T) {\n")
|
||||
strings.write_string(&sb, "\trun_diag_case(t, \"")
|
||||
strings.write_string(&sb, c.name)
|
||||
strings.write_string(&sb, "\")\n}\n\n")
|
||||
}
|
||||
|
||||
testing.expect(t, count > 0, "at least one case should be enabled")
|
||||
|
||||
generated := strings.to_string(sb)
|
||||
|
||||
dirs := [?]string{#directory, "diagnostic_test_cases_test.odin"}
|
||||
gen_path, _ := filepath.join(dirs[:], context.temp_allocator)
|
||||
|
||||
existing_data, _ := os.read_entire_file_from_path(gen_path, context.temp_allocator)
|
||||
existing := string(existing_data)
|
||||
|
||||
if existing == generated do return
|
||||
|
||||
werr := os.write_entire_file(gen_path, transmute([]byte)generated)
|
||||
if werr != nil {
|
||||
testing.expectf(t, false, "failed to write %s: %v", gen_path, werr)
|
||||
return
|
||||
}
|
||||
testing.expectf(
|
||||
t,
|
||||
false,
|
||||
"diagnostic_test_cases_test.odin regenerated (%d cases), re-run tests to see results",
|
||||
count,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#+test
|
||||
package mustache
|
||||
|
||||
import "core:testing"
|
||||
|
||||
@(test)
|
||||
test_diag_missing_2nd_closing_brace :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Missing 2nd closing brace")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_missing_2nd_opening_brace :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Missing 2nd opening brace")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_mismatched_section_tags :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Mismatched section tags")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_missing_closing_section :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Missing closing section")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_empty_tag :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "empty tag")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_missing_pipe_arguement :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Missing pipe arguement")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_double_dot_access :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Double dot access")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_too_many_opening_braces :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Too many opening braces")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_too_many_closing_braces :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Too many closing braces")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_no_error_when_missing_index_page :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "No error when missing index page")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_format_pipe_with_no_date_format_set :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "format pipe with no date format set")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_first_last_with_zero_argument :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "first/last with zero argument")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_first_last_with_negative_argument :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "first/last with negative argument")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_first_succeeds_on_params_author_name :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "first succeeds on params.author.name")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_first_last_in_partial :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "first/last in partial")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_diag_pipes_are_ignored_in_comments :: proc(t: ^testing.T) {
|
||||
run_diag_case(t, "Pipes are ignored in comments")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<footer>
|
||||
<menu>
|
||||
<a class="goto-top opacity-0" href="#">{{> icons/chevron_up}}</a>
|
||||
{{#params.social}}
|
||||
<a href="{{url}}" target="_blank" rel="noopener noreferrer me" title="{{name}}">{{>* icon}}</a>
|
||||
{{/params.social}}
|
||||
</menu>
|
||||
<p>Proudly built with <a href="https://github.com/sbrow/thor/">Thor</a> and <a
|
||||
href="https://edwardtufte.github.io/tufte-css/">Tufte CSS</a>
|
||||
</p>
|
||||
<p><small>©</small> {{! now | format "2006" }} {{params.author.name | first -3 }}</p>
|
||||
</footer>
|
||||
+120
-46
@@ -23,9 +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)
|
||||
Warning, // non-fatal: operation succeeded but user should know
|
||||
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 {
|
||||
@@ -60,13 +60,13 @@ tag_error :: proc(err: Error, tmpl: Template) -> Error {
|
||||
if err == nil do return nil
|
||||
b := body(err)
|
||||
return Error_Body {
|
||||
msg = b.msg,
|
||||
pos = b.pos,
|
||||
span = b.span,
|
||||
hint = b.hint,
|
||||
kind = b.kind,
|
||||
msg = b.msg,
|
||||
pos = b.pos,
|
||||
span = b.span,
|
||||
hint = b.hint,
|
||||
kind = b.kind,
|
||||
source = tmpl.source,
|
||||
path = tmpl.path,
|
||||
path = tmpl.path,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,18 +156,22 @@ parse :: proc(
|
||||
tmpl: Template,
|
||||
err: Error,
|
||||
) {
|
||||
tmpl.source = source
|
||||
tmpl.path = path
|
||||
|
||||
tokens, terr := tokenize(source, tokens_allocator)
|
||||
if terr != nil {
|
||||
return {}, terr
|
||||
return tmpl, terr
|
||||
}
|
||||
|
||||
tmpl.nodes, err = parse_tokens(tokens[:], source, allocator)
|
||||
if err != nil {
|
||||
delete(tmpl.nodes)
|
||||
return {}, err
|
||||
e := body(err)
|
||||
e.source = source
|
||||
e.path = path
|
||||
return {}, e
|
||||
}
|
||||
tmpl.source = source
|
||||
tmpl.path = path
|
||||
deindent_blocks(tmpl.nodes[:], allocator)
|
||||
return tmpl, nil
|
||||
}
|
||||
@@ -269,7 +273,12 @@ parse_section :: proc(
|
||||
case .Variable:
|
||||
idx := len(nodes)
|
||||
append(nodes, Node{kind = .Variable})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos, tag_content_base(source, tok.pos))
|
||||
pipe_key, perr := parse_pipeline(
|
||||
tok.value,
|
||||
&nodes[idx].filters,
|
||||
tok.pos,
|
||||
tag_content_base(source, tok.pos),
|
||||
)
|
||||
if perr != nil {
|
||||
return Error_Body {
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{%s}}}}': %v", tok.value, perr),
|
||||
@@ -283,7 +292,12 @@ parse_section :: proc(
|
||||
case .Unescaped:
|
||||
idx := len(nodes)
|
||||
append(nodes, Node{kind = .Unescaped})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos, tag_content_base(source, tok.pos))
|
||||
pipe_key, perr := parse_pipeline(
|
||||
tok.value,
|
||||
&nodes[idx].filters,
|
||||
tok.pos,
|
||||
tag_content_base(source, tok.pos),
|
||||
)
|
||||
if perr != nil {
|
||||
return Error_Body {
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{&%s}}}}': %v", tok.value, perr),
|
||||
@@ -303,7 +317,12 @@ parse_section :: proc(
|
||||
content_start := 0
|
||||
if pos^ < len(tokens) {content_start = tokens[pos^].pos}
|
||||
append(nodes, Node{kind = .Section, pos = tok.pos})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos, tag_content_base(source, tok.pos))
|
||||
pipe_key, perr := parse_pipeline(
|
||||
tok.value,
|
||||
&nodes[idx].filters,
|
||||
tok.pos,
|
||||
tag_content_base(source, tok.pos),
|
||||
)
|
||||
if perr != nil {
|
||||
return Error_Body {
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{#%s}}}}': %v", tok.value, perr),
|
||||
@@ -324,7 +343,12 @@ parse_section :: proc(
|
||||
content_start := 0
|
||||
if pos^ < len(tokens) {content_start = tokens[pos^].pos}
|
||||
append(nodes, Node{kind = .Inverted, pos = tok.pos})
|
||||
pipe_key, perr := parse_pipeline(tok.value, &nodes[idx].filters, tok.pos, tag_content_base(source, tok.pos))
|
||||
pipe_key, perr := parse_pipeline(
|
||||
tok.value,
|
||||
&nodes[idx].filters,
|
||||
tok.pos,
|
||||
tag_content_base(source, tok.pos),
|
||||
)
|
||||
if perr != nil {
|
||||
return Error_Body {
|
||||
msg = fmt.tprintf("pipe parse error in '{{{{^%s}}}}': %v", tok.value, perr),
|
||||
@@ -625,7 +649,14 @@ 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, warnings)
|
||||
transformed, perr := apply_pipeline(
|
||||
val,
|
||||
node.filters[:],
|
||||
node.pos,
|
||||
ctx[:],
|
||||
current,
|
||||
warnings,
|
||||
)
|
||||
if perr != nil {
|
||||
return tag_error(perr, current)
|
||||
}
|
||||
@@ -644,7 +675,14 @@ 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, warnings)
|
||||
transformed, perr := apply_pipeline(
|
||||
val,
|
||||
node.filters[:],
|
||||
node.pos,
|
||||
ctx[:],
|
||||
current,
|
||||
warnings,
|
||||
)
|
||||
if perr != nil {
|
||||
return tag_error(perr, current)
|
||||
}
|
||||
@@ -659,7 +697,14 @@ 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, warnings)
|
||||
transformed, perr := apply_pipeline(
|
||||
val,
|
||||
node.filters[:],
|
||||
node.pos,
|
||||
ctx[:],
|
||||
current,
|
||||
warnings,
|
||||
)
|
||||
if perr != nil {
|
||||
return tag_error(perr, current)
|
||||
}
|
||||
@@ -673,16 +718,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,
|
||||
warnings,
|
||||
) or_return
|
||||
render_nodes(
|
||||
current,
|
||||
children,
|
||||
ctx,
|
||||
partials,
|
||||
b,
|
||||
blocks,
|
||||
indent_state,
|
||||
warnings,
|
||||
) or_return
|
||||
}
|
||||
} else {
|
||||
context_push(ctx, val, current, node)
|
||||
@@ -707,24 +752,31 @@ 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, warnings)
|
||||
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,
|
||||
warnings,
|
||||
) 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:
|
||||
@@ -863,7 +915,14 @@ warn_unknown_key :: proc(current: Template, ctx: []any, node: Node) {
|
||||
if path == "" {
|
||||
path = "<input>"
|
||||
}
|
||||
diag := format_tag_error(path, current.source, node.pos, msg, hint, colorize = diags.should_colorize())
|
||||
diag := format_tag_error(
|
||||
path,
|
||||
current.source,
|
||||
node.pos,
|
||||
msg,
|
||||
hint,
|
||||
colorize = diags.should_colorize(),
|
||||
)
|
||||
log.warnf("%s", diag)
|
||||
}
|
||||
|
||||
@@ -886,7 +945,14 @@ warn_missing_partial :: proc(
|
||||
if path == "" {
|
||||
path = "<input>"
|
||||
}
|
||||
diag := format_tag_error(path, current.source, node.pos, msg, hint, colorize = diags.should_colorize())
|
||||
diag := format_tag_error(
|
||||
path,
|
||||
current.source,
|
||||
node.pos,
|
||||
msg,
|
||||
hint,
|
||||
colorize = diags.should_colorize(),
|
||||
)
|
||||
log.warnf("%s", diag)
|
||||
}
|
||||
|
||||
@@ -965,6 +1031,14 @@ warn_context_depth :: proc(current: Template, node: Node) {
|
||||
if path == "" {
|
||||
path = "<input>"
|
||||
}
|
||||
diag := format_tag_error(path, current.source, node.pos, msg, "", colorize = diags.should_colorize())
|
||||
diag := format_tag_error(
|
||||
path,
|
||||
current.source,
|
||||
node.pos,
|
||||
msg,
|
||||
"",
|
||||
colorize = diags.should_colorize(),
|
||||
)
|
||||
log.warnf("%s", diag)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package mustache
|
||||
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
load_partials :: proc(
|
||||
dir: string,
|
||||
base: string = "",
|
||||
allocator := context.allocator,
|
||||
) -> map[string]Template {
|
||||
partials := make(map[string]Template, allocator)
|
||||
|
||||
entries, err := os.read_all_directory_by_path(dir, context.temp_allocator)
|
||||
if err != nil {
|
||||
log.warnf("cannot read partials dir %s: %v", dir, err)
|
||||
return partials
|
||||
}
|
||||
defer os.file_info_slice_delete(entries, context.temp_allocator)
|
||||
|
||||
for entry in entries {
|
||||
if entry.type == .Regular {
|
||||
if !strings.has_suffix(entry.name, ".html") do continue
|
||||
stripped := entry.name[:len(entry.name) - len(".html")]
|
||||
key := fmt.tprintf("%s%s", base, stripped)
|
||||
data, ferr := os.read_entire_file_from_path(entry.fullpath, allocator)
|
||||
if ferr != nil do continue
|
||||
source := string(data)
|
||||
path := strings.clone(entry.fullpath, allocator)
|
||||
tmpl, perr := parse(source, path, allocator = allocator)
|
||||
if perr != nil {
|
||||
b := body(perr)
|
||||
log.warnf("failed to parse partial %s: %s", entry.fullpath, b.msg)
|
||||
continue
|
||||
}
|
||||
partials[key] = tmpl
|
||||
} else if entry.type == .Directory {
|
||||
sub_base := fmt.tprintf("%s%s/", base, entry.name)
|
||||
sub := load_partials(entry.fullpath, sub_base, allocator)
|
||||
for k, v in sub {
|
||||
partials[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return partials
|
||||
}
|
||||
Reference in New Issue
Block a user