module generator
import (
std
std.alloc
std.collections
std.fs
std.io
std.strings
vendor.mdhtml
)
let ensure_parent(engine: *mut Engine, path: str): bool {
let error = std.fs.create_directory(std.fs.dirname(path), true, true)
if error.is_error() { return fail_path(engine, "could not create output directory: ", path) }
return true
}
let output_page_name(
engine: *mut Engine,
relative: str,
): (std.fs.Path, bool) {
let extension = std.fs.extension(relative)
if extension.eq(".html") { return join_relative(engine, "", relative) }
if !extension.eq(".md") {
fail_path(engine, "unsupported page extension: ", relative)
return std.fs.Path.init(engine.allocator), false
}
let without_extension = relative[:@len(relative) - @len(extension)]
let mut result = std.fs.Path.init(engine.allocator)
if !result.push(without_extension) || !result.bytes.append_slice(".html".([]u8)) {
fail(engine, "out of memory")
return result, false
}
return result, true
}
let render_page(
engine: *mut Engine,
source_path: str,
relative: str,
): bool {
let source, read_error = std.fs.read_file(engine.allocator, source_path)
if read_error.is_error() { return fail_path(engine, "could not read page: ", relative) }
let document, parsed = parse_document(engine, source.as_str(), relative)
if !parsed { return false }
let mut body = std.strings.StringBuilder.init(engine.allocator)
if std.fs.extension(relative).eq(".md") {
let mut markdown = std.strings.StringBuilder.init(engine.allocator)
if !append_text(markdown.&mut, document.body) {
return fail(engine, "out of memory")
}
let reader = markdown.&mut.(*mut dyn std.io.Reader)
let writer = body.&mut.(*mut dyn std.io.Writer)
if !vendor.mdhtml.md_to_html(reader, writer) {
return fail_path(engine, "could not render Markdown page: ", relative)
}
} else if !append_text(body.&mut, document.body) {
return fail(engine, "out of memory")
}
let mut context = Context.{ fields=document.fields }
let template, has_template = find_field(context.fields.as_slice(), "template")
let mut rendered = body
if has_template {
match template {
.String(template_name) => {
let mut stack = std.collections.DynamicArray(str).init(engine.allocator)
let result, ok = render_template(
engine,
template_name,
context.&,
body.as_str(),
stack.&mut,
)
if !ok { return false }
rendered = result
}
_ => {
return fail_path(engine, "page template must be a string in ", relative)
}
}
}
let output_relative, output_name_ok = output_page_name(engine, relative)
if !output_name_ok { return false }
let output_path, output_path_ok = make_path(
engine,
engine.output,
output_relative.as_str(),
)
if !output_path_ok || !ensure_parent(engine, output_path.as_str()) { return false }
let write_error = std.fs.write_file(output_path.as_str(), rendered.as_str().([]u8))
if write_error.is_error() { return fail_path(engine, "could not write page: ", output_path.as_str()) }
return true
}
let classify_unknown(path: str): (std.fs.DirectoryEntryKind, bool) {
let directory, directory_error = std.fs.is_directory(path)
if directory_error.is_none() && directory { return .Directory, true }
let file, file_error = std.fs.is_file(path)
if file_error.is_none() && file { return .File, true }
return .Unknown, false
}
let build_pages(engine: *mut Engine, relative_directory: str): bool {
let directory, directory_ok = make_path(engine, "pages", relative_directory)
if !directory_ok { return false }
let mut iterator, open_error = std.fs.DirectoryIterator.open(directory.as_str())
if open_error.is_error() { return fail_path(engine, "could not open pages directory: ", directory.as_str()) }
defer iterator.close()
let mut name = std.strings.StringBuilder.init(engine.allocator)
for true {
let mut kind, found, next_error = iterator.next(name.&mut)
if next_error.is_error() { return fail_path(engine, "could not enumerate pages directory: ", directory.as_str()) }
if !found { break }
let relative, relative_ok = join_relative(engine, relative_directory, name.as_str())
if !relative_ok { return false }
let source, source_ok = make_path(engine, "pages", relative.as_str())
if !source_ok { return false }
if kind == .Unknown {
let classified, known = classify_unknown(source.as_str())
if !known { return fail_path(engine, "unknown page entry kind: ", source.as_str()) }
kind = classified
}
if kind == .Directory {
if !build_pages(engine, relative.as_str()) { return false }
} else if kind == .File {
if !render_page(engine, source.as_str(), relative.as_str()) { return false }
} else {
return fail_path(engine, "unsupported page entry: ", source.as_str())
}
}
return true
}
let copy_static(engine: *mut Engine, relative_directory: str): bool {
let directory, directory_ok = make_path(engine, "static", relative_directory)
if !directory_ok { return false }
let mut iterator, open_error = std.fs.DirectoryIterator.open(directory.as_str())
if open_error.is_error() { return fail_path(engine, "could not open static directory: ", directory.as_str()) }
defer iterator.close()
let mut name = std.strings.StringBuilder.init(engine.allocator)
for true {
let mut kind, found, next_error = iterator.next(name.&mut)
if next_error.is_error() { return fail_path(engine, "could not enumerate static directory: ", directory.as_str()) }
if !found { break }
let relative, relative_ok = join_relative(engine, relative_directory, name.as_str())
if !relative_ok { return false }
let source, source_ok = make_path(engine, "static", relative.as_str())
if !source_ok { return false }
if kind == .Unknown {
let classified, known = classify_unknown(source.as_str())
if !known { return fail_path(engine, "unknown static entry kind: ", source.as_str()) }
kind = classified
}
if kind == .Directory {
if !copy_static(engine, relative.as_str()) { return false }
} else if kind == .File {
let destination, destination_ok = make_path(
engine,
engine.output,
relative.as_str(),
)
if !destination_ok || !ensure_parent(engine, destination.as_str()) { return false }
let contents, read_error = std.fs.read_file(engine.allocator, source.as_str())
if read_error.is_error() { return fail_path(engine, "could not read static file: ", source.as_str()) }
let write_error = std.fs.write_file(destination.as_str(), contents.as_str().([]u8))
if write_error.is_error() { return fail_path(engine, "could not write static file: ", destination.as_str()) }
} else {
return fail_path(engine, "unsupported static entry: ", source.as_str())
}
}
return true
}
pub let build_site(
root: str,
output: str,
error: *mut std.strings.StringBuilder,
): bool {
error.clear()
let mut arena = std.alloc.Arena.init()
defer arena.deinit()
let allocator = arena.as_allocator()
let mut engine = Engine.{
allocator=allocator,
root=root,
output=output,
error=error,
}
if @len(output) == 0 {
return fail(engine.&mut, "output directory cannot be empty")
}
let pages, pages_ok = make_path(engine.&mut, "pages")
if !pages_ok { return false }
let templates, templates_ok = make_path(engine.&mut, "templates")
if !templates_ok { return false }
let static, static_ok = make_path(engine.&mut, "static")
if !static_ok { return false }
let output_path, output_path_ok = make_path(engine.&mut, output)
if !output_path_ok { return false }
let pages_is_directory, pages_error = std.fs.is_directory(pages.as_str())
if pages_error.is_error() || !pages_is_directory {
return fail_path(engine.&mut, "missing pages directory: ", pages.as_str())
}
let templates_is_directory, templates_error = std.fs.is_directory(templates.as_str())
if templates_error.is_error() || !templates_is_directory {
return fail_path(engine.&mut, "missing templates directory: ", templates.as_str())
}
let static_is_directory, static_error = std.fs.is_directory(static.as_str())
if static_error.is_error() || !static_is_directory {
return fail_path(engine.&mut, "missing static directory: ", static.as_str())
}
let output_error = std.fs.create_directory(output_path.as_str(), true, true)
if output_error.is_error() {
return fail_path(
engine.&mut,
"could not create output directory: ",
output_path.as_str(),
)
}
if !build_pages(engine.&mut, "") { return false }
return copy_static(engine.&mut, "")
}