Add implementation to walk directories and read data in parallel
commit
9cdcbfe7c6
@ -0,0 +1 @@
|
||||
/target
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "viki"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-walkdir = "0.2.0"
|
||||
clap = { version = "4.3.8", features = ["derive"] }
|
||||
futures = "0.3.28"
|
||||
globset = { version = "0.4.10", features = ["serde", "serde1"] }
|
||||
miette = { version = "5.9.0", features = ["serde", "fancy"] }
|
||||
serde = { version = "1.0.164", features = ["derive"] }
|
||||
tera = "1.19.0"
|
||||
tokio = { version = "1.29.0", features = ["rt-multi-thread", "net", "macros", "sync", "fs", "io-std", "io-util", "time", "process"] }
|
||||
toml = "0.7.5"
|
||||
tracing = { version = "0.1.37", features = ["async-await", "release_max_level_debug"] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["serde", "env-filter"] }
|
@ -0,0 +1,22 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[clap(infer_subcommands = true)]
|
||||
pub struct Args {
|
||||
#[command(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
pub enum Command {
|
||||
/// Builds the project
|
||||
Build(BuildArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub struct BuildArgs {
|
||||
#[clap(default_value = ".")]
|
||||
pub directory: PathBuf,
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use async_walkdir::WalkDir;
|
||||
use futures::StreamExt;
|
||||
use globset::{Glob, GlobSetBuilder};
|
||||
use miette::{Context, IntoDiagnostic, Result};
|
||||
use tokio::fs;
|
||||
|
||||
use super::IndexData;
|
||||
|
||||
/// loads directory data
|
||||
pub struct DirLoader {
|
||||
base_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FolderData {
|
||||
pub path: PathBuf,
|
||||
pub index: IndexData,
|
||||
pub pages: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl DirLoader {
|
||||
pub fn new(base_path: PathBuf) -> Self {
|
||||
Self { base_path }
|
||||
}
|
||||
|
||||
/// Asynchronously reads all the entries at the given content location
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub async fn read_content(&self) -> Result<Vec<FolderData>> {
|
||||
let mut entries = WalkDir::new(&self.base_path);
|
||||
let mut paths = Vec::new();
|
||||
paths.push(self.base_path.to_owned());
|
||||
|
||||
while let Some(res) = entries.next().await {
|
||||
match res {
|
||||
Ok(entry) => {
|
||||
let entry_path = entry.path();
|
||||
if entry_path.is_dir() {
|
||||
paths.push(entry_path)
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e).into_diagnostic(),
|
||||
}
|
||||
}
|
||||
|
||||
let results = futures::future::join_all(paths.into_iter().map(Self::read_dir)).await;
|
||||
let mut folder_data = Vec::new();
|
||||
|
||||
for res in results {
|
||||
match res {
|
||||
Ok(Some(data)) => folder_data.push(data),
|
||||
Err(e) => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(folder_data)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
async fn read_dir(path: PathBuf) -> Result<Option<FolderData>> {
|
||||
let index_path = path.join("_index.md");
|
||||
|
||||
if !index_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
let index_data = read_index_data(&index_path).await?;
|
||||
let pages = find_pages(&path, &index_data).await?;
|
||||
|
||||
Ok(Some(FolderData {
|
||||
path,
|
||||
index: index_data,
|
||||
pages,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
async fn read_index_data(path: &Path) -> Result<IndexData> {
|
||||
let index_str = fs::read_to_string(path)
|
||||
.await
|
||||
.into_diagnostic()
|
||||
.context("reading index file")?;
|
||||
toml::from_str(&index_str).into_diagnostic()
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
async fn find_pages(dir: &Path, index_data: &IndexData) -> Result<Vec<PathBuf>> {
|
||||
let include_set = build_glob_set(&index_data.include_files)
|
||||
.build()
|
||||
.into_diagnostic()?;
|
||||
let excluded_set = build_glob_set(&index_data.excluded_files)
|
||||
.build()
|
||||
.into_diagnostic()?;
|
||||
|
||||
let mut read_dir = fs::read_dir(dir).await.into_diagnostic()?;
|
||||
let mut pages = Vec::new();
|
||||
|
||||
while let Some(entry) = read_dir.next_entry().await.into_diagnostic()? {
|
||||
let entry_path = entry.path();
|
||||
|
||||
if entry_path.is_file()
|
||||
&& include_set.is_match(&entry_path)
|
||||
&& !excluded_set.is_match(&entry_path)
|
||||
{
|
||||
pages.push(entry_path);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(pages)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace")]
|
||||
fn build_glob_set(globs: &Vec<Glob>) -> GlobSetBuilder {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
globs.iter().fold(&mut builder, |b, g| b.add(g.clone()));
|
||||
|
||||
builder
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use globset::Glob;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct IndexData {
|
||||
/// the default template that is used for rendering
|
||||
pub default_template: Option<String>,
|
||||
|
||||
/// files that are included for rendering
|
||||
pub include_files: Vec<Glob>,
|
||||
|
||||
/// files that are explicitly excluded from rendering
|
||||
pub excluded_files: Vec<Glob>,
|
||||
|
||||
/// File paths with templates used to rendering them
|
||||
pub templates: HashMap<Glob, String>,
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
mod dir_loader;
|
||||
mod index;
|
||||
mod page;
|
||||
|
||||
pub use dir_loader::*;
|
||||
pub use index::*;
|
||||
pub use page::*;
|
@ -0,0 +1,12 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct PageMetadata {
|
||||
/// template used to render this page
|
||||
pub template: Option<String>,
|
||||
|
||||
/// remaining data of this page
|
||||
/// passed to the templates when rendering
|
||||
#[serde(flatten)]
|
||||
pub data: toml::Value,
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
pub mod args;
|
||||
pub mod data;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
Loading…
Reference in New Issue