Add rendering implementation
parent
9cdcbfe7c6
commit
b7b262718d
@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
test
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub folders: Folders,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Folders {
|
||||||
|
pub content: Option<PathBuf>,
|
||||||
|
pub templates: Option<PathBuf>,
|
||||||
|
pub output: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_config(dir: &Path) -> Result<Config> {
|
||||||
|
let cfg_string = fs::read_to_string(dir.join("viki.toml"))
|
||||||
|
.await
|
||||||
|
.into_diagnostic()?;
|
||||||
|
toml::from_str(&cfg_string).into_diagnostic()
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
mod dir_loader;
|
mod dir_loader;
|
||||||
mod index;
|
mod index;
|
||||||
mod page;
|
mod page;
|
||||||
|
mod page_loader;
|
||||||
|
|
||||||
pub use dir_loader::*;
|
pub use dir_loader::*;
|
||||||
pub use index::*;
|
pub use index::*;
|
||||||
pub use page::*;
|
pub use page::*;
|
||||||
|
pub use page_loader::*;
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use miette::{Context, IntoDiagnostic, Result};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use super::Page;
|
||||||
|
|
||||||
|
pub struct PageLoader;
|
||||||
|
|
||||||
|
/// loads a page and parses the data depending on the extension
|
||||||
|
#[tracing::instrument(level = "trace")]
|
||||||
|
pub async fn load_page(path: &Path) -> Result<Page> {
|
||||||
|
let string_content = load_string_content(path).await?;
|
||||||
|
|
||||||
|
if let Some(extension) = path.extension() {
|
||||||
|
let extension_lower = extension.to_string_lossy().to_lowercase();
|
||||||
|
match extension_lower.as_str() {
|
||||||
|
"toml" => Ok(Page::Data(
|
||||||
|
toml::from_str(&string_content).into_diagnostic()?,
|
||||||
|
)),
|
||||||
|
_ => Ok(Page::Content(string_content)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Page::Content(string_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace")]
|
||||||
|
async fn load_string_content(path: &Path) -> Result<String> {
|
||||||
|
fs::read_to_string(path)
|
||||||
|
.await
|
||||||
|
.into_diagnostic()
|
||||||
|
.context("reading page content")
|
||||||
|
}
|
@ -1,7 +1,53 @@
|
|||||||
pub mod args;
|
use args::BuildArgs;
|
||||||
|
use clap::Parser;
|
||||||
|
use config::{read_config, Config};
|
||||||
|
use data::DirLoader;
|
||||||
|
use miette::Result;
|
||||||
|
use rendering::ContentRenderer;
|
||||||
|
use tracing::metadata::LevelFilter;
|
||||||
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod config;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
|
mod rendering;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<()> {
|
||||||
println!("Hello, world!");
|
let args: Args = Args::parse();
|
||||||
|
init_tracing();
|
||||||
|
|
||||||
|
match args.command {
|
||||||
|
args::Command::Build(build_args) => {
|
||||||
|
let cfg = read_config(&build_args.directory).await?;
|
||||||
|
build(cfg, build_args).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn build(cfg: Config, args: BuildArgs) -> Result<()> {
|
||||||
|
let folders = cfg.folders;
|
||||||
|
let base_path = args.directory;
|
||||||
|
let content_dir = base_path.join(folders.content.unwrap_or("content".into()));
|
||||||
|
let template_dir = base_path.join(folders.templates.unwrap_or("templates".into()));
|
||||||
|
let out_dir = base_path.join(folders.output.unwrap_or("public".into()));
|
||||||
|
|
||||||
|
let dirs = DirLoader::new(content_dir).read_content().await?;
|
||||||
|
let template_glob = format!("{}/**/*", template_dir.to_string_lossy());
|
||||||
|
ContentRenderer::new(template_glob, out_dir)
|
||||||
|
.render_all(dirs)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_tracing() {
|
||||||
|
tracing_subscriber::fmt::SubscriberBuilder::default()
|
||||||
|
.with_max_level(LevelFilter::TRACE)
|
||||||
|
.with_writer(std::io::stderr)
|
||||||
|
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||||
|
.compact()
|
||||||
|
.init();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use futures::future;
|
||||||
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use tera::{Context, Tera};
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use crate::data::{load_page, FolderData};
|
||||||
|
|
||||||
|
// renders content using the given template folder
|
||||||
|
pub struct ContentRenderer {
|
||||||
|
template_glob: String,
|
||||||
|
out_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentRenderer {
|
||||||
|
pub fn new(template_glob: String, out_dir: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
template_glob,
|
||||||
|
out_dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
|
pub async fn render_all(&self, dirs: Vec<FolderData>) -> Result<()> {
|
||||||
|
if self.out_dir.exists() {
|
||||||
|
fs::remove_dir_all(&self.out_dir).await.into_diagnostic()?;
|
||||||
|
}
|
||||||
|
let tera = Tera::new(&self.template_glob).into_diagnostic()?;
|
||||||
|
future::try_join_all(dirs.into_iter().map(|data| self.render_folder(&tera, data))).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
|
async fn render_folder(&self, tera: &Tera, data: FolderData) -> Result<()> {
|
||||||
|
for page_path in data.pages {
|
||||||
|
let page = load_page(&page_path).await?;
|
||||||
|
let mut context = Context::new();
|
||||||
|
let mut template_name = data
|
||||||
|
.index
|
||||||
|
.default_template
|
||||||
|
.to_owned()
|
||||||
|
.unwrap_or("default".into());
|
||||||
|
|
||||||
|
match page {
|
||||||
|
crate::data::Page::Data(data) => {
|
||||||
|
if let Some(tmpl) = data.template {
|
||||||
|
template_name = tmpl;
|
||||||
|
}
|
||||||
|
context.insert("data", &data.data);
|
||||||
|
}
|
||||||
|
crate::data::Page::Content(content) => context.insert("content", &content),
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("context = {:?}", context);
|
||||||
|
|
||||||
|
let html = tera.render(&template_name, &context).into_diagnostic()?;
|
||||||
|
let rel_path = page_path
|
||||||
|
.strip_prefix(&data.content_root)
|
||||||
|
.into_diagnostic()?;
|
||||||
|
let mut out_path = self.out_dir.join(rel_path);
|
||||||
|
out_path.set_extension("html");
|
||||||
|
let parent = out_path.parent().unwrap();
|
||||||
|
|
||||||
|
if !parent.exists() {
|
||||||
|
fs::create_dir_all(parent).await.into_diagnostic()?;
|
||||||
|
}
|
||||||
|
fs::write(out_path, html).await.into_diagnostic()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue