391 lines
15 KiB
Rust
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,
|
|
}
|
|
}
|
|
|
|
}
|