refactor: Broke config/flags into a separate struct from Site.

This commit is contained in:
Spencer Brower
2026-07-14 16:47:12 -04:00
parent 3c149246be
commit 00b73c04ba
4 changed files with 86 additions and 48 deletions
+2 -2
View File
@@ -34,8 +34,8 @@ Page :: struct {
// TODO: What is the lifetime of pages?
walk_content :: proc(site: ^Site) -> []Page {
content_path := site.content_dir
include_drafts := site.drafts
sectionate := site.sectionate
include_drafts := .Drafts in site.features
sectionate := .Sections in site.features
allocator := site_allocator(site)
+1 -1
View File
@@ -20,7 +20,7 @@ main :: proc() {
pages := walk_content(&site)
render_site(pages, site)
if !site.watch {
if !(.Watch in site.features) {
break
}
time.sleep(5 * time.Second)
+53 -11
View File
@@ -8,9 +8,29 @@ import "core:mem"
import "core:os"
import "core:strings"
Site :: struct {
// TODO: User can still technically try to set this
arena: mem.Dynamic_Arena `args:"hidden"`,
arena: mem.Dynamic_Arena,
title: string,
description: string,
author: string,
base_url: string,
config_path: string,
content_dir: string,
static_dir: string,
output_dir: string,
layouts_dir: string,
params: json.Value,
features: bit_set[Feature],
}
Feature :: enum {
Sections,
Drafts,
Watch,
}
Flags :: struct {
config_path: string `args:"name=config"`,
title: string,
description: string,
@@ -21,13 +41,13 @@ Site :: struct {
layouts_dir: string,
author: string,
params: json.Value,
sectionate: bool,
sectionate: bool `args:"name=sections"`,
drafts: bool `args:"name=drafts"`,
watch: bool,
}
init_site :: proc(site: ^Site, args: []string) {
_flags: Site
_flags: Flags
mem.dynamic_arena_init(&site.arena, alignment = 64) // FIXME: This is a hack
alloc := site_allocator(site)
flags.parse_or_exit(&_flags, args, .Odin, alloc)
@@ -43,13 +63,15 @@ init_site :: proc(site: ^Site, args: []string) {
}
}
if load_site_config(site, path, alloc) {
site_merge(site, _flags)
cfg, cfg_ok := load_site_config(path, alloc)
if cfg_ok {
merge_flags(&cfg, _flags)
} else {
_flags.arena = site.arena
site^ = _flags
cfg = _flags
}
site_apply_flags(site, cfg)
// Determine config file's directory for relative defaults
config_dir := "./"
if idx := strings.last_index(path, "/"); idx >= 0 {
@@ -76,10 +98,10 @@ init_site :: proc(site: ^Site, args: []string) {
}
load_site_config :: proc(
config: ^Site,
path: string,
allocator := context.allocator,
) -> (
config: Flags,
ok: bool,
) {
data, err := os.read_entire_file_from_path(path, allocator)
@@ -87,7 +109,7 @@ load_site_config :: proc(
return
}
unmarshal_err := json.unmarshal_string(string(data), config, allocator = allocator)
unmarshal_err := json.unmarshal_string(string(data), &config, allocator = allocator)
if unmarshal_err != nil {
log.warnf("thor: failed to parse %s: %v", path, unmarshal_err)
return
@@ -97,7 +119,7 @@ load_site_config :: proc(
return
}
site_merge :: proc(config: ^Site, flags: Site) {
merge_flags :: proc(config: ^Flags, flags: Flags) {
if flags.base_url != "" {
config.base_url = flags.base_url
}
@@ -116,9 +138,29 @@ site_merge :: proc(config: ^Site, flags: Site) {
if flags.watch {
config.watch = true
}
if flags.sectionate {
config.sectionate = true
}
config.config_path = flags.config_path
}
site_apply_flags :: proc(site: ^Site, flags: Flags) {
site.title = flags.title
site.description = flags.description
site.author = flags.author
site.base_url = flags.base_url
site.config_path = flags.config_path
site.content_dir = flags.content_dir
site.static_dir = flags.static_dir
site.output_dir = flags.output_dir
site.layouts_dir = flags.layouts_dir
site.params = flags.params
if flags.sectionate {site.features += {.Sections}}
if flags.drafts {site.features += {.Drafts}}
if flags.watch {site.features += {.Watch}}
}
site_allocator :: proc(site: ^Site) -> mem.Allocator {
return mem.dynamic_arena_allocator(&site.arena)
}
+30 -34
View File
@@ -34,16 +34,15 @@ test_load_site_config :: proc(t: ^testing.T) {
)
defer os.remove(path)
site: Site
ok := load_site_config(&site, path, context.temp_allocator)
cfg, ok := load_site_config(path, context.temp_allocator)
testing.expect(t, ok)
testing.expect_value(t, site.title, "Test Site")
testing.expect_value(t, site.description, "Test desc")
testing.expect_value(t, site.base_url, "https://example.com")
testing.expect_value(t, site.author, "Tester")
testing.expect_value(t, cfg.title, "Test Site")
testing.expect_value(t, cfg.description, "Test desc")
testing.expect_value(t, cfg.base_url, "https://example.com")
testing.expect_value(t, cfg.author, "Tester")
params, has_params := site.params.(json.Object)
params, has_params := cfg.params.(json.Object)
testing.expect(t, has_params)
social_val := params["social"]
@@ -63,8 +62,7 @@ test_load_site_config :: proc(t: ^testing.T) {
@(test)
test_load_site_config_missing_file :: proc(t: ^testing.T) {
site: Site
ok := load_site_config(&site, "./nonexistent_thor_test.json", context.temp_allocator)
_, ok := load_site_config("./nonexistent_thor_test.json", context.temp_allocator)
testing.expect(t, !ok)
}
@@ -73,8 +71,7 @@ test_load_site_config_invalid_json :: proc(t: ^testing.T) {
path := write_temp_config("invalid", `{not valid json}`)
defer os.remove(path)
site: Site
ok := load_site_config(&site, path, context.temp_allocator)
_, ok := load_site_config(path, context.temp_allocator)
testing.expect(t, !ok)
}
@@ -83,27 +80,26 @@ test_load_site_config_partial :: proc(t: ^testing.T) {
path := write_temp_config("partial", `{"title":"Partial"}`)
defer os.remove(path)
site: Site
ok := load_site_config(&site, path, context.temp_allocator)
cfg, ok := load_site_config(path, context.temp_allocator)
testing.expect(t, ok)
testing.expect_value(t, site.title, "Partial")
testing.expect_value(t, site.description, "")
testing.expect_value(t, site.author, "")
testing.expect(t, site.params == nil)
testing.expect_value(t, cfg.title, "Partial")
testing.expect_value(t, cfg.description, "")
testing.expect_value(t, cfg.author, "")
testing.expect(t, cfg.params == nil)
}
@(test)
test_site_merge_overrides :: proc(t: ^testing.T) {
config := Site {
config := Flags {
base_url = "https://original.com",
content_dir = "./content",
}
flags := Site {
flags := Flags {
base_url = "https://override.com",
}
site_merge(&config, flags)
merge_flags(&config, flags)
testing.expect_value(t, config.base_url, "https://override.com")
testing.expect_value(t, config.content_dir, "./content")
@@ -111,13 +107,13 @@ test_site_merge_overrides :: proc(t: ^testing.T) {
@(test)
test_site_merge_empty_flags_keep_config :: proc(t: ^testing.T) {
config := Site {
config := Flags {
base_url = "https://keep.com",
content_dir = "./keep",
}
flags := Site{}
flags := Flags{}
site_merge(&config, flags)
merge_flags(&config, flags)
testing.expect_value(t, config.base_url, "https://keep.com")
testing.expect_value(t, config.content_dir, "./keep")
@@ -125,38 +121,38 @@ test_site_merge_empty_flags_keep_config :: proc(t: ^testing.T) {
@(test)
test_site_merge_drafts_true :: proc(t: ^testing.T) {
config := Site {
config := Flags {
drafts = false,
}
flags := Site {
flags := Flags {
drafts = true,
}
site_merge(&config, flags)
merge_flags(&config, flags)
testing.expect(t, config.drafts)
}
@(test)
test_site_merge_drafts_false_preserves :: proc(t: ^testing.T) {
config := Site {
config := Flags {
drafts = false,
}
flags := Site {
flags := Flags {
drafts = false,
}
site_merge(&config, flags)
merge_flags(&config, flags)
testing.expect(t, !config.drafts)
}
@(test)
test_site_merge_config_path :: proc(t: ^testing.T) {
config := Site{}
flags := Site {
config := Flags{}
flags := Flags {
config_path = "./custom/thor.json",
}
site_merge(&config, flags)
merge_flags(&config, flags)
testing.expect_value(t, config.config_path, "./custom/thor.json")
}
@@ -192,7 +188,7 @@ test_init_site_flag_overrides_default :: proc(t: ^testing.T) {
init_site(&site, args)
defer destroy_site(&site)
testing.expect(t, site.drafts)
testing.expect(t, .Drafts in site.features)
testing.expect_value(t, site.base_url, "https://flag.com")
}
@@ -212,7 +208,7 @@ test_init_site_full_pipeline :: proc(t: ^testing.T) {
testing.expect_value(t, site.title, "Pipeline Test")
testing.expect_value(t, site.description, "Full")
testing.expect_value(t, site.author, "Author")
testing.expect(t, site.drafts)
testing.expect(t, .Drafts in site.features)
testing.expect_value(t, site.base_url, "https://config.com")
}