Files
thor/site.odin
T
2026-07-15 11:34:16 -04:00

197 lines
4.3 KiB
Odin

package main
import "core:encoding/json"
import "core:flags"
import "core:fmt"
import "core:log"
import "core:mem"
import "core:os"
import "core:strings"
Site :: struct {
arena: mem.Dynamic_Arena,
title: string,
description: string,
author: string,
base_url: string,
config_path: string,
content_dir: string,
assets_dir: string,
output_dir: string,
layouts_dir: string,
params: json.Value,
features: bit_set[Feature],
}
Feature :: enum {
Sections,
Drafts,
Minify,
Watch,
}
Flags :: struct {
config_path: string `args:"name=config"`,
title: string,
description: string,
base_url: string `args:"name=base-url"`,
content_dir: string `args:"name=content"`,
assets_dir: string `args:"name=assets"`,
output_dir: string `args:"name=output"`,
layouts_dir: string,
author: string,
params: json.Value,
sectionate: bool `args:"name=sections"`,
drafts: bool `args:"name=drafts"`,
watch: bool,
minify: bool `args:"name=minify"`,
}
init_site :: proc(site: ^Site, args: []string) {
_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)
path := _flags.config_path
if path == "" {
found, ok := find_config("thor.json")
if ok {
path = found
log.debugf("thor: using config %s", path)
} else {
path = "./thor.json"
}
}
cfg, cfg_ok := load_site_config(path, alloc)
if cfg_ok {
merge_flags(&cfg, _flags)
} else {
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 {
config_dir = path[:idx]
}
// Hardcoded defaults (lowest precedence)
// TODO: Probably shouldn't use temp allocator here?
if site.content_dir == "" {
site.content_dir = fmt.tprintf("%s/content", config_dir)
}
if site.assets_dir == "" {
site.assets_dir = fmt.tprintf("%s/assets", config_dir)
}
if site.output_dir == "" {
site.output_dir = fmt.tprintf("%s/public", config_dir)
}
if site.layouts_dir == "" {
site.layouts_dir = fmt.tprintf("%s/layouts", config_dir)
}
if site.base_url == "" {
site.base_url = "http://localhost:8080"
}
}
load_site_config :: proc(
path: string,
allocator := context.allocator,
) -> (
config: Flags,
ok: bool,
) {
data, err := os.read_entire_file_from_path(path, allocator)
if err != nil {
return
}
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
}
ok = true
return
}
merge_flags :: proc(config: ^Flags, flags: Flags) {
if flags.base_url != "" {
config.base_url = flags.base_url
}
if flags.content_dir != "" {
config.content_dir = flags.content_dir
}
if flags.assets_dir != "" {
config.assets_dir = flags.assets_dir
}
if flags.output_dir != "" {
config.output_dir = flags.output_dir
}
if flags.drafts {
config.drafts = true
}
if flags.watch {
config.watch = true
}
if flags.sectionate {
config.sectionate = true
}
if flags.minify {
config.minify = 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.assets_dir = flags.assets_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}}
if flags.minify {site.features += {.Minify}}
}
site_allocator :: proc(site: ^Site) -> mem.Allocator {
return mem.dynamic_arena_allocator(&site.arena)
}
destroy_site :: proc(site: ^Site) {
mem.dynamic_arena_destroy(&site.arena)
}
find_config :: proc(filename: string) -> (path: string, ok: bool) {
dir, _ := os.get_working_directory(context.temp_allocator)
for {
candidate := fmt.tprintf("%s/%s", dir, filename)
if os.exists(candidate) {
path = candidate
ok = true
return
}
idx := strings.last_index(dir, "/")
if idx <= 0 {
return
}
dir = dir[:idx]
}
}