use std::path::Path; use actix_web::{get, web, Responder}; use cached::proc_macro::cached; use ramhorns::Content; use regex::Regex; use serde::{Deserialize, Serialize}; use crate::{ config::Config, misc::{ markdown::{File, TypeFileMetadata}, utils::{make_kw, read_file, Html}, }, template::{InfosPage, NavBar}, }; #[derive(Debug, Deserialize)] pub struct PathRequest { q: Option, } #[get("/cours")] async fn page(info: web::Query, config: web::Data) -> impl Responder { Html(build_page(&info, config.get_ref().to_owned())) } #[derive(Content, Debug)] struct CoursTemplate { navbar: NavBar, filetree: String, content: Option, } #[derive(Clone, Debug, Serialize)] struct FileNode { name: String, is_dir: bool, children: Vec, } #[cached] fn compile_patterns(exclusion_list: Vec) -> Vec { exclusion_list .iter() .map(|pattern| Regex::new(pattern).unwrap()) .collect() } fn get_filetree(dir_path: &str, exclusion_patterns: &Vec) -> FileNode { let children = std::fs::read_dir(dir_path) .unwrap() .filter_map(Result::ok) .filter_map(|entry| { let entry_path = entry.path(); let entry_name = entry_path.file_name()?.to_string_lossy().to_string(); // Exclude element with the exclusion_list if exclusion_patterns.iter().any(|re| re.is_match(&entry_name)) { return None; } if entry_path.is_file() { Some(FileNode { name: entry_name, is_dir: false, children: vec![], }) } else { // Exclude empty directories let children_of_children = get_filetree(entry_path.to_str().unwrap(), exclusion_patterns); if children_of_children.is_dir && children_of_children.children.is_empty() { None } else { Some(children_of_children) } } }) .collect(); FileNode { name: Path::new(dir_path) .file_name() .unwrap() .to_string_lossy() .to_string(), is_dir: true, children, } } /// Get a page content fn get_content( cours_dir: &str, path: &web::Query, exclusion_list: &[String], ) -> Option { let filename = match &path.q { Some(q) => q, None => "index.md", }; // We should support regex? if exclusion_list .iter() .any(|excluded_term| filename.contains(excluded_term.as_str())) { return None; } read_file( &format!("{cours_dir}/{filename}"), &TypeFileMetadata::Generic, ) } fn build_page(info: &web::Query, config: Config) -> String { let cours_dir = "data/cours"; let exclusion_list = config.fc.exclude_courses.unwrap(); let exclusion_patterns = compile_patterns(exclusion_list.clone()); let filetree = get_filetree(cours_dir, &exclusion_patterns); config.tmpl.render( "cours.html", CoursTemplate { navbar: NavBar { cours: true, ..NavBar::default() }, filetree: serde_json::to_string(&filetree).unwrap(), content: get_content(cours_dir, info, &exclusion_list), }, InfosPage { title: Some("Cours".into()), desc: Some("Cours à l'univ".into()), kw: Some(make_kw(&[ "cours", "études", "université", "licence", "master", "notes", "digital garden", ])), }, ) }