Files
webTemplate/src/render/index.rs

391 lines
15 KiB
Rust

pub mod indexer {
use std::{collections::BTreeMap, ffi::OsString, fs, path::Path};
use hashlink::LinkedHashMap;
use regex::Regex;
use yaml_rust2::{Yaml, YamlLoader};
#[derive(Debug)]
pub struct IndexItem {
// path to source file. If none, it is a directory without an index page
pub src: Option<String>,
// if it is an asset file
pub is_asset: bool,
// url path with starting slash
pub path: String,
// the title of the page
pub title: String,
// url friendly name
pub friendly: String,
// if the page sould be in the site navigator
pub public: bool,
// name of the remplate to use
pub template: String,
// list of sub pages
pub sub_pages: Vec<Box<IndexItem>>,
// extention of the output file
pub target_extention: String,
}
impl IndexItem {
fn new() -> IndexItem {
IndexItem {
src: None,
is_asset: false,
path: String::from("/"),
title: String::from(""),
friendly: String::from(""),
public: false,
sub_pages: Vec::new(),
template: String::from("default"),
target_extention: String::from(""),
}
}
pub fn index(path: &Path) -> Option<IndexItem> {
IndexItem::scan_entry(path, String::from("/"))
}
fn scan_entry(path: &Path, last_url: String) -> Option<IndexItem> {
let mut item = IndexItem::new();
if let Some(file_name) = path.file_name() {
if let Some(file_name) = file_name.to_str() {
let file_name = String::from(file_name);
if path.is_dir() {
if file_name != "templates" {
item.friendly = file_name.clone();
item.path = last_url.clone();
let url = if last_url == String::from("/") {
format!("/{file_name}")
} else {
format!("{}/{file_name}", last_url.clone())
};
let index_path = path.join("index.md");
match IndexItem::scan_entry(&index_path, last_url.clone()) {
Some(mut item) => {
let sub_pages = IndexItem::scan_dir(&path, last_url);
item.sub_pages = sub_pages.0;
item.public = sub_pages.1;
return Some(item);
},
None => {
let sub_pages = IndexItem::scan_dir(&path, url);
item.sub_pages = sub_pages.0;
item.public = sub_pages.1;
return Some(item);
}
}
}
}
else if path.is_file() {
item.src = match path.to_str() {
Some(path) => Some(String::from(path)),
None => None,
};
if item.src == None || file_name.len() < 4 {
return None;
}
let extention: String = file_name.clone().drain(file_name.len()-3..).collect();
if extention == ".md" {
let friendly = file_name.clone().drain(..file_name.len()-3).collect();
item.friendly = if friendly == "index" {
String::from("")
} else {
friendly
};
item.path = last_url;
let md = fs::read_to_string(path);
match md {
Ok(md) => {
let params = split_params(md).yaml;
IndexItem::parse_params(&mut item, params);
return Some(item)
},
Err(_) => todo!()
}
}
else {
item.is_asset = true;
item.friendly = file_name.clone();
item.path = last_url;
return Some(item);
}
}
return None
}
}
None
}
fn scan_dir(path: &Path, last_url: String) -> (Vec<Box<IndexItem>>, bool) {
let mut items: Vec<Box<IndexItem>> = Vec::new();
let mut has_public: bool = false;
let list = fs::read_dir(path);
match list {
Ok(list) => {
for entry in list {
match entry {
Ok(entry) => {
if entry.file_name() != OsString::from("index.md") {
let item = IndexItem::scan_entry(&entry.path(), last_url.clone());
if let Some(i) = item {
if i.public {
has_public = true;
}
items.push(Box::from(i));
}
}
},
Err(_) => todo!()
}
}
},
Err(_) => todo!()
};
items.sort_by(|i, c| i.friendly.cmp(&c.friendly));
(items, has_public)
}
fn parse_params(item: &mut IndexItem, params: Yaml) {
if let Yaml::Hash(params) = params {
if let Some(value) = params.get(&Yaml::from(Yaml::String(String::from("title")))) {
if let Yaml::String(title) = value {
item.title = title.clone();
}
};
if let Some(value) = params.get(&Yaml::from(Yaml::String(String::from("public")))) {
if let Yaml::Boolean(public) = value {
item.public = public.clone();
}
};
if let Some(value) = params.get(&Yaml::from(Yaml::String(String::from("template")))) {
if let Yaml::String(template) = value {
item.template = template.clone();
}
};
if let Some(value) = params.get(&Yaml::from(Yaml::String(String::from("target_extention")))) {
if let Yaml::String(target_extention) = value {
item.target_extention = target_extention.clone();
}
};
}
}
pub fn get_url(&self) -> String {
if self.src != None {
if self.path.ends_with("/") {
format!("{}{}", self.path, self.friendly)
} else {
format!("{}/{}", self.path, self.friendly)
}
}
else {
String::from("")
}
}
pub fn to_minijinja(&self) -> minijinja::Value {
let mut list: BTreeMap<String, minijinja::Value> = BTreeMap::new();
list.insert(String::from("path"), minijinja::Value::from(self.path.clone()));
list.insert(String::from("title"), minijinja::Value::from(self.title.clone()));
list.insert(String::from("friendly"), minijinja::Value::from(self.friendly.clone()));
list.insert(String::from("public"), minijinja::Value::from(self.public.clone()));
list.insert(String::from("template"), minijinja::Value::from(self.template.clone()));
list.insert(String::from("url"), minijinja::Value::from(self.get_url()));
list.insert(String::from("sub_pages"), minijinja::Value::from({
let mut list: Vec<minijinja::Value> = Vec::new();
for page in self.sub_pages.iter() {
list.push(page.to_minijinja());
};
list
}));
minijinja::Value::from(list)
}
}
#[derive(Debug, Clone)]
pub struct Template {
pub name: String,
pub extention: String,
pub path: Option<String>,
pub src: Option<String>,
pub sub_templates: Vec<Box<Template>>
}
impl Template {
fn new() -> Template {
Template {
name: String::new(),
extention: String::new(),
path: None,
src: None,
sub_templates: Vec::new()
}
}
pub fn search(&self, path: String) -> Option<Self> {
let mut parts = path.split("/");
match parts.next() {
None => None,
Some(first) => {
if self.name == first {
let sub_path = parts.collect::<Vec<&str>>().join("/");
if sub_path.len() == 0 {
return Some(self.clone())
}
return self.search_sub(path)
}
None
}
}
}
fn search_sub(&self, path: String) -> Option<Self> {
for templ in self.sub_templates.clone() {
if let Some(templ) = templ.search(path.clone()) {
return Some(templ)
}
}
None
}
pub fn index(path: &Path) -> Vec<Box<Self>> {
match Self::scan_template(path) {
Some(templates) => templates.sub_templates,
None => Vec::new(),
}
}
fn scan_template(path: &Path) -> Option<Self> {
let mut item = Template::new();
if let Some(file_name) = path.file_name() {
if let Some(file_name) = file_name.to_str() {
let file_name = String::from(file_name);
if path.is_dir() {
item.name = file_name;
item.sub_templates = Template::scan_dir(&path);
return Some(item);
}
else if path.is_file() {
item.path = match path.to_str() {
Some(path) => Some(String::from(path)),
None => None,
};
if item.path == None {
return None;
}
let path = item.path.clone().unwrap();
item.src = match fs::read_to_string(path) {
Ok(src) => Some(src),
Err(_) => None,
};
let re= Regex::new(r"\.").unwrap();
let mut matches = re.captures_iter(&file_name);
if let Some(dot) = matches.next() {
let dot = dot.get(0).unwrap();
item.extention = file_name.clone().drain(dot.end()..).collect();
item.name = file_name.clone().drain(..dot.start()).collect();
return Some(item);
}
else {
item.name = file_name;
return Some(item);
}
}
return None
}
}
None
}
fn scan_dir(path: &Path) -> Vec<Box<Template>> {
let mut items: Vec<Box<Template>> = Vec::new();
let list = fs::read_dir(path);
match list {
Ok(list) => {
for entry in list {
match entry {
Ok(entry) => {
let item = Template::scan_template(&entry.path());
if let Some(i) = item {
items.push(Box::from(i));
}
},
Err(_) => todo!()
}
}
},
Err(_) => todo!()
};
items.sort_by(|i, c| i.name.cmp(&c.name));
items
}
}
pub struct SplitMd {
pub yaml: Yaml,
pub md: String
}
fn join_yaml(yaml: Vec<Yaml>) -> Yaml {
let mut out: LinkedHashMap<Yaml, Yaml> = LinkedHashMap::new();
for yaml in yaml {
match yaml {
yaml_rust2::Yaml::Hash(map) => {
for yaml in map.iter() {
out.insert(yaml.0.clone(), yaml.1.clone());
}
},
_ => {
todo!();
},
}
}
Yaml::Hash(out)
}
pub fn split_params(md: String) -> SplitMd {
let re= Regex::new(r"---").unwrap();
let mut matches = re.captures_iter(&md);
if let Some(first_dashes) = matches.next() {
let first_dashes = first_dashes.get(0).unwrap();
if first_dashes.start() == 0 {
if let Some(second_dashes) = matches.next() {
let second_dashes = second_dashes.get(0).unwrap();
let yaml: String = md.clone().drain(first_dashes.end()..second_dashes.start()).collect();
let markdown: String = md.clone().drain(second_dashes.end()..).collect();
if yaml.len() > 0 {
match YamlLoader::load_from_str(&yaml) {
Ok(yaml) => {
return SplitMd {
yaml: join_yaml(yaml),
md: markdown,
};
},
Err(_) => {}
}
}
}
}
}
SplitMd {
yaml: Yaml::BadValue,
md: md,
}
}
}