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? // TODO: What is the lifetime of pages?
walk_content :: proc(site: ^Site) -> []Page { walk_content :: proc(site: ^Site) -> []Page {
content_path := site.content_dir content_path := site.content_dir
include_drafts := site.drafts include_drafts := .Drafts in site.features
sectionate := site.sectionate sectionate := .Sections in site.features
allocator := site_allocator(site) allocator := site_allocator(site)
+1 -1
View File
@@ -20,7 +20,7 @@ main :: proc() {
pages := walk_content(&site) pages := walk_content(&site)
render_site(pages, site) render_site(pages, site)
if !site.watch { if !(.Watch in site.features) {
break break
} }
time.sleep(5 * time.Second) time.sleep(5 * time.Second)
+53 -11
View File
@@ -8,9 +8,29 @@ import "core:mem"
import "core:os" import "core:os"
import "core:strings" import "core:strings"
Site :: struct { Site :: struct {
// TODO: User can still technically try to set this arena: mem.Dynamic_Arena,
arena: mem.Dynamic_Arena `args:"hidden"`, 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"`, config_path: string `args:"name=config"`,
title: string, title: string,
description: string, description: string,
@@ -21,13 +41,13 @@ Site :: struct {
layouts_dir: string, layouts_dir: string,
author: string, author: string,
params: json.Value, params: json.Value,
sectionate: bool, sectionate: bool `args:"name=sections"`,
drafts: bool `args:"name=drafts"`, drafts: bool `args:"name=drafts"`,
watch: bool, watch: bool,
} }
init_site :: proc(site: ^Site, args: []string) { init_site :: proc(site: ^Site, args: []string) {
_flags: Site _flags: Flags
mem.dynamic_arena_init(&site.arena, alignment = 64) // FIXME: This is a hack mem.dynamic_arena_init(&site.arena, alignment = 64) // FIXME: This is a hack
alloc := site_allocator(site) alloc := site_allocator(site)
flags.parse_or_exit(&_flags, args, .Odin, alloc) 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) { cfg, cfg_ok := load_site_config(path, alloc)
site_merge(site, _flags) if cfg_ok {
merge_flags(&cfg, _flags)
} else { } else {
_flags.arena = site.arena cfg = _flags
site^ = _flags
} }
site_apply_flags(site, cfg)
// Determine config file's directory for relative defaults // Determine config file's directory for relative defaults
config_dir := "./" config_dir := "./"
if idx := strings.last_index(path, "/"); idx >= 0 { if idx := strings.last_index(path, "/"); idx >= 0 {
@@ -76,10 +98,10 @@ init_site :: proc(site: ^Site, args: []string) {
} }
load_site_config :: proc( load_site_config :: proc(
config: ^Site,
path: string, path: string,
allocator := context.allocator, allocator := context.allocator,
) -> ( ) -> (
config: Flags,
ok: bool, ok: bool,
) { ) {
data, err := os.read_entire_file_from_path(path, allocator) data, err := os.read_entire_file_from_path(path, allocator)
@@ -87,7 +109,7 @@ load_site_config :: proc(
return 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 { if unmarshal_err != nil {
log.warnf("thor: failed to parse %s: %v", path, unmarshal_err) log.warnf("thor: failed to parse %s: %v", path, unmarshal_err)
return return
@@ -97,7 +119,7 @@ load_site_config :: proc(
return return
} }
site_merge :: proc(config: ^Site, flags: Site) { merge_flags :: proc(config: ^Flags, flags: Flags) {
if flags.base_url != "" { if flags.base_url != "" {
config.base_url = flags.base_url config.base_url = flags.base_url
} }
@@ -116,9 +138,29 @@ site_merge :: proc(config: ^Site, flags: Site) {
if flags.watch { if flags.watch {
config.watch = true config.watch = true
} }
if flags.sectionate {
config.sectionate = true
}
config.config_path = flags.config_path 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 { site_allocator :: proc(site: ^Site) -> mem.Allocator {
return mem.dynamic_arena_allocator(&site.arena) 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) defer os.remove(path)
site: Site cfg, ok := load_site_config(path, context.temp_allocator)
ok := load_site_config(&site, path, context.temp_allocator)
testing.expect(t, ok) testing.expect(t, ok)
testing.expect_value(t, site.title, "Test Site") testing.expect_value(t, cfg.title, "Test Site")
testing.expect_value(t, site.description, "Test desc") testing.expect_value(t, cfg.description, "Test desc")
testing.expect_value(t, site.base_url, "https://example.com") testing.expect_value(t, cfg.base_url, "https://example.com")
testing.expect_value(t, site.author, "Tester") 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) testing.expect(t, has_params)
social_val := params["social"] social_val := params["social"]
@@ -63,8 +62,7 @@ test_load_site_config :: proc(t: ^testing.T) {
@(test) @(test)
test_load_site_config_missing_file :: proc(t: ^testing.T) { test_load_site_config_missing_file :: proc(t: ^testing.T) {
site: Site _, ok := load_site_config("./nonexistent_thor_test.json", context.temp_allocator)
ok := load_site_config(&site, "./nonexistent_thor_test.json", context.temp_allocator)
testing.expect(t, !ok) 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}`) path := write_temp_config("invalid", `{not valid json}`)
defer os.remove(path) defer os.remove(path)
site: Site _, ok := load_site_config(path, context.temp_allocator)
ok := load_site_config(&site, path, context.temp_allocator)
testing.expect(t, !ok) testing.expect(t, !ok)
} }
@@ -83,27 +80,26 @@ test_load_site_config_partial :: proc(t: ^testing.T) {
path := write_temp_config("partial", `{"title":"Partial"}`) path := write_temp_config("partial", `{"title":"Partial"}`)
defer os.remove(path) defer os.remove(path)
site: Site cfg, ok := load_site_config(path, context.temp_allocator)
ok := load_site_config(&site, path, context.temp_allocator)
testing.expect(t, ok) testing.expect(t, ok)
testing.expect_value(t, site.title, "Partial") testing.expect_value(t, cfg.title, "Partial")
testing.expect_value(t, site.description, "") testing.expect_value(t, cfg.description, "")
testing.expect_value(t, site.author, "") testing.expect_value(t, cfg.author, "")
testing.expect(t, site.params == nil) testing.expect(t, cfg.params == nil)
} }
@(test) @(test)
test_site_merge_overrides :: proc(t: ^testing.T) { test_site_merge_overrides :: proc(t: ^testing.T) {
config := Site { config := Flags {
base_url = "https://original.com", base_url = "https://original.com",
content_dir = "./content", content_dir = "./content",
} }
flags := Site { flags := Flags {
base_url = "https://override.com", 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.base_url, "https://override.com")
testing.expect_value(t, config.content_dir, "./content") testing.expect_value(t, config.content_dir, "./content")
@@ -111,13 +107,13 @@ test_site_merge_overrides :: proc(t: ^testing.T) {
@(test) @(test)
test_site_merge_empty_flags_keep_config :: proc(t: ^testing.T) { test_site_merge_empty_flags_keep_config :: proc(t: ^testing.T) {
config := Site { config := Flags {
base_url = "https://keep.com", base_url = "https://keep.com",
content_dir = "./keep", 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.base_url, "https://keep.com")
testing.expect_value(t, config.content_dir, "./keep") 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)
test_site_merge_drafts_true :: proc(t: ^testing.T) { test_site_merge_drafts_true :: proc(t: ^testing.T) {
config := Site { config := Flags {
drafts = false, drafts = false,
} }
flags := Site { flags := Flags {
drafts = true, drafts = true,
} }
site_merge(&config, flags) merge_flags(&config, flags)
testing.expect(t, config.drafts) testing.expect(t, config.drafts)
} }
@(test) @(test)
test_site_merge_drafts_false_preserves :: proc(t: ^testing.T) { test_site_merge_drafts_false_preserves :: proc(t: ^testing.T) {
config := Site { config := Flags {
drafts = false, drafts = false,
} }
flags := Site { flags := Flags {
drafts = false, drafts = false,
} }
site_merge(&config, flags) merge_flags(&config, flags)
testing.expect(t, !config.drafts) testing.expect(t, !config.drafts)
} }
@(test) @(test)
test_site_merge_config_path :: proc(t: ^testing.T) { test_site_merge_config_path :: proc(t: ^testing.T) {
config := Site{} config := Flags{}
flags := Site { flags := Flags {
config_path = "./custom/thor.json", config_path = "./custom/thor.json",
} }
site_merge(&config, flags) merge_flags(&config, flags)
testing.expect_value(t, config.config_path, "./custom/thor.json") 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) init_site(&site, args)
defer destroy_site(&site) 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") 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.title, "Pipeline Test")
testing.expect_value(t, site.description, "Full") testing.expect_value(t, site.description, "Full")
testing.expect_value(t, site.author, "Author") 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") testing.expect_value(t, site.base_url, "https://config.com")
} }