mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
feat: Added Unified file system / default layout mounts.
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
- 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
|
||||||
|
- [ ] Clean up the default layouts
|
||||||
- [ ] Performance
|
- [ ] Performance
|
||||||
- [ ] See if we can disable bounds checks in `write_indented` and elsewhere.
|
- [ ] See if we can disable bounds checks in `write_indented` and elsewhere.
|
||||||
- [ ] Instead of loading the site fresh each time in watch mode, create a
|
- [ ] Instead of loading the site fresh each time in watch mode, create a
|
||||||
`reload_site` proc, that just updates changed resources.
|
`reload_site` proc, that just updates changed resources.
|
||||||
|
- [ ] Only publish referenced assets.
|
||||||
- [ ] mustache data keys for opengraph, etc.
|
- [ ] mustache data keys for opengraph, etc.
|
||||||
- [ ] Block attributes on code fences (`{ #ex-1 }`) — hello-world.md
|
- [ ] Block attributes on code fences (`{ #ex-1 }`) — hello-world.md
|
||||||
- [ ] include-code shortcode (`{{< include-code ... >}}`) — i-ported-fd-to-odin
|
- [ ] include-code shortcode (`{{< include-code ... >}}`) — i-ported-fd-to-odin
|
||||||
@@ -35,6 +37,7 @@
|
|||||||
- [ ] Free cmark HTML output (`body_html`) — cmark allocates via C malloc, not the arena, so it leaks per iteration in watch mode
|
- [ ] Free cmark HTML output (`body_html`) — cmark allocates via C malloc, not the arena, so it leaks per iteration in watch mode
|
||||||
- [ ] Rename `{{#is_post}}` to `{{#is_article}}` in templates and data model — currently hardcoded to posts section, should use `is_article` (any page with a section) instead.
|
- [ ] Rename `{{#is_post}}` to `{{#is_article}}` in templates and data model — currently hardcoded to posts section, should use `is_article` (any page with a section) instead.
|
||||||
- [ ] Split `load_page` into frontmatter-parse + body-process phases so draft pages can skip the markdown pipeline entirely
|
- [ ] Split `load_page` into frontmatter-parse + body-process phases so draft pages can skip the markdown pipeline entirely
|
||||||
|
- [ ] Mount content in VFS
|
||||||
- [ ] commands
|
- [ ] commands
|
||||||
- [ ] `build` alias of default
|
- [ ] `build` alias of default
|
||||||
- [ ] `init` set up new project
|
- [ ] `init` set up new project
|
||||||
|
|||||||
+22
-44
@@ -5,57 +5,35 @@ import "core:log"
|
|||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
// copy_assets_dir recursively copies files from assets_dir to output_dir.
|
copy_assets_dir :: proc(vfs: ^VFS, output_dir: string, features: bit_set[Feature]) {
|
||||||
// .css files are minified when .Minify is enabled; all other files are copied verbatim.
|
for virtual_path, entry in vfs.files {
|
||||||
// Silently skips if assets_dir doesn't exist.
|
if !strings.has_prefix(virtual_path, "assets/") {
|
||||||
copy_assets_dir :: proc(assets_dir: string, output_dir: string, features: bit_set[Feature]) {
|
continue
|
||||||
if !os.exists(assets_dir) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
copy_assets_recursive(assets_dir, "", output_dir, features)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_assets_recursive :: proc(
|
rel := virtual_path[len("assets/"):]
|
||||||
current: string,
|
|
||||||
rel_prefix: string,
|
|
||||||
output_dir: string,
|
|
||||||
features: bit_set[Feature],
|
|
||||||
) {
|
|
||||||
entries, err := os.read_all_directory_by_path(current, context.allocator)
|
|
||||||
if err != nil {
|
|
||||||
log.warnf("thor: cannot read %s: %v", current, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.file_info_slice_delete(entries, context.allocator)
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
rel := rel_prefix == "" ? entry.name : fmt.tprintf("%s/%s", rel_prefix, entry.name)
|
|
||||||
switch entry.type {
|
|
||||||
case .Regular:
|
|
||||||
dest := fmt.tprintf("%s/%s", output_dir, rel)
|
dest := fmt.tprintf("%s/%s", output_dir, rel)
|
||||||
|
|
||||||
if idx := strings.last_index(dest, "/"); idx >= 0 {
|
if idx := strings.last_index(dest, "/"); idx >= 0 {
|
||||||
if err := os.make_directory_all(dest[:idx]); err != nil && err != .Exist {
|
if err := os.make_directory_all(dest[:idx]); err != nil && err != .Exist {
|
||||||
log.warnf("thor: cannot create %s: %v", dest[:idx], err)
|
log.warnf("thor: cannot create %s: %v", dest[:idx], err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if .Minify in features && strings.has_suffix(entry.name, ".css") {
|
|
||||||
data, read_err := os.read_entire_file_from_path(entry.fullpath, context.allocator)
|
|
||||||
if read_err != nil {
|
|
||||||
log.warnf("thor: cannot read %s: %v", entry.fullpath, read_err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
minified := minify_css(string(data))
|
|
||||||
write_file(dest, minified)
|
|
||||||
} else {
|
|
||||||
if err := os.copy_file(dest, entry.fullpath); err != nil {
|
|
||||||
log.warnf("thor: cannot copy %s: %v", entry.fullpath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .Directory:
|
|
||||||
copy_assets_recursive(entry.fullpath, rel, output_dir, features)
|
|
||||||
case .Undetermined, .Symlink, .Named_Pipe, .Socket, .Block_Device, .Character_Device:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if .Minify in features && strings.has_suffix(rel, ".css") {
|
||||||
|
data, ok := vfs_get(vfs, virtual_path)
|
||||||
|
if ok {
|
||||||
|
write_file(dest, minify_css(string(data)))
|
||||||
|
}
|
||||||
|
} else if entry.data != nil {
|
||||||
|
if err := os.write_entire_file(dest, entry.data); err != nil {
|
||||||
|
log.warnf("thor: cannot write %s: %v", dest, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := os.copy_file(dest, entry.fs_path); err != nil {
|
||||||
|
log.warnf("thor: cannot copy %s: %v", entry.fs_path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
DEFAULTS_PATH :: #directory + os.Path_Separator_String + "defaults"
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{title}}</title>
|
||||||
|
{{> head}}
|
||||||
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{> nav}}{{$content}}{{/content}}
|
||||||
|
{{> footer}}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{{<base}}
|
||||||
|
{{$content}}
|
||||||
|
<main>
|
||||||
|
<header>
|
||||||
|
{{&body}}
|
||||||
|
</header>
|
||||||
|
<ul>
|
||||||
|
{{#list_pages}} <li><a href="{{permalink}}">{{&title}}</a><span>{{#date_iso}}<time
|
||||||
|
datetime="{{date_iso}}">{{date_display}}</time>{{/date_iso}}</span>
|
||||||
|
</li>
|
||||||
|
{{/list_pages}}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
{{/content}}
|
||||||
|
{{/base}}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{{<base}}
|
||||||
|
{{$content}}
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
<h1>{{page_title}}</h1>
|
||||||
|
{{#date_iso}} <time class="subtitle" datetime="{{date_iso}}">{{date_display}}</time>
|
||||||
|
{{/date_iso}} {{&body}}
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
{{/content}}
|
||||||
|
{{/base}}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<footer>
|
||||||
|
<p>© {{now.year}} {{author}}</p>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<meta property="og:url" content="{{og_url}}">
|
||||||
|
<meta property="og:site_name" content="{{og_site_name}}">
|
||||||
|
<meta property="og:title" content="{{og_title}}">
|
||||||
|
<meta property="og:description" content="{{og_description}}">
|
||||||
|
<meta property="og:locale" content="en_us">
|
||||||
|
<meta property="og:type" content="{{og_type}}">
|
||||||
|
{{#is_article}}<meta property="article:section" content="{{og_section}}">
|
||||||
|
<meta property="article:published_time" content="{{og_published}}">
|
||||||
|
{{/is_article}}<meta property="og:image" content="{{og_image}}">
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">{{title}}</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{{<base}}
|
||||||
|
{{$content}}
|
||||||
|
<main>
|
||||||
|
<h1>{{page_title}}</h1>
|
||||||
|
{{&body}}
|
||||||
|
{{#year_sections}}
|
||||||
|
<section>
|
||||||
|
<h2>{{year}}</h2>
|
||||||
|
<ul>
|
||||||
|
{{#posts}} <li><a href="{{permalink}}">{{&title}}</a><span>{{#date_iso}}<time
|
||||||
|
datetime="{{date_iso}}">{{date_display}}</time>{{/date_iso}}</span>
|
||||||
|
</li>
|
||||||
|
{{/posts}}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
{{/year_sections}}
|
||||||
|
</main>
|
||||||
|
{{/content}}
|
||||||
|
{{/base}}
|
||||||
@@ -44,13 +44,12 @@ main :: proc() {
|
|||||||
defer destroy_site(&site)
|
defer destroy_site(&site)
|
||||||
// TODO: Make it so this isn't necessary
|
// TODO: Make it so this isn't necessary
|
||||||
context.allocator = site_allocator(&site)
|
context.allocator = site_allocator(&site)
|
||||||
|
build_vfs(&site)
|
||||||
|
|
||||||
site_load_content(&site)
|
site_load_content(&site)
|
||||||
render_site(&site)
|
render_site(&site)
|
||||||
|
|
||||||
if !(.Watch in site.features) {
|
(.Watch in site.features) or_break
|
||||||
break
|
|
||||||
}
|
|
||||||
time.sleep(5 * time.Second)
|
time.sleep(5 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-58
@@ -102,24 +102,24 @@ og_type :: proc(is_article: bool) -> string {
|
|||||||
return "website"
|
return "website"
|
||||||
}
|
}
|
||||||
|
|
||||||
load_template :: proc(layouts_dir: string, name: string) -> mustache.Template {
|
load_template :: proc(vfs: ^VFS, virtual_path: string) -> mustache.Template {
|
||||||
data, _ := os.read_entire_file_from_path(
|
data, ok := vfs_get(vfs, virtual_path)
|
||||||
fmt.tprintf("%s/%s", layouts_dir, name),
|
if !ok {
|
||||||
context.allocator,
|
log.warnf("thor: template %s not found", virtual_path)
|
||||||
)
|
return mustache.Template{}
|
||||||
|
}
|
||||||
tpl, err := mustache.parse(string(data))
|
tpl, err := mustache.parse(string(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.warnf("thor: failed to parse template %s: %v", name, err)
|
log.warnf("thor: failed to parse template %s: %v", virtual_path, err)
|
||||||
}
|
}
|
||||||
return tpl
|
return tpl
|
||||||
}
|
}
|
||||||
|
|
||||||
get_template :: proc(
|
get_template :: proc(
|
||||||
layouts_dir: string,
|
vfs: ^VFS,
|
||||||
layout: string,
|
layout: string,
|
||||||
cache: ^map[string]mustache.Template,
|
cache: ^map[string]mustache.Template,
|
||||||
) -> mustache.Template {
|
) -> mustache.Template {
|
||||||
// Build fallback chain: <layout> → [section_index] → page → base
|
|
||||||
chain: [4]string
|
chain: [4]string
|
||||||
n := 0
|
n := 0
|
||||||
chain[n] = layout; n += 1
|
chain[n] = layout; n += 1
|
||||||
@@ -138,18 +138,18 @@ get_template :: proc(
|
|||||||
if cached, ok := cache[candidate]; ok {
|
if cached, ok := cache[candidate]; ok {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
filename := fmt.tprintf("%s.html", candidate)
|
virtual := fmt.tprintf("layouts/%s.html", candidate)
|
||||||
if os.exists(fmt.tprintf("%s/%s", layouts_dir, filename)) {
|
if _, ok := vfs_get(vfs, virtual); ok {
|
||||||
tpl := load_template(layouts_dir, filename)
|
tpl := load_template(vfs, virtual)
|
||||||
cache[candidate] = tpl
|
cache[candidate] = tpl
|
||||||
return tpl
|
return tpl
|
||||||
}
|
}
|
||||||
if candidate != chain[n - 1] {
|
if candidate != chain[n - 1] {
|
||||||
log.warnf("thor: template %s not found, falling back", filename)
|
log.warnf("thor: template %s not found, falling back", virtual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.errorf("thor: base.html not found in %s", layouts_dir)
|
log.errorf("thor: base.html not found in VFS")
|
||||||
return mustache.Template{}
|
return mustache.Template{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +181,8 @@ render_site :: proc(site: ^Site) {
|
|||||||
sort_pages_by_date(pages)
|
sort_pages_by_date(pages)
|
||||||
|
|
||||||
// Load shared resources
|
// Load shared resources
|
||||||
partials := load_partials(site.layouts_dir)
|
partials := load_partials(&site.vfs)
|
||||||
partials["base"] = load_template(site.layouts_dir, "base.html")
|
partials["base"] = load_template(&site.vfs, "layouts/base.html")
|
||||||
|
|
||||||
template_cache: map[string]mustache.Template
|
template_cache: map[string]mustache.Template
|
||||||
defer delete(template_cache)
|
defer delete(template_cache)
|
||||||
@@ -225,7 +225,7 @@ render_site :: proc(site: ^Site) {
|
|||||||
if page._is_index {
|
if page._is_index {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tpl := get_template(site.layouts_dir, page.layout, &template_cache)
|
tpl := get_template(&site.vfs, page.layout, &template_cache)
|
||||||
html := render_page_html(page, site, tpl, partials, base)
|
html := render_page_html(page, site, tpl, partials, base)
|
||||||
if .Minify in site.features {
|
if .Minify in site.features {
|
||||||
html = minify_html(html)
|
html = minify_html(html)
|
||||||
@@ -246,7 +246,7 @@ render_site :: proc(site: ^Site) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout := fmt.tprintf("%s_index", section)
|
layout := fmt.tprintf("%s_index", section)
|
||||||
section_tpl := get_template(site.layouts_dir, layout, &template_cache)
|
section_tpl := get_template(&site.vfs, layout, &template_cache)
|
||||||
html := render_section(
|
html := render_section(
|
||||||
site,
|
site,
|
||||||
section,
|
section,
|
||||||
@@ -264,7 +264,7 @@ render_site :: proc(site: ^Site) {
|
|||||||
|
|
||||||
// Render home page
|
// Render home page
|
||||||
if has_home {
|
if has_home {
|
||||||
home_tpl := get_template(site.layouts_dir, "home", &template_cache)
|
home_tpl := get_template(&site.vfs, "home", &template_cache)
|
||||||
home_html := render_home_html(home, site, home_tpl, partials, base)
|
home_html := render_home_html(home, site, home_tpl, partials, base)
|
||||||
if .Minify in site.features {
|
if .Minify in site.features {
|
||||||
home_html = minify_html(home_html)
|
home_html = minify_html(home_html)
|
||||||
@@ -281,7 +281,7 @@ render_site :: proc(site: ^Site) {
|
|||||||
write_file(fmt.tprintf("%s/sitemap.xml", site.output_dir), sitemap)
|
write_file(fmt.tprintf("%s/sitemap.xml", site.output_dir), sitemap)
|
||||||
|
|
||||||
// Copy and optionally minify assets directory
|
// Copy and optionally minify assets directory
|
||||||
copy_assets_dir(site.assets_dir, site.output_dir, site.features)
|
copy_assets_dir(&site.vfs, site.output_dir, site.features)
|
||||||
|
|
||||||
// Generate robots.txt
|
// Generate robots.txt
|
||||||
robots := fmt.aprintf("User-agent: *\nAllow: /\nSitemap: %s/sitemap.xml\n", site.base_url)
|
robots := fmt.aprintf("User-agent: *\nAllow: /\nSitemap: %s/sitemap.xml\n", site.base_url)
|
||||||
@@ -393,52 +393,32 @@ render_section :: proc(
|
|||||||
return render_template(content_tpl, data, partials)
|
return render_template(content_tpl, data, partials)
|
||||||
}
|
}
|
||||||
|
|
||||||
load_partials :: proc(layouts_dir: string) -> map[string]mustache.Template {
|
load_partials :: proc(vfs: ^VFS) -> map[string]mustache.Template {
|
||||||
partials: map[string]mustache.Template
|
partials: map[string]mustache.Template
|
||||||
partials_dir := fmt.tprintf("%s/partials", layouts_dir)
|
prefix := "layouts/partials/"
|
||||||
load_partials_recursive(&partials, partials_dir, "")
|
for virtual_path in vfs.files {
|
||||||
return partials
|
if !strings.has_prefix(virtual_path, prefix) {
|
||||||
}
|
|
||||||
|
|
||||||
load_partials_recursive :: proc(
|
|
||||||
partials: ^map[string]mustache.Template,
|
|
||||||
base_dir: string,
|
|
||||||
rel_prefix: string,
|
|
||||||
) {
|
|
||||||
entries, err := os.read_all_directory_by_path(base_dir, context.allocator)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.file_info_slice_delete(entries, context.allocator)
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
#partial switch entry.type {
|
|
||||||
case .Regular:
|
|
||||||
name := entry.name
|
|
||||||
if !strings.has_suffix(name, ".html") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stripped := name[:len(name) - len(".html")]
|
if !strings.has_suffix(virtual_path, ".html") {
|
||||||
key := stripped
|
continue
|
||||||
if rel_prefix != "" {
|
|
||||||
key = fmt.tprintf("%s/%s", rel_prefix, stripped)
|
|
||||||
}
|
}
|
||||||
data, ok := os.read_entire_file_from_path(entry.fullpath, context.allocator)
|
|
||||||
if ok == nil {
|
stripped := virtual_path[len(prefix):]
|
||||||
tpl, perr := mustache.parse(string(data))
|
key := stripped[:len(stripped) - len(".html")]
|
||||||
if perr != nil {
|
|
||||||
log.warnf("thor: failed to parse partial %s: %v", key, perr)
|
data, ok := vfs_get(vfs, virtual_path)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tpl, err := mustache.parse(string(data))
|
||||||
|
if err != nil {
|
||||||
|
log.warnf("thor: failed to parse partial %s: %v", key, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
partials[key] = tpl
|
partials[key] = tpl
|
||||||
}
|
}
|
||||||
case .Directory:
|
return partials
|
||||||
sub_prefix := entry.name
|
|
||||||
if rel_prefix != "" {
|
|
||||||
sub_prefix = fmt.tprintf("%s/%s", rel_prefix, entry.name)
|
|
||||||
}
|
|
||||||
load_partials_recursive(partials, entry.fullpath, sub_prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
format_date :: proc(iso: string) -> string {
|
format_date :: proc(iso: string) -> string {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import "core:strings"
|
|||||||
Site :: struct {
|
Site :: struct {
|
||||||
arena: mem.Dynamic_Arena,
|
arena: mem.Dynamic_Arena,
|
||||||
pages: [dynamic]Page,
|
pages: [dynamic]Page,
|
||||||
|
modules: [dynamic]string,
|
||||||
|
vfs: VFS,
|
||||||
title: string,
|
title: string,
|
||||||
description: string,
|
description: string,
|
||||||
author: string,
|
author: string,
|
||||||
@@ -62,6 +64,7 @@ Config_File :: struct {
|
|||||||
layouts_dir: string,
|
layouts_dir: string,
|
||||||
markdown_extensions: json.Value,
|
markdown_extensions: json.Value,
|
||||||
params: json.Value,
|
params: json.Value,
|
||||||
|
modules: json.Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration loaded from command line arguments. Gets folded in to Site
|
// Configuration loaded from command line arguments. Gets folded in to Site
|
||||||
@@ -161,6 +164,14 @@ site_apply_config :: proc(site: ^Site, config: Config_File, config_dir: string)
|
|||||||
if ext_obj, ok := config.markdown_extensions.(json.Object); ok {
|
if ext_obj, ok := config.markdown_extensions.(json.Object); ok {
|
||||||
apply_extension_config(&site.markdown_extensions, ext_obj)
|
apply_extension_config(&site.markdown_extensions, ext_obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if modules_arr, ok := config.modules.(json.Array); ok {
|
||||||
|
for &item in modules_arr {
|
||||||
|
if s, ok2 := item.(json.String); ok2 {
|
||||||
|
append(&site.modules, fmt.tprintf("%s/%s", config_dir, s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
site_apply_path_defaults :: proc(site: ^Site, config_dir: string) {
|
site_apply_path_defaults :: proc(site: ^Site, config_dir: string) {
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
// Virtual File System Entry
|
||||||
|
VFS_Entry :: struct {
|
||||||
|
fs_path: string,
|
||||||
|
data: []byte,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Virtual File System
|
||||||
|
VFS :: struct {
|
||||||
|
files: map[string]VFS_Entry,
|
||||||
|
}
|
||||||
|
|
||||||
|
build_vfs :: proc(site: ^Site) {
|
||||||
|
site.vfs.files = make(map[string]VFS_Entry, site_allocator(site))
|
||||||
|
|
||||||
|
mount_dir(&site.vfs, fmt.tprintf("%s/layouts", DEFAULTS_PATH), "layouts")
|
||||||
|
|
||||||
|
for i := len(site.modules) - 1; i >= 0; i -= 1 {
|
||||||
|
module := site.modules[i]
|
||||||
|
mount_subdir(&site.vfs, module, "layouts")
|
||||||
|
mount_subdir(&site.vfs, module, "assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_dir(&site.vfs, site.layouts_dir, "layouts")
|
||||||
|
mount_dir(&site.vfs, site.assets_dir, "assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_subdir :: proc(vfs: ^VFS, module_dir: string, target: string) {
|
||||||
|
source := fmt.tprintf("%s/%s", module_dir, target)
|
||||||
|
mount_dir(vfs, source, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_dir :: proc(vfs: ^VFS, source_dir: string, target: string) {
|
||||||
|
if !os.exists(source_dir) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mount_recursive(vfs, source_dir, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_recursive :: proc(vfs: ^VFS, current_dir: string, target_prefix: string) {
|
||||||
|
entries, err := os.read_all_directory_by_path(current_dir, context.allocator)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.file_info_slice_delete(entries, context.allocator)
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
#partial switch entry.type {
|
||||||
|
case .Regular:
|
||||||
|
virtual := fmt.tprintf("%s/%s", target_prefix, entry.name)
|
||||||
|
vfs.files[virtual] = VFS_Entry {
|
||||||
|
fs_path = entry.fullpath,
|
||||||
|
}
|
||||||
|
case .Directory:
|
||||||
|
sub_prefix := fmt.tprintf("%s/%s", target_prefix, entry.name)
|
||||||
|
mount_recursive(vfs, entry.fullpath, sub_prefix)
|
||||||
|
case:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vfs_get :: proc(vfs: ^VFS, virtual_path: string) -> ([]byte, bool) {
|
||||||
|
entry, ok := vfs.files[virtual_path]
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if entry.data != nil {
|
||||||
|
return entry.data, true
|
||||||
|
}
|
||||||
|
data, err := os.read_entire_file_from_path(entry.fs_path, context.allocator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return data, true
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user