mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
feat: Added duplicate weights warning.
This commit is contained in:
@@ -45,14 +45,14 @@ thor/
|
|||||||
| `site.odin` | `Flags` (CLI), `Config_File` (thor.json), `Site_Context` (template-facing: `title`, `description`, `base_url`, `params`, `og`, `menus`), `Site` (runtime state + arena + VFS + pages + `og`). `Feature` enum. 5-step `init_site`. Config menu parsing in `site_apply_config`. |
|
| `site.odin` | `Flags` (CLI), `Config_File` (thor.json), `Site_Context` (template-facing: `title`, `description`, `base_url`, `params`, `og`, `menus`), `Site` (runtime state + arena + VFS + pages + `og`). `Feature` enum. 5-step `init_site`. Config menu parsing in `site_apply_config`. |
|
||||||
| `content.odin` | `Page` struct (includes `weight`, `menus: map[string]Menu_Entry`, `og`), `Pending_File` struct, `scan_content_files` (section-aware walk that handles leaf bundles), `collect_languages` (pre-scan for code fence languages), `load_page` (falls back to file mtime when no frontmatter date), `infer_layout`. Calls `md.process()` for the markdown pipeline. |
|
| `content.odin` | `Page` struct (includes `weight`, `menus: map[string]Menu_Entry`, `og`), `Pending_File` struct, `scan_content_files` (section-aware walk that handles leaf bundles), `collect_languages` (pre-scan for code fence languages), `load_page` (falls back to file mtime when no frontmatter date), `infer_layout`. Calls `md.process()` for the markdown pipeline. |
|
||||||
| `render.odin` | Template rendering: `render_site`, `render_page_html`, `render_home_html`, `render_section`. `Template_Context` (unified render struct with `site: Site_Context`, `page: Page`, `menus`, `posts`, `pages`). 3-frame context stack via `[]any{ctx.site, ctx.page, ctx}`. `sort_pages` (weight primary, date secondary). `to_title_case` for section display names. VFS-based template loading with fallback chain (`get_template`). |
|
| `render.odin` | Template rendering: `render_site`, `render_page_html`, `render_home_html`, `render_section`. `Template_Context` (unified render struct with `site: Site_Context`, `page: Page`, `menus`, `posts`, `pages`). 3-frame context stack via `[]any{ctx.site, ctx.page, ctx}`. `sort_pages` (weight primary, date secondary). `to_title_case` for section display names. VFS-based template loading with fallback chain (`get_template`). |
|
||||||
| `menus.odin` | Menu system: `Menu_Entry {name, url, weight}`, `DEFAULT_WEIGHT = 10`. `build_menus` (priority chain: config → auto + page frontmatter). `collect_auto_menus` (sections + root-level pages). `merge_page_menus` (frontmatter entries with effective weight fallback). `parse_page_menus` (string/array/object forms). `parse_config_menus` (from thor.json). `sort_menu_entries` / `compare_menu_entries` (weight primary, name secondary). |
|
| `menus.odin` | Menu system: `Menu_Entry {name, url, weight: Maybe(int)}`, `DEFAULT_WEIGHT = 10`. `build_menus` (priority chain: config → auto + page frontmatter, then `warn_all_duplicate_weights`). `collect_auto_menus` (sections + root-level pages, skips pages with explicit `"menus": "main"` frontmatter). `merge_page_menus` (frontmatter entries with effective weight fallback via nil check). `parse_page_menus` (string/array/object forms). `parse_config_menus` (from thor.json). `sort_menu_entries` / `compare_menu_entries` (weight primary via `.? or_else DEFAULT_WEIGHT`, name secondary). `warn_duplicate_weights` / `warn_all_duplicate_weights` (log when two entries in same menu have same explicitly-set weight). |
|
||||||
| `minify.odin` | HTML/CSS minification via tree-sitter. Imports `ts "treesitter"`. |
|
| `minify.odin` | HTML/CSS minification via tree-sitter. Imports `ts "treesitter"`. |
|
||||||
| `feed.odin` | RSS feed + sitemap XML. Uses `page.url` for canonical URLs. |
|
| `feed.odin` | RSS feed + sitemap XML. Uses `page.url` for canonical URLs. |
|
||||||
| `vfs.odin` | Union file system: `VFS`, `build_vfs`, `mount_dir`, `mount_subdir`, `mount_recursive`, `vfs_get`, `vfs_get_entry`, `vfs_entry_data`. Layers defaults → modules → site. |
|
| `vfs.odin` | Union file system: `VFS`, `build_vfs`, `mount_dir`, `mount_subdir`, `mount_recursive`, `vfs_get`, `vfs_get_entry`, `vfs_entry_data`. Layers defaults → modules → site. |
|
||||||
| `assets.odin` | `copy_assets_dir` — iterates VFS entries with `assets/` prefix, minifies CSS, copies verbatim or via `os.copy_file`. |
|
| `assets.odin` | `copy_assets_dir` — iterates VFS entries with `assets/` prefix, minifies CSS, copies verbatim or via `os.copy_file`. |
|
||||||
| `html.odin` | `strip_html_tags`, `unescape_html`, `generate_summary` (word-count truncation, zero-alloc), `generate_description` (HTML→plain text: strip tags, decode entities, collapse whitespace). |
|
| `html.odin` | `strip_html_tags`, `unescape_html`, `generate_summary` (word-count truncation, zero-alloc), `generate_description` (HTML→plain text: strip tags, decode entities, collapse whitespace). |
|
||||||
| `opengraph.odin` | `Open_Graph` struct (fields ordered per OGP spec, `is_article: Maybe(bool)`). `og_for_site(site)` for site defaults (from config + derived), `og_for_page(site_og, page)` for page-specific (overlay page.og + derive from page data). Description falls back to `generate_description(generate_summary(body_html))`. |
|
| `opengraph.odin` | `Open_Graph` struct (fields ordered per OGP spec, `is_article: Maybe(bool)`). `og_for_site(site)` for site defaults (from config + derived), `og_for_page(site_og, page)` for page-specific (overlay page.og + derive from page data). Description falls back to `generate_description(generate_summary(body_html))`. |
|
||||||
| `frontmatter.odin` | JSON frontmatter parser (`{ }` delimited). Supports `layout`, `lastmod`, `weight`, `menus`, and nested `og` object (via `json_get_open_graph`). Helpers: `json_get_string`, `json_get_bool`, `json_get_int`. |
|
| `frontmatter.odin` | JSON frontmatter parser (`{ }` delimited). Supports `layout`, `lastmod`, `weight: Maybe(int)`, `menus`, and nested `og` object (via `json_get_open_graph`). Helpers: `json_get_string`, `json_get_bool`, `json_get_int` (returns `Maybe(int)`, nil for absent/invalid). |
|
||||||
| `defaults.odin` | `DEFAULTS_PATH` constant, resolved at compile time via `#directory` so bundled templates ship in the binary. |
|
| `defaults.odin` | `DEFAULTS_PATH` constant, resolved at compile time via `#directory` so bundled templates ship in the binary. |
|
||||||
|
|
||||||
### Subpackages
|
### Subpackages
|
||||||
@@ -77,7 +77,7 @@ Icon SVGs live as HTML partials in `layouts/partials/icons/` (home, github, rss,
|
|||||||
```
|
```
|
||||||
thor.json → find_config → init_site (5-step)
|
thor.json → find_config → init_site (5-step)
|
||||||
→ build_vfs (defaults/layouts → modules → site/layouts, site/assets)
|
→ build_vfs (defaults/layouts → modules → site/layouts, site/assets)
|
||||||
→ site_load_content (scan_content_files + collect_languages + preload_grammars + load_page + url computation + build_menus)
|
→ site_load_content (scan_content_files + collect_languages + preload_grammars + load_page + url computation + build_menus + warn_all_duplicate_weights)
|
||||||
→ render_site
|
→ render_site
|
||||||
→ load_partials + get_template (VFS + fallback chain)
|
→ load_partials + get_template (VFS + fallback chain)
|
||||||
→ render_page_html / render_home_html / render_section (3-frame context stack: site, page, ctx)
|
→ render_page_html / render_home_html / render_section (3-frame context stack: site, page, ctx)
|
||||||
@@ -98,7 +98,7 @@ Page :: struct {
|
|||||||
description: string,
|
description: string,
|
||||||
date: string,
|
date: string,
|
||||||
year: string,
|
year: string,
|
||||||
weight: int, // page ordering (default DEFAULT_WEIGHT = 10)
|
weight: Maybe(int), // page ordering (nil = unset, defaults to DEFAULT_WEIGHT at comparison time)
|
||||||
lastmod: string,
|
lastmod: string,
|
||||||
menus: map[string]Menu_Entry, // frontmatter menu assignments
|
menus: map[string]Menu_Entry, // frontmatter menu assignments
|
||||||
content: string, // rendered HTML body
|
content: string, // rendered HTML body
|
||||||
@@ -131,9 +131,12 @@ Menu system in `menus.odin`. `Menu_Entry :: struct {name: string, url: string, w
|
|||||||
|
|
||||||
### Weight
|
### Weight
|
||||||
|
|
||||||
- `Page.weight` — general page ordering (default `DEFAULT_WEIGHT`). Affects `sort_pages` (weight primary, date secondary).
|
All weight fields use `Maybe(int)` — nil means "unset," `some(v)` means explicitly set. This distinguishes `"weight": 10` (explicit) from no weight key (defaults to `DEFAULT_WEIGHT` at comparison time via `.? or_else DEFAULT_WEIGHT`). Eliminates the old `0`-as-sentinel pattern from `json_get_int`.
|
||||||
- Per-menu weight — from object frontmatter form. Falls back to `Page.weight` when `DEFAULT_WEIGHT`.
|
|
||||||
- `Menu_Entry.weight` — effective weight after fallback. Sorted ascending, name alphabetical for ties.
|
- `Page.weight: Maybe(int)` — page-level ordering. nil = unset. Affects `sort_pages` (weight primary, date secondary).
|
||||||
|
- `Menu_Entry.weight: Maybe(int)` — per-menu ordering. nil for auto-generated entries and string/array frontmatter forms. Explicit value from object frontmatter form `{"weight": N}`.
|
||||||
|
- Effective weight in `merge_page_menus`: per-menu weight if set, else falls back to `page.weight`. Both `Maybe(int)`, so nil propagates naturally — no value-based sentinel check.
|
||||||
|
- Sorted ascending via `.? or_else DEFAULT_WEIGHT`, name alphabetical for ties.
|
||||||
|
|
||||||
### Templates
|
### Templates
|
||||||
|
|
||||||
@@ -145,6 +148,10 @@ Menu system in `menus.odin`. `Menu_Entry :: struct {name: string, url: string, w
|
|||||||
|
|
||||||
`Template_Context.menus` resolves above `Page.menus` (frontmatter assignments) on the 3-frame context stack. Accessible as `{{#menus.main}}` or `{{#site.menus.main}}`.
|
`Template_Context.menus` resolves above `Page.menus` (frontmatter assignments) on the 3-frame context stack. Accessible as `{{#menus.main}}` or `{{#site.menus.main}}`.
|
||||||
|
|
||||||
|
### Duplicate weight warnings
|
||||||
|
|
||||||
|
`warn_duplicate_weights` (called from `build_menus` after all menus are sorted) logs a warning when two entries in the same menu have the same explicitly-set weight. Only non-nil weights are checked — nil (unset/default) entries are never flagged, so auto-generated entries don't produce noise. The warning includes the menu name, weight value, and both entry names.
|
||||||
|
|
||||||
## Config system
|
## Config system
|
||||||
|
|
||||||
Config is split into three structs with a clear 5-step initialization flow:
|
Config is split into three structs with a clear 5-step initialization flow:
|
||||||
@@ -487,6 +494,9 @@ These are things that are easy to get wrong:
|
|||||||
- `#partial switch` is usually a code smell. prefer a `case all, extra, types:` branch.
|
- `#partial switch` is usually a code smell. prefer a `case all, extra, types:` branch.
|
||||||
- you don't usually need to create arena allocators in tests, instead use context.temp_allocator if you want to simplify cleanup.
|
- you don't usually need to create arena allocators in tests, instead use context.temp_allocator if you want to simplify cleanup.
|
||||||
- you don't need to manually set up a tracking allocator in tests. the context.allocator will warn you about leaks.
|
- you don't need to manually set up a tracking allocator in tests. the context.allocator will warn you about leaks.
|
||||||
|
- **`Maybe(T)` unwrap syntax:** `value.? or_else default`. Not `value or_else default` — `or_else` works on the `?T` returned by `.?`, not on `Maybe(T)` directly.
|
||||||
|
- **`Maybe(T)` equality:** `a == b` works directly between two `Maybe(T)` values (nil == nil → true, some(5) == some(5) → true, nil == some(5) → false). Also `a == 5` works (int coerces to `Maybe(int)`).
|
||||||
|
- **File logger in tests:** `log.create_file_logger(&f)` + `context.logger = logger` captures log output. Must be set inline in the test proc (not via a helper proc) for context propagation. Clean up with `log.destroy_file_logger(logger)` then `os.read_entire_file_from_path` to verify output.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
## High priority
|
## High priority
|
||||||
|
|
||||||
- Polish existing features before moving on to new ones.
|
- Polish existing features before moving on to new ones.
|
||||||
- [ ] Add Weights
|
- [x] Add Weights
|
||||||
- [ ] sort by `page.weight` when loading
|
- [x] sort by `page.weight` when loading
|
||||||
- [ ] re-sort by `page.menu.weight` when building menus.
|
- [x] re-sort by `page.menu.weight` when building menus.
|
||||||
- [ ] warn user when 2 pages with explicit weights match.
|
- [x] warn user when 2 pages with explicit weights match.
|
||||||
- [ ] Improve diagnostics
|
- [ ] Improve diagnostics
|
||||||
- [ ] All Diagnostics should show:
|
- [ ] All Diagnostics should show:
|
||||||
- [ ] *What* went wrong
|
- [ ] *What* went wrong
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
- [ ] show "stack traces" in template error diagnostics
|
- [ ] show "stack traces" in template error diagnostics
|
||||||
- [ ] better diagnostics for syntax errors in treesitter.
|
- [ ] better diagnostics for syntax errors in treesitter.
|
||||||
- [ ] Ensure diagnostics for MAX_CONTEXT_DEPTH are good.
|
- [ ] Ensure diagnostics for MAX_CONTEXT_DEPTH are good.
|
||||||
|
- [ ] improve matching weights message.
|
||||||
- [ ] show a proper diagnostic for timezones
|
- [ ] show a proper diagnostic for timezones
|
||||||
- currently "unable to load timezone 'America/New_Yorkskie'"
|
- currently "unable to load timezone 'America/New_Yorkskie'"
|
||||||
- want rust style diagnostic and better message, maybe "unknown timezone 'America/New_Yorkskie'"
|
- want rust style diagnostic and better message, maybe "unknown timezone 'America/New_Yorkskie'"
|
||||||
|
|||||||
+33
-11
@@ -35,8 +35,8 @@ parse_page_menus :: proc(
|
|||||||
case json.String:
|
case json.String:
|
||||||
result = make(map[string]Menu_Entry, allocator)
|
result = make(map[string]Menu_Entry, allocator)
|
||||||
result[string(v)] = Menu_Entry {
|
result[string(v)] = Menu_Entry {
|
||||||
name = page.title,
|
name = page.title,
|
||||||
url = page.permalink,
|
url = page.permalink,
|
||||||
}
|
}
|
||||||
|
|
||||||
case json.Array:
|
case json.Array:
|
||||||
@@ -143,12 +143,14 @@ build_menus :: proc(site: ^Site) {
|
|||||||
log.fatalf("menus: cannot mix config menus with frontmatter menus")
|
log.fatalf("menus: cannot mix config menus with frontmatter menus")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
}
|
}
|
||||||
|
warn_all_duplicate_weights(site)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// No config menus — auto-generate, then merge page menus on top
|
// No config menus — auto-generate, then merge page menus on top
|
||||||
collect_auto_menus(site)
|
collect_auto_menus(site)
|
||||||
merge_page_menus(site)
|
merge_page_menus(site)
|
||||||
|
warn_all_duplicate_weights(site)
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge_page_menus collects frontmatter menu entries from pages and merges
|
// merge_page_menus collects frontmatter menu entries from pages and merges
|
||||||
@@ -169,11 +171,10 @@ merge_page_menus :: proc(site: ^Site) {
|
|||||||
if effective == nil {
|
if effective == nil {
|
||||||
effective = page.weight
|
effective = page.weight
|
||||||
}
|
}
|
||||||
append(&page_entries[menu_name], Menu_Entry{
|
append(
|
||||||
name = entry.name,
|
&page_entries[menu_name],
|
||||||
url = entry.url,
|
Menu_Entry{name = entry.name, url = entry.url, weight = effective},
|
||||||
weight = effective,
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +246,7 @@ collect_auto_menus :: proc(site: ^Site) {
|
|||||||
if _, has_main := page.menus["main"]; has_main {
|
if _, has_main := page.menus["main"]; has_main {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
append(
|
append(&entries, Menu_Entry{name = page.title, url = page.permalink, weight = page.weight})
|
||||||
&entries,
|
|
||||||
Menu_Entry{name = page.title, url = page.permalink, weight = page.weight},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
@@ -279,6 +277,29 @@ sort_menu_entries :: proc(entries: []Menu_Entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// warn_duplicate_weights logs a warning for each pair of adjacent entries
|
||||||
|
// (pre-sorted) that have the same explicitly-set weight. Entries with nil
|
||||||
|
// weight (unset/default) are never flagged.
|
||||||
|
warn_duplicate_weights :: proc(menu_name: string, entries: []Menu_Entry) {
|
||||||
|
for i in 0 ..< len(entries) - 1 {
|
||||||
|
if entries[i].weight != nil && entries[i].weight == entries[i + 1].weight {
|
||||||
|
log.warnf(
|
||||||
|
"menus('%s'):'%s' and '%s' share the same weight (%d).",
|
||||||
|
menu_name,
|
||||||
|
entries[i].name,
|
||||||
|
entries[i + 1].name,
|
||||||
|
entries[i].weight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn_all_duplicate_weights :: proc(site: ^Site) {
|
||||||
|
for menu_name, entries in site.menus {
|
||||||
|
warn_duplicate_weights(menu_name, entries)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// parse_config_menus converts raw JSON from thor.json into map[string][]Menu_Entry.
|
// parse_config_menus converts raw JSON from thor.json into map[string][]Menu_Entry.
|
||||||
// Entries are sorted by weight, then name.
|
// Entries are sorted by weight, then name.
|
||||||
parse_config_menus :: proc(
|
parse_config_menus :: proc(
|
||||||
@@ -367,3 +388,4 @@ parse_config_menus :: proc(
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package main
|
|||||||
import "core:encoding/json"
|
import "core:encoding/json"
|
||||||
import "core:log"
|
import "core:log"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
import "core:strings"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
make_page :: proc(title: string, permalink: string) -> Page {
|
make_page :: proc(title: string, permalink: string) -> Page {
|
||||||
@@ -423,3 +425,96 @@ test_auto_menus_no_duplicate_with_frontmatter :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, len(main) == 1, "expected exactly 1 entry (no duplicate)")
|
testing.expect(t, len(main) == 1, "expected exactly 1 entry (no duplicate)")
|
||||||
testing.expect_value(t, main[0].name, "Ideas")
|
testing.expect_value(t, main[0].name, "Ideas")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- warn_duplicate_weights tests ---
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_warn_duplicate_weights_explicit :: proc(t: ^testing.T) {
|
||||||
|
path := "/tmp/thor_test_warn_explicit.log"
|
||||||
|
os.remove(path)
|
||||||
|
f, err := os.open(path, os.O_RDWR | os.O_CREATE | os.O_TRUNC)
|
||||||
|
if err != nil {
|
||||||
|
testing.expect(t, false, "failed to open temp log file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.create_file_logger(f)
|
||||||
|
context.logger = logger
|
||||||
|
|
||||||
|
entries := []Menu_Entry {
|
||||||
|
{name = "Alpha", url = "/a/", weight = 5},
|
||||||
|
{name = "Beta", url = "/b/", weight = 5},
|
||||||
|
}
|
||||||
|
warn_duplicate_weights("main", entries)
|
||||||
|
|
||||||
|
log.destroy_file_logger(logger)
|
||||||
|
|
||||||
|
data, _ := os.read_entire_file_from_path(path, context.temp_allocator)
|
||||||
|
output := string(data)
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
testing.expect(t, strings.contains(output, "duplicate weight 5"), "expected weight in warning")
|
||||||
|
testing.expect(t, strings.contains(output, "Alpha"), "expected first entry name")
|
||||||
|
testing.expect(t, strings.contains(output, "Beta"), "expected second entry name")
|
||||||
|
testing.expect(t, strings.contains(output, "'main'"), "expected menu name in warning")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_warn_duplicate_weights_nil_not_flagged :: proc(t: ^testing.T) {
|
||||||
|
path := "/tmp/thor_test_warn_nil.log"
|
||||||
|
os.remove(path)
|
||||||
|
f, err := os.open(path, os.O_RDWR | os.O_CREATE | os.O_TRUNC)
|
||||||
|
if err != nil {
|
||||||
|
testing.expect(t, false, "failed to open temp log file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.create_file_logger(f)
|
||||||
|
context.logger = logger
|
||||||
|
|
||||||
|
entries := []Menu_Entry {
|
||||||
|
{name = "Alpha", url = "/a/"},
|
||||||
|
{name = "Beta", url = "/b/"},
|
||||||
|
}
|
||||||
|
warn_duplicate_weights("main", entries)
|
||||||
|
|
||||||
|
log.destroy_file_logger(logger)
|
||||||
|
|
||||||
|
data, _ := os.read_entire_file_from_path(path, context.temp_allocator)
|
||||||
|
output := string(data)
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
testing.expect(t, output == "", "nil-weight entries should not produce warnings")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_warn_duplicate_weights_explicit_default :: proc(t: ^testing.T) {
|
||||||
|
path := "/tmp/thor_test_warn_default.log"
|
||||||
|
os.remove(path)
|
||||||
|
f, err := os.open(path, os.O_RDWR | os.O_CREATE | os.O_TRUNC)
|
||||||
|
if err != nil {
|
||||||
|
testing.expect(t, false, "failed to open temp log file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.create_file_logger(f)
|
||||||
|
context.logger = logger
|
||||||
|
|
||||||
|
entries := []Menu_Entry {
|
||||||
|
{name = "Alpha", url = "/a/", weight = 10},
|
||||||
|
{name = "Beta", url = "/b/", weight = 10},
|
||||||
|
}
|
||||||
|
warn_duplicate_weights("main", entries)
|
||||||
|
|
||||||
|
log.destroy_file_logger(logger)
|
||||||
|
|
||||||
|
data, _ := os.read_entire_file_from_path(path, context.temp_allocator)
|
||||||
|
output := string(data)
|
||||||
|
os.remove(path)
|
||||||
|
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(output, "duplicate weight 10"),
|
||||||
|
"explicit weight 10 (== DEFAULT_WEIGHT) should warn — this is the Maybe(int) win",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user