mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
feat!: Added the ability to disable features / extensions.
BREAKING CHANGE: `sectionate` has been moved to `markdown_extensions.sections` in the config.
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
- README.md
|
- README.md
|
||||||
- [ ] "Zero is beautiful"
|
- [ ] "Zero is beautiful"
|
||||||
- [ ] Content-hash fingerprinting for CSS and JS cache busting
|
- [ ] Content-hash fingerprinting for CSS and JS cache busting
|
||||||
|
- [ ] Performance
|
||||||
|
- [ ] See if we can disable bounds checks in `write_indented` and elsewhere.
|
||||||
- [ ] remove "has_*" keys from context.
|
- [ ] remove "has_*" keys from context.
|
||||||
- [ ] Enable configuration to opt-out of features, particular pre/post processors.
|
- [x] Enable configuration to opt-out of features, particular pre/post processors.
|
||||||
- [ ] proper date/time/now object.
|
- [ ] proper date/time/now object.
|
||||||
- [ ] Footer isn't centered properly.
|
- [ ] Footer isn't centered properly.
|
||||||
- [ ] mustache data keys for opengraph, etc.
|
- [ ] mustache data keys for opengraph, etc.
|
||||||
|
|||||||
+32
-17
@@ -35,18 +35,18 @@ Page :: struct {
|
|||||||
walk_content :: proc(site: ^Site) -> []Page {
|
walk_content :: proc(site: ^Site) -> []Page {
|
||||||
content_path := site.content_dir
|
content_path := site.content_dir
|
||||||
include_drafts := .Drafts in site.features
|
include_drafts := .Drafts in site.features
|
||||||
sectionate := .Sections in site.features
|
ext := site.markdown_extensions
|
||||||
|
|
||||||
allocator := site_allocator(site)
|
allocator := site_allocator(site)
|
||||||
|
|
||||||
pages := make([dynamic]Page, allocator)
|
pages := make([dynamic]Page, allocator)
|
||||||
|
|
||||||
collect_home(&pages, content_path, sectionate)
|
collect_home(&pages, content_path, ext)
|
||||||
collect_standalone(&pages, content_path, sectionate)
|
collect_standalone(&pages, content_path, ext)
|
||||||
|
|
||||||
posts_path := fmt.tprintf("%s/posts", content_path)
|
posts_path := fmt.tprintf("%s/posts", content_path)
|
||||||
if os.exists(posts_path) {
|
if os.exists(posts_path) {
|
||||||
collect_posts(&pages, posts_path, sectionate)
|
collect_posts(&pages, posts_path, ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
if include_drafts {
|
if include_drafts {
|
||||||
@@ -63,10 +63,10 @@ walk_content :: proc(site: ^Site) -> []Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_home :: proc(pages: ^[dynamic]Page, content_path: string, sectionate: bool) {
|
collect_home :: proc(pages: ^[dynamic]Page, content_path: string, ext: bit_set[Markdown_Extension]) {
|
||||||
html_path := fmt.tprintf("%s/index.html", content_path)
|
html_path := fmt.tprintf("%s/index.html", content_path)
|
||||||
if os.exists(html_path) {
|
if os.exists(html_path) {
|
||||||
page, ok := load_page(html_path, .Home, "", sectionate)
|
page, ok := load_page(html_path, .Home, "", ext)
|
||||||
if ok {
|
if ok {
|
||||||
page.permalink = "/"
|
page.permalink = "/"
|
||||||
append(pages, page)
|
append(pages, page)
|
||||||
@@ -76,7 +76,7 @@ collect_home :: proc(pages: ^[dynamic]Page, content_path: string, sectionate: bo
|
|||||||
|
|
||||||
md_path := fmt.tprintf("%s/index.md", content_path)
|
md_path := fmt.tprintf("%s/index.md", content_path)
|
||||||
if os.exists(md_path) {
|
if os.exists(md_path) {
|
||||||
page, ok := load_page(md_path, .Home, "", sectionate)
|
page, ok := load_page(md_path, .Home, "", ext)
|
||||||
if ok {
|
if ok {
|
||||||
page.permalink = "/"
|
page.permalink = "/"
|
||||||
append(pages, page)
|
append(pages, page)
|
||||||
@@ -84,7 +84,7 @@ collect_home :: proc(pages: ^[dynamic]Page, content_path: string, sectionate: bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_standalone :: proc(pages: ^[dynamic]Page, content_path: string, sectionate: bool) {
|
collect_standalone :: proc(pages: ^[dynamic]Page, content_path: string, ext: bit_set[Markdown_Extension]) {
|
||||||
entries, err := os.read_all_directory_by_path(content_path, context.allocator)
|
entries, err := os.read_all_directory_by_path(content_path, context.allocator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.warnf("thor: cannot read %s: %v", content_path, err)
|
log.warnf("thor: cannot read %s: %v", content_path, err)
|
||||||
@@ -104,14 +104,14 @@ collect_standalone :: proc(pages: ^[dynamic]Page, content_path: string, sectiona
|
|||||||
}
|
}
|
||||||
|
|
||||||
slug := strip_extension(entry.name)
|
slug := strip_extension(entry.name)
|
||||||
page, ok := load_page(entry.fullpath, .Standalone, slug, sectionate)
|
page, ok := load_page(entry.fullpath, .Standalone, slug, ext)
|
||||||
if ok {
|
if ok {
|
||||||
append(pages, page)
|
append(pages, page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_posts :: proc(pages: ^[dynamic]Page, posts_path: string, sectionate: bool) {
|
collect_posts :: proc(pages: ^[dynamic]Page, posts_path: string, ext: bit_set[Markdown_Extension]) {
|
||||||
entries, err := os.read_all_directory_by_path(posts_path, context.allocator)
|
entries, err := os.read_all_directory_by_path(posts_path, context.allocator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.warnf("thor: cannot read %s: %v", posts_path, err)
|
log.warnf("thor: cannot read %s: %v", posts_path, err)
|
||||||
@@ -126,7 +126,7 @@ collect_posts :: proc(pages: ^[dynamic]Page, posts_path: string, sectionate: boo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
slug := strip_extension(entry.name)
|
slug := strip_extension(entry.name)
|
||||||
page, ok := load_page(entry.fullpath, .Post, slug, sectionate)
|
page, ok := load_page(entry.fullpath, .Post, slug, ext)
|
||||||
if ok {
|
if ok {
|
||||||
append(pages, page)
|
append(pages, page)
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ collect_posts :: proc(pages: ^[dynamic]Page, posts_path: string, sectionate: boo
|
|||||||
if !os.exists(index_path) {
|
if !os.exists(index_path) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
page, ok := load_page(index_path, .Post, entry.name, sectionate)
|
page, ok := load_page(index_path, .Post, entry.name, ext)
|
||||||
if ok {
|
if ok {
|
||||||
page.bundle_dir = entry.fullpath
|
page.bundle_dir = entry.fullpath
|
||||||
append(pages, page)
|
append(pages, page)
|
||||||
@@ -152,7 +152,7 @@ load_page :: proc(
|
|||||||
file_path: string,
|
file_path: string,
|
||||||
page_type: Page_Type,
|
page_type: Page_Type,
|
||||||
slug: string,
|
slug: string,
|
||||||
sectionate: bool,
|
ext: bit_set[Markdown_Extension],
|
||||||
) -> (
|
) -> (
|
||||||
page: Page,
|
page: Page,
|
||||||
ok: bool,
|
ok: bool,
|
||||||
@@ -182,11 +182,26 @@ load_page :: proc(
|
|||||||
if strings.has_suffix(file_path, ".html") {
|
if strings.has_suffix(file_path, ".html") {
|
||||||
page.body_html = strings.clone(body)
|
page.body_html = strings.clone(body)
|
||||||
} else {
|
} else {
|
||||||
clean_body, sn_defs, mn_defs := strip_definitions(body)
|
sn_defs := make(map[string]string)
|
||||||
|
mn_defs := make(map[string]string)
|
||||||
|
clean_body := body
|
||||||
|
if .Sidenotes in ext {
|
||||||
|
clean_body, sn_defs, mn_defs = strip_definitions(body)
|
||||||
|
}
|
||||||
html := cm.markdown_to_html_from_string(clean_body, {.Unsafe})
|
html := cm.markdown_to_html_from_string(clean_body, {.Unsafe})
|
||||||
html = expand_emoji(html)
|
if .Emoji in ext {
|
||||||
html = highlight_code(inject_alerts(inject_notes(html, sn_defs, mn_defs)), file_path)
|
html = expand_emoji(html)
|
||||||
if sectionate {
|
}
|
||||||
|
if .Sidenotes in ext {
|
||||||
|
html = inject_notes(html, sn_defs, mn_defs)
|
||||||
|
}
|
||||||
|
if .Alerts in ext {
|
||||||
|
html = inject_alerts(html)
|
||||||
|
}
|
||||||
|
if .Highlight in ext {
|
||||||
|
html = highlight_code(html, file_path)
|
||||||
|
}
|
||||||
|
if .Sections in ext {
|
||||||
html = wrap_sections(html)
|
html = wrap_sections(html)
|
||||||
}
|
}
|
||||||
page.body_html = html
|
page.body_html = html
|
||||||
|
|||||||
@@ -9,49 +9,84 @@ import "core:os"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
|
|
||||||
|
// Site is the primary workhorse.
|
||||||
Site :: struct {
|
Site :: struct {
|
||||||
arena: mem.Dynamic_Arena,
|
arena: mem.Dynamic_Arena,
|
||||||
title: string,
|
title: string,
|
||||||
description: string,
|
description: string,
|
||||||
author: string,
|
author: string,
|
||||||
base_url: string,
|
base_url: string,
|
||||||
config_path: string,
|
config_path: string,
|
||||||
content_dir: string,
|
content_dir: string,
|
||||||
assets_dir: string,
|
assets_dir: string,
|
||||||
output_dir: string,
|
output_dir: string,
|
||||||
layouts_dir: string,
|
layouts_dir: string,
|
||||||
params: json.Value,
|
params: json.Object,
|
||||||
features: bit_set[Feature],
|
features: bit_set[Feature],
|
||||||
|
markdown_extensions: bit_set[Markdown_Extension],
|
||||||
}
|
}
|
||||||
|
|
||||||
Feature :: enum {
|
Feature :: enum {
|
||||||
Sections,
|
|
||||||
Drafts,
|
Drafts,
|
||||||
Minify,
|
Minify,
|
||||||
Watch,
|
Watch,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Markdown_Extension :: enum {
|
||||||
|
Emoji,
|
||||||
|
Sidenotes,
|
||||||
|
Alerts,
|
||||||
|
Highlight,
|
||||||
|
Sections,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_MARKDOWN_EXTENSIONS :: bit_set[Markdown_Extension] {
|
||||||
|
.Emoji,
|
||||||
|
.Sidenotes,
|
||||||
|
.Alerts,
|
||||||
|
// .Highlight,
|
||||||
|
// .Sections,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration loaded from `thor.json`. Gets folded in to Site before
|
||||||
|
// Flags
|
||||||
|
Config_File :: struct {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
base_url: string,
|
||||||
|
author: string,
|
||||||
|
content_dir: string,
|
||||||
|
assets_dir: string,
|
||||||
|
output_dir: string,
|
||||||
|
layouts_dir: string,
|
||||||
|
markdown_extensions: json.Value,
|
||||||
|
params: json.Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration loaded from command line arguments. Gets folded in to Site
|
||||||
|
// after Config_File
|
||||||
Flags :: struct {
|
Flags :: struct {
|
||||||
config_path: string `args:"name=config"`,
|
config_path: string `args:"name=config" usage:"Path to thor.json config file"`,
|
||||||
title: string,
|
base_url: string `args:"name=base-url" usage:"Site base URL (e.g. https://example.com)"`,
|
||||||
description: string,
|
content_dir: string `args:"name=content" usage:"Path to content directory"`,
|
||||||
base_url: string `args:"name=base-url"`,
|
assets_dir: string `args:"name=assets" usage:"Path to assets directory (CSS, JS, fonts, images)"`,
|
||||||
content_dir: string `args:"name=content"`,
|
output_dir: string `args:"name=output" usage:"Path to output directory (default: public/)"`,
|
||||||
assets_dir: string `args:"name=assets"`,
|
drafts: bool `args:"name=drafts" usage:"Include draft pages in the build"`,
|
||||||
output_dir: string `args:"name=output"`,
|
watch: bool `usage:"Rebuild on file changes (polls every 5 seconds)"`,
|
||||||
layouts_dir: string,
|
minify: bool `args:"name=minify" usage:"Minify HTML output and CSS assets"`,
|
||||||
author: string,
|
md_enable: string `args:"name=ext" usage:"Enable markdown extensions (comma-separated: emoji,sidenotes,alerts,highlight,sections)"`,
|
||||||
params: json.Value,
|
md_disable: string `args:"name=no-ext" usage:"Disable markdown extensions (comma-separated: emoji,sidenotes,alerts,highlight,sections)"`,
|
||||||
sectionate: bool `args:"name=sections"`,
|
|
||||||
drafts: bool `args:"name=drafts"`,
|
|
||||||
watch: bool,
|
|
||||||
minify: bool `args:"name=minify"`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init_site :: proc(site: ^Site, args: []string) {
|
init_site :: proc(site: ^Site, args: []string) {
|
||||||
_flags: Flags
|
mem.dynamic_arena_init(&site.arena, alignment = 64)
|
||||||
mem.dynamic_arena_init(&site.arena, alignment = 64) // FIXME: This is a hack
|
|
||||||
alloc := site_allocator(site)
|
alloc := site_allocator(site)
|
||||||
|
|
||||||
|
// Set defaults
|
||||||
|
site.base_url = "http://localhost:8080"
|
||||||
|
site.markdown_extensions = DEFAULT_MARKDOWN_EXTENSIONS
|
||||||
|
|
||||||
|
_flags: Flags
|
||||||
flags.parse_or_exit(&_flags, args, .Odin, alloc)
|
flags.parse_or_exit(&_flags, args, .Odin, alloc)
|
||||||
|
|
||||||
path := _flags.config_path
|
path := _flags.config_path
|
||||||
@@ -65,106 +100,127 @@ init_site :: proc(site: ^Site, args: []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, cfg_ok := load_site_config(path, alloc)
|
config: Config_File
|
||||||
if cfg_ok {
|
config_loaded := load_config_file(&config, path, alloc)
|
||||||
merge_flags(&cfg, _flags)
|
|
||||||
} else {
|
|
||||||
cfg = _flags
|
|
||||||
}
|
|
||||||
|
|
||||||
site_apply_flags(site, cfg)
|
|
||||||
|
|
||||||
// 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 {
|
||||||
config_dir = path[:idx]
|
config_dir = path[:idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardcoded defaults (lowest precedence)
|
if config_loaded {
|
||||||
// TODO: Probably shouldn't use temp allocator here?
|
site_apply_config(site, config, config_dir)
|
||||||
if site.content_dir == "" {
|
} else {
|
||||||
site.content_dir = fmt.tprintf("%s/content", config_dir)
|
site_apply_path_defaults(site, 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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
site_apply_cli_flags(site, _flags)
|
||||||
|
site.config_path = path
|
||||||
}
|
}
|
||||||
|
|
||||||
load_site_config :: proc(
|
load_config_file :: proc(
|
||||||
|
config: ^Config_File,
|
||||||
path: string,
|
path: string,
|
||||||
allocator := context.allocator,
|
allocator := context.allocator,
|
||||||
) -> (
|
) -> bool {
|
||||||
config: Flags,
|
|
||||||
ok: bool,
|
|
||||||
) {
|
|
||||||
data, err := os.read_entire_file_from_path(path, allocator)
|
data, err := os.read_entire_file_from_path(path, allocator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
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 false
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = true
|
return true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
merge_flags :: proc(config: ^Flags, flags: Flags) {
|
site_apply_config :: proc(site: ^Site, config: Config_File, config_dir: string) {
|
||||||
if flags.base_url != "" {
|
if config.title != "" do site.title = config.title
|
||||||
config.base_url = flags.base_url
|
if config.description != "" do site.description = config.description
|
||||||
|
if config.author != "" do site.author = config.author
|
||||||
|
if config.base_url != "" do site.base_url = config.base_url
|
||||||
|
|
||||||
|
site.content_dir =
|
||||||
|
config.content_dir if config.content_dir != "" else fmt.tprintf("%s/content", config_dir)
|
||||||
|
site.assets_dir =
|
||||||
|
config.assets_dir if config.assets_dir != "" else fmt.tprintf("%s/assets", config_dir)
|
||||||
|
site.output_dir =
|
||||||
|
config.output_dir if config.output_dir != "" else fmt.tprintf("%s/public", config_dir)
|
||||||
|
site.layouts_dir =
|
||||||
|
config.layouts_dir if config.layouts_dir != "" else fmt.tprintf("%s/layouts", config_dir)
|
||||||
|
|
||||||
|
if params, ok := config.markdown_extensions.(json.Object); ok {
|
||||||
|
site.params = params
|
||||||
}
|
}
|
||||||
if flags.content_dir != "" {
|
|
||||||
config.content_dir = flags.content_dir
|
// Apply markdown extensions from config
|
||||||
|
if ext_obj, ok := config.markdown_extensions.(json.Object); ok {
|
||||||
|
apply_extension_config(&site.markdown_extensions, ext_obj)
|
||||||
}
|
}
|
||||||
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_apply_path_defaults :: proc(site: ^Site, config_dir: string) {
|
||||||
site.title = flags.title
|
site.content_dir = fmt.tprintf("%s/content", config_dir)
|
||||||
site.description = flags.description
|
site.assets_dir = fmt.tprintf("%s/assets", config_dir)
|
||||||
site.author = flags.author
|
site.output_dir = fmt.tprintf("%s/public", config_dir)
|
||||||
site.base_url = flags.base_url
|
site.layouts_dir = fmt.tprintf("%s/layouts", config_dir)
|
||||||
site.config_path = flags.config_path
|
}
|
||||||
site.content_dir = flags.content_dir
|
|
||||||
site.assets_dir = flags.assets_dir
|
site_apply_cli_flags :: proc(site: ^Site, flags: Flags) {
|
||||||
site.output_dir = flags.output_dir
|
if flags.base_url != "" do site.base_url = flags.base_url
|
||||||
site.layouts_dir = flags.layouts_dir
|
if flags.content_dir != "" do site.content_dir = flags.content_dir
|
||||||
site.params = flags.params
|
if flags.assets_dir != "" do site.assets_dir = flags.assets_dir
|
||||||
|
if flags.output_dir != "" do site.output_dir = flags.output_dir
|
||||||
|
|
||||||
if flags.sectionate {site.features += {.Sections}}
|
|
||||||
if flags.drafts {site.features += {.Drafts}}
|
if flags.drafts {site.features += {.Drafts}}
|
||||||
if flags.watch {site.features += {.Watch}}
|
if flags.watch {site.features += {.Watch}}
|
||||||
if flags.minify {site.features += {.Minify}}
|
if flags.minify {site.features += {.Minify}}
|
||||||
|
|
||||||
|
site.markdown_extensions += parse_extension_list(flags.md_enable)
|
||||||
|
site.markdown_extensions -= parse_extension_list(flags.md_disable)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_extension_list :: proc(s: string) -> bit_set[Markdown_Extension] {
|
||||||
|
result: bit_set[Markdown_Extension]
|
||||||
|
if s == "" do return result
|
||||||
|
for part in strings.split(s, ",", allocator = context.temp_allocator) {
|
||||||
|
name := strings.to_lower(strings.trim_space(part), allocator = context.temp_allocator)
|
||||||
|
switch name {
|
||||||
|
case "emoji":
|
||||||
|
result += {.Emoji}
|
||||||
|
case "sidenotes":
|
||||||
|
result += {.Sidenotes}
|
||||||
|
case "alerts":
|
||||||
|
result += {.Alerts}
|
||||||
|
case "highlight":
|
||||||
|
result += {.Highlight}
|
||||||
|
case "sections":
|
||||||
|
result += {.Sections}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_extension_config :: proc(ext: ^bit_set[Markdown_Extension], config: json.Object) {
|
||||||
|
for name, val in config {
|
||||||
|
enabled, is_bool := val.(json.Boolean)
|
||||||
|
if !is_bool do continue
|
||||||
|
switch name {
|
||||||
|
case "emoji":
|
||||||
|
if enabled {ext^ += {.Emoji}} else {ext^ -= {.Emoji}}
|
||||||
|
case "sidenotes":
|
||||||
|
if enabled {ext^ += {.Sidenotes}} else {ext^ -= {.Sidenotes}}
|
||||||
|
case "alerts":
|
||||||
|
if enabled {ext^ += {.Alerts}} else {ext^ -= {.Alerts}}
|
||||||
|
case "highlight":
|
||||||
|
if enabled {ext^ += {.Highlight}} else {ext^ -= {.Highlight}}
|
||||||
|
case "sections":
|
||||||
|
if enabled {ext^ += {.Sections}} else {ext^ -= {.Sections}}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
site_allocator :: proc(site: ^Site) -> mem.Allocator {
|
site_allocator :: proc(site: ^Site) -> mem.Allocator {
|
||||||
|
|||||||
+30
-75
@@ -18,7 +18,7 @@ write_temp_config :: proc(name: string, content: string) -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_load_site_config :: proc(t: ^testing.T) {
|
test_load_config_file :: proc(t: ^testing.T) {
|
||||||
path := write_temp_config(
|
path := write_temp_config(
|
||||||
"valid",
|
"valid",
|
||||||
`{
|
`{
|
||||||
@@ -35,7 +35,8 @@ test_load_site_config :: proc(t: ^testing.T) {
|
|||||||
)
|
)
|
||||||
defer os.remove(path)
|
defer os.remove(path)
|
||||||
|
|
||||||
cfg, ok := load_site_config(path, context.temp_allocator)
|
cfg: Config_File
|
||||||
|
ok := load_config_file(&cfg, path, context.temp_allocator)
|
||||||
|
|
||||||
testing.expect(t, ok)
|
testing.expect(t, ok)
|
||||||
testing.expect_value(t, cfg.title, "Test Site")
|
testing.expect_value(t, cfg.title, "Test Site")
|
||||||
@@ -62,30 +63,33 @@ test_load_site_config :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_load_site_config_missing_file :: proc(t: ^testing.T) {
|
test_load_config_file_missing :: proc(t: ^testing.T) {
|
||||||
_, ok := load_site_config("./nonexistent_thor_test.json", context.temp_allocator)
|
cfg: Config_File
|
||||||
|
ok := load_config_file(&cfg, "./nonexistent_thor_test.json", context.temp_allocator)
|
||||||
testing.expect(t, !ok)
|
testing.expect(t, !ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_load_site_config_invalid_json :: proc(t: ^testing.T) {
|
test_load_config_file_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)
|
||||||
|
|
||||||
|
cfg: Config_File
|
||||||
ok := false
|
ok := false
|
||||||
{
|
{
|
||||||
context.logger = log.nil_logger()
|
context.logger = log.nil_logger()
|
||||||
_, ok = load_site_config(path, context.temp_allocator)
|
ok = load_config_file(&cfg, path, context.temp_allocator)
|
||||||
}
|
}
|
||||||
testing.expect(t, !ok)
|
testing.expect(t, !ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_load_site_config_partial :: proc(t: ^testing.T) {
|
test_load_config_file_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)
|
||||||
|
|
||||||
cfg, ok := load_site_config(path, context.temp_allocator)
|
cfg: Config_File
|
||||||
|
ok := load_config_file(&cfg, path, context.temp_allocator)
|
||||||
|
|
||||||
testing.expect(t, ok)
|
testing.expect(t, ok)
|
||||||
testing.expect_value(t, cfg.title, "Partial")
|
testing.expect_value(t, cfg.title, "Partial")
|
||||||
@@ -94,73 +98,6 @@ test_load_site_config_partial :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, cfg.params == nil)
|
testing.expect(t, cfg.params == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_site_merge_overrides :: proc(t: ^testing.T) {
|
|
||||||
config := Flags {
|
|
||||||
base_url = "https://original.com",
|
|
||||||
content_dir = "./content",
|
|
||||||
}
|
|
||||||
flags := Flags {
|
|
||||||
base_url = "https://override.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
merge_flags(&config, flags)
|
|
||||||
|
|
||||||
testing.expect_value(t, config.base_url, "https://override.com")
|
|
||||||
testing.expect_value(t, config.content_dir, "./content")
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_site_merge_empty_flags_keep_config :: proc(t: ^testing.T) {
|
|
||||||
config := Flags {
|
|
||||||
base_url = "https://keep.com",
|
|
||||||
content_dir = "./keep",
|
|
||||||
}
|
|
||||||
flags := Flags{}
|
|
||||||
|
|
||||||
merge_flags(&config, flags)
|
|
||||||
|
|
||||||
testing.expect_value(t, config.base_url, "https://keep.com")
|
|
||||||
testing.expect_value(t, config.content_dir, "./keep")
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_site_merge_drafts_true :: proc(t: ^testing.T) {
|
|
||||||
config := Flags {
|
|
||||||
drafts = false,
|
|
||||||
}
|
|
||||||
flags := Flags {
|
|
||||||
drafts = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
merge_flags(&config, flags)
|
|
||||||
testing.expect(t, config.drafts)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_site_merge_drafts_false_preserves :: proc(t: ^testing.T) {
|
|
||||||
config := Flags {
|
|
||||||
drafts = false,
|
|
||||||
}
|
|
||||||
flags := Flags {
|
|
||||||
drafts = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
merge_flags(&config, flags)
|
|
||||||
testing.expect(t, !config.drafts)
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
|
||||||
test_site_merge_config_path :: proc(t: ^testing.T) {
|
|
||||||
config := Flags{}
|
|
||||||
flags := Flags {
|
|
||||||
config_path = "./custom/thor.json",
|
|
||||||
}
|
|
||||||
|
|
||||||
merge_flags(&config, flags)
|
|
||||||
testing.expect_value(t, config.config_path, "./custom/thor.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_init_site_defaults_no_config :: proc(t: ^testing.T) {
|
test_init_site_defaults_no_config :: proc(t: ^testing.T) {
|
||||||
site: Site
|
site: Site
|
||||||
@@ -169,9 +106,13 @@ test_init_site_defaults_no_config :: proc(t: ^testing.T) {
|
|||||||
defer destroy_site(&site)
|
defer destroy_site(&site)
|
||||||
|
|
||||||
testing.expect_value(t, site.content_dir, "./content")
|
testing.expect_value(t, site.content_dir, "./content")
|
||||||
|
testing.expect_value(t, site.assets_dir, "./assets")
|
||||||
testing.expect_value(t, site.output_dir, "./public")
|
testing.expect_value(t, site.output_dir, "./public")
|
||||||
testing.expect_value(t, site.layouts_dir, "./layouts")
|
testing.expect_value(t, site.layouts_dir, "./layouts")
|
||||||
testing.expect_value(t, site.base_url, "http://localhost:8080")
|
testing.expect_value(t, site.base_url, "http://localhost:8080")
|
||||||
|
testing.expect(t, .Emoji in site.markdown_extensions)
|
||||||
|
testing.expect(t, .Sidenotes in site.markdown_extensions)
|
||||||
|
testing.expect(t, .Alerts in site.markdown_extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -182,6 +123,7 @@ test_init_site_config_dir_relative :: proc(t: ^testing.T) {
|
|||||||
defer destroy_site(&site)
|
defer destroy_site(&site)
|
||||||
|
|
||||||
testing.expect_value(t, site.content_dir, "./sub/content")
|
testing.expect_value(t, site.content_dir, "./sub/content")
|
||||||
|
testing.expect_value(t, site.assets_dir, "./sub/assets")
|
||||||
testing.expect_value(t, site.output_dir, "./sub/public")
|
testing.expect_value(t, site.output_dir, "./sub/public")
|
||||||
testing.expect_value(t, site.layouts_dir, "./sub/layouts")
|
testing.expect_value(t, site.layouts_dir, "./sub/layouts")
|
||||||
}
|
}
|
||||||
@@ -217,3 +159,16 @@ test_init_site_full_pipeline :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, site.base_url, "https://config.com")
|
testing.expect_value(t, site.base_url, "https://config.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_init_site_md_enable_disable :: proc(t: ^testing.T) {
|
||||||
|
site: Site
|
||||||
|
args := []string{"thor", "-config:./nonexistent.json", "-ext:highlight,sections", "-no-ext:emoji"}
|
||||||
|
init_site(&site, args)
|
||||||
|
defer destroy_site(&site)
|
||||||
|
|
||||||
|
testing.expect(t, .Highlight in site.markdown_extensions)
|
||||||
|
testing.expect(t, .Sections in site.markdown_extensions)
|
||||||
|
testing.expect(t, !(.Emoji in site.markdown_extensions))
|
||||||
|
testing.expect(t, .Sidenotes in site.markdown_extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user