use std::fs;
use std::path::Path;
use askama::Template;
use crate::Document;
use crate::Site;
pub fn generate_html_index_pages(site: Site, documents: Vec<Document>, directory: &Path) {
for group_by in [
GroupBy::Authors,
GroupBy::Module,
GroupBy::Category,
GroupBy::ModificationDate,
] {
let document_tables = group_and_sort_documents(documents.clone(), group_by.clone());
let index_template = IndexTemplate {
site: site.clone(),
tables: document_tables,
group_by: group_by.clone(),
static_dir: site.static_dir.clone(),
};
let html = index_template.render().unwrap();
let key = match group_by.clone() {
GroupBy::Authors => "authors",
GroupBy::ModificationDate => "date",
GroupBy::Module => "module",
GroupBy::Category => "category",
};
fs::write(directory.join(format!("index_by_{key}.html")), &html).unwrap();
}
}
pub fn generate_html_error_pages(site: Site, directory: &Path) {
for (error_template, path) in [
(
ErrorTemplate {
site: site.clone(),
error_code: "404".to_string(),
error_message: "Diese Datei existiert nicht mehr :/".to_string(),
},
Path::new("404_documents.html"),
),
(
ErrorTemplate {
site: site.clone(),
error_code: "404".to_string(),
error_message: "Diese Seite existiert nicht".to_string(),
},
Path::new("404.html"),
),
] {
let html = error_template.render().unwrap();
fs::write(directory.join(path), html).unwrap();
}
}
#[derive(Clone, PartialEq)]
enum GroupBy {
Authors,
Module,
Category,
ModificationDate,
}
#[derive(Clone, PartialEq)]
enum SortBy {
Title,
Authors,
Module,
Category,
ModificationDate,
}
#[derive(Template)]
#[template(path = "table.html.j2")]
struct IndexTemplate {
site: Site,
tables: Vec<DocumentTable>,
group_by: GroupBy,
static_dir: String,
}
#[derive(Clone, Debug, PartialEq)]
struct DocumentTable {
title: String,
documents: Vec<Document>,
}
#[derive(Template, Clone)]
#[template(path = "error.html.j2")]
struct ErrorTemplate {
site: Site,
error_code: String,
error_message: String,
}
fn group_and_sort_documents(documents: Vec<Document>, group_by: GroupBy) -> Vec<DocumentTable> {
let mut tables = vec![];
if group_by == GroupBy::ModificationDate {
if documents.is_empty() {
return tables;
}
tables.push(DocumentTable {
title: "".to_string(),
documents,
});
for table in &mut tables {
table
.documents
.sort_by_key(|document| std::cmp::Reverse(document.modification_time));
}
return tables;
}
for document in documents {
let key = match group_by.clone() {
GroupBy::Module => format!("Modul: {}", document.module.name()),
GroupBy::Authors => format!("Autor: {}", document.authors.join(", ")),
GroupBy::Category => format!("Kategorie: {}", document.module.category()),
GroupBy::ModificationDate => unreachable!(),
};
match tables
.clone()
.into_iter()
.position(|table| table.title == key)
{
Some(index) => tables[index].documents.push(document),
None => tables.push(DocumentTable {
title: key,
documents: vec![document],
}),
}
}
tables.sort_by_key(|table| table.title.clone());
for table in &mut tables {
table.documents.sort_by(|a, b| {
for sort_by in [
SortBy::Category,
SortBy::Module,
SortBy::Title,
SortBy::Authors,
SortBy::ModificationDate,
] {
let order: std::cmp::Ordering = match sort_by {
SortBy::Title => a.title.cmp(&b.title),
SortBy::Authors => a.authors.cmp(&b.authors).reverse(),
SortBy::Module => a.module.name().cmp(&b.module.name()),
SortBy::Category => a.module.category().cmp(&b.module.category()),
SortBy::ModificationDate => {
a.modification_time.cmp(&b.modification_time).reverse()
}
};
if order == std::cmp::Ordering::Equal {
continue;
}
return order;
}
std::cmp::Ordering::Equal
})
}
tables
}
#[cfg(test)]
mod test {
use std::fs;
use chrono::TimeZone;
use chrono::Utc;
use parameterized::parameterized;
use url::Url;
use super::*;
use crate::config::Provider;
use crate::config::Source;
use crate::inventory::Module;
use crate::inventory::Semester1;
use crate::inventory::Semester2;
#[parameterized(group_by = {
GroupBy::Authors,
GroupBy::ModificationDate,
GroupBy::Module,
GroupBy::Category,
})]
fn test_generate_html_index_pages(group_by: GroupBy) {
let site = Site {
name: "Typst4EI".to_string(),
description: "Hello World!".to_string(),
static_dir: "static".to_string(),
files_dir: "files".to_string(),
};
let (documents, _, _, _, _) = test_documents();
let directory = assert_fs::TempDir::new().unwrap();
generate_html_index_pages(site, documents, directory.path());
let key = match group_by {
GroupBy::Authors => "authors",
GroupBy::ModificationDate => "date",
GroupBy::Module => "module",
GroupBy::Category => "category",
};
let actual_filename = format!("{}/index_by_{key}.html", directory.path().display());
let expected_filename = format!("testdata/generate_html_pages/index_by_{key}.html");
let actual_html = fs::read_to_string(&actual_filename).unwrap();
let expected_html = fs::read_to_string(&expected_filename).unwrap();
pretty_assertions::assert_eq!(expected_html, actual_html);
}
#[parameterized(file = {
"404.html",
"404_documents.html",
})]
fn test_generate_html_error_pages(file: &str) {
let site = Site {
name: "Typst4EI".to_string(),
description: "Hello World!".to_string(),
static_dir: "static".to_string(),
files_dir: "files".to_string(),
};
let directory = assert_fs::TempDir::new().unwrap();
generate_html_error_pages(site, directory.path());
let actual_filename = format!("{}/{}", directory.path().display(), file);
let expected_filename = format!("testdata/generate_html_pages/{}", file);
let actual_html = fs::read_to_string(&actual_filename).unwrap();
let expected_html = fs::read_to_string(&expected_filename).unwrap();
pretty_assertions::assert_eq!(expected_html, actual_html);
}
#[ignore]
#[test]
fn test_generate_html_pages_overwrite_expected_html_files() {
let site = Site {
name: "Typst4EI".to_string(),
description: "Hello World!".to_string(),
static_dir: "static".to_string(),
files_dir: "files".to_string(),
};
let (documents, _, _, _, _) = test_documents();
let directory = Path::new("testdata/generate_html_pages");
generate_html_index_pages(site.clone(), documents, directory);
generate_html_error_pages(site, directory);
}
#[parameterized(group_by = {
GroupBy::Authors,
GroupBy::ModificationDate,
GroupBy::Category,
GroupBy::Module,
})]
fn test_group_and_sort_documents(group_by: GroupBy) {
let (
documents,
documents_grouped_by_authors,
documents_grouped_by_date,
documents_grouped_by_category,
documents_grouped_by_module,
) = test_documents();
let actual_documents = group_and_sort_documents(documents, group_by.clone());
let expected_documents = match group_by {
GroupBy::Authors => documents_grouped_by_authors,
GroupBy::ModificationDate => documents_grouped_by_date,
GroupBy::Category => documents_grouped_by_category,
GroupBy::Module => documents_grouped_by_module,
};
pretty_assertions::assert_eq!(expected_documents, actual_documents);
}
fn test_documents() -> (
Vec<Document>,
Vec<DocumentTable>,
Vec<DocumentTable>,
Vec<DocumentTable>,
Vec<DocumentTable>,
) {
let alex_source = Source {
provider: Provider::Gitea,
owner: "alex".to_string(),
repo: "TUM-Formelsammlungen".to_string(),
custom_display_url: Some(Url::parse("https://gitea.mintcalc.com/").unwrap()),
custom_fetch_url: Some(Url::parse("https://gitea.localhost/").unwrap()),
hide_urls: false,
};
let max_source = Source {
provider: Provider::Codeberg,
owner: "max".to_string(),
repo: "kleine-kochbuecher-der-elektrotechnik".to_string(),
custom_display_url: None,
custom_fetch_url: None,
hide_urls: false,
};
let moritz_source = Source {
provider: Provider::Gitea,
owner: "moritz".to_string(),
repo: "tum-fs".to_string(),
custom_display_url: None,
custom_fetch_url: None,
hide_urls: true,
};
let alex_sem1_analysis_1 = Document {
filename: "sem1-analysis.pdf".to_string(),
title: "Analysis 1".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester1(Semester1::Analysis1),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
};
let max_sem1_analysis_1 = Document {
filename: "sem1-analysis-1.pdf".to_string(),
title: "Analysis 1".to_string(),
authors: vec!["Max".to_string()],
module: Module::Semester1(Semester1::Analysis1),
source: max_source.clone(),
modification_time: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
};
let alex_sem1_analysis_1_copy = Document {
filename: "sem1-analysis-1-copy.pdf".to_string(),
title: "Analysis 1".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester1(Semester1::Analysis1),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_500_000_000, 0).unwrap(),
};
let alex_sem1_computertechnik = Document {
filename: "sem1-computertechnik.pdf".to_string(),
title: "Computertechnik".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester1(Semester1::ComputerTechnology),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_300_000_000, 0).unwrap(),
};
let max_sem1_digitaltechnik = Document {
filename: "sem1-digitaltechnik.pdf".to_string(),
title: "Digitaltechnik".to_string(),
authors: vec!["Max".to_string()],
module: Module::Semester1(Semester1::DigitalTechnology),
source: max_source.clone(),
modification_time: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
};
let moritz_sem1_digitaltechnik = Document {
filename: "sem1-digitaltechnik-2.pdf".to_string(),
title: "Digitaltechnik".to_string(),
authors: vec!["Moritz".to_string()],
module: Module::Semester1(Semester1::DigitalTechnology),
source: moritz_source.clone(),
modification_time: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
};
let alex_sem1_linare_algebra = Document {
filename: "sem1-lineare-algebra.pdf".to_string(),
title: "Lineare Algebra".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester1(Semester1::LinearAlgebra),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
};
let alex_sem1_schaltungstheorie = Document {
filename: "sem1-schaltungstheorie.pdf".to_string(),
title: "Schaltungstheorie".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester1(Semester1::CircuitTheory),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_400_000_000, 0).unwrap(),
};
let moritz_sem1_schaltungstheorie = Document {
filename: "sem1-schaltungstheorie-2.pdf".to_string(),
title: "Schaltungstheorie".to_string(),
authors: vec!["Moritz".to_string()],
module: Module::Semester1(Semester1::CircuitTheory),
source: moritz_source.clone(),
modification_time: Utc.timestamp_opt(1_300_000_000, 0).unwrap(),
};
let alex_sem1_linare_algebra_2 = Document {
filename: "sem1-lineare-algebra-2.pdf".to_string(),
title: "Lineare Algebra".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester1(Semester1::LinearAlgebra),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_900_000_000, 0).unwrap(),
};
let alex_sem2_algorithmen_und_datenstrukturen = Document {
filename: "sem2-algorithmen-und-datenstrukturen.pdf".to_string(),
title: "Algorithmen und Datenstrukturen".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester2(Semester2::AlgorithmsAndDataStructures),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
};
let max_sem2_algorithmen_und_datenstrukturen = Document {
filename: "sem2-algorithmen-und-datenstrukturen-2.pdf".to_string(),
title: "Algorithmen und Datenstrukturen".to_string(),
authors: vec!["Max".to_string()],
module: Module::Semester2(Semester2::AlgorithmsAndDataStructures),
source: max_source.clone(),
modification_time: Utc.timestamp_opt(2_700_000_000, 0).unwrap(),
};
let alex_sem2_analysis_2 = Document {
filename: "sem2-analysis-2.pdf".to_string(),
title: "Analysis 2".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester2(Semester2::Analysis2),
source: moritz_source,
modification_time: Utc.timestamp_opt(1_500_000_000, 0).unwrap(),
};
let max_sem2_analysis_2 = Document {
filename: "sem2-analysis-2.pdf".to_string(),
title: "Analysis 2".to_string(),
authors: vec!["Max".to_string()],
module: Module::Semester2(Semester2::Analysis2),
source: max_source,
modification_time: Utc.timestamp_opt(2_300_000_000, 0).unwrap(),
};
let alex_sem2_kalender = Document {
filename: "sem2-kalender.pdf".to_string(),
title: "Kalender".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Misc,
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(2_900_000_000, 0).unwrap(),
};
let alex_sem2_elektrizitaet_und_magnetismus = Document {
filename: "sem2-elektrizität-und-magnetismus.pdf".to_string(),
title: "Elektrizität und Magnetismus".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester2(Semester2::ElectrityAndMagnetism),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(1_500_000_000, 0).unwrap(),
};
let alex_sem2_elektrizitaet_und_magnetismus_flash_cards = Document {
filename: "sem2-elektrizität-und-magnetismus-flash-cards.pdf".to_string(),
title: "Elektrizität und Magnetismus Flash Cards".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester2(Semester2::ElectrityAndMagnetism),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(2_200_000_000, 0).unwrap(),
};
let alex_sem2_physik = Document {
filename: "sem2-physik.pdf".to_string(),
title: "Physik".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester2(Semester2::Physics),
source: alex_source.clone(),
modification_time: Utc.timestamp_opt(2_500_000_000, 0).unwrap(),
};
let alex_sem2_systemtheorie = Document {
filename: "sem2-systemtheorie.pdf".to_string(),
title: "Systemtheorie".to_string(),
authors: vec!["Alex".to_string()],
module: Module::Semester2(Semester2::SystemsTheory),
source: alex_source,
modification_time: Utc.timestamp_opt(2_800_000_000, 0).unwrap(),
};
let documents = vec![
alex_sem1_analysis_1.clone(),
max_sem1_analysis_1.clone(),
alex_sem1_analysis_1_copy.clone(),
alex_sem1_computertechnik.clone(),
max_sem1_digitaltechnik.clone(),
alex_sem2_analysis_2.clone(),
moritz_sem1_digitaltechnik.clone(),
alex_sem1_linare_algebra.clone(),
alex_sem1_schaltungstheorie.clone(),
moritz_sem1_schaltungstheorie.clone(),
alex_sem1_linare_algebra_2.clone(),
alex_sem2_algorithmen_und_datenstrukturen.clone(),
max_sem2_algorithmen_und_datenstrukturen.clone(),
max_sem2_analysis_2.clone(),
alex_sem2_kalender.clone(),
alex_sem2_elektrizitaet_und_magnetismus.clone(),
alex_sem2_elektrizitaet_und_magnetismus_flash_cards.clone(),
alex_sem2_physik.clone(),
alex_sem2_systemtheorie.clone(),
];
let documents_grouped_by_authors = vec![
DocumentTable {
title: "Autor: Alex".to_string(),
documents: vec![
alex_sem1_analysis_1.clone(),
alex_sem1_analysis_1_copy.clone(),
alex_sem1_computertechnik.clone(),
alex_sem1_linare_algebra_2.clone(),
alex_sem1_linare_algebra.clone(),
alex_sem1_schaltungstheorie.clone(),
alex_sem2_algorithmen_und_datenstrukturen.clone(),
alex_sem2_analysis_2.clone(),
alex_sem2_elektrizitaet_und_magnetismus.clone(),
alex_sem2_elektrizitaet_und_magnetismus_flash_cards.clone(),
alex_sem2_physik.clone(),
alex_sem2_systemtheorie.clone(),
alex_sem2_kalender.clone(),
],
},
DocumentTable {
title: "Autor: Max".to_string(),
documents: vec![
max_sem1_analysis_1.clone(),
max_sem1_digitaltechnik.clone(),
max_sem2_algorithmen_und_datenstrukturen.clone(),
max_sem2_analysis_2.clone(),
],
},
DocumentTable {
title: "Autor: Moritz".to_string(),
documents: vec![
moritz_sem1_digitaltechnik.clone(),
moritz_sem1_schaltungstheorie.clone(),
],
},
];
let documents_grouped_by_date = vec![DocumentTable {
title: "".to_string(),
documents: vec![
alex_sem2_kalender.clone(),
alex_sem2_systemtheorie.clone(),
max_sem2_algorithmen_und_datenstrukturen.clone(),
alex_sem2_physik.clone(),
max_sem2_analysis_2.clone(),
alex_sem2_elektrizitaet_und_magnetismus_flash_cards.clone(),
alex_sem1_linare_algebra_2.clone(),
alex_sem1_analysis_1.clone(),
max_sem1_analysis_1.clone(),
max_sem1_digitaltechnik.clone(),
moritz_sem1_digitaltechnik.clone(),
alex_sem1_linare_algebra.clone(),
alex_sem2_algorithmen_und_datenstrukturen.clone(),
alex_sem1_analysis_1_copy.clone(),
alex_sem2_analysis_2.clone(),
alex_sem2_elektrizitaet_und_magnetismus.clone(),
alex_sem1_schaltungstheorie.clone(),
alex_sem1_computertechnik.clone(),
moritz_sem1_schaltungstheorie.clone(),
],
}];
let documents_grouped_by_tag = vec![
DocumentTable {
title: "Kategorie: Semester 1".to_string(),
documents: vec![
max_sem1_analysis_1.clone(),
alex_sem1_analysis_1.clone(),
alex_sem1_analysis_1_copy.clone(),
alex_sem1_computertechnik.clone(),
moritz_sem1_digitaltechnik.clone(),
max_sem1_digitaltechnik.clone(),
alex_sem1_linare_algebra_2.clone(),
alex_sem1_linare_algebra.clone(),
moritz_sem1_schaltungstheorie.clone(),
alex_sem1_schaltungstheorie.clone(),
],
},
DocumentTable {
title: "Kategorie: Semester 2".to_string(),
documents: vec![
max_sem2_algorithmen_und_datenstrukturen.clone(),
alex_sem2_algorithmen_und_datenstrukturen.clone(),
max_sem2_analysis_2.clone(),
alex_sem2_analysis_2.clone(),
alex_sem2_elektrizitaet_und_magnetismus.clone(),
alex_sem2_elektrizitaet_und_magnetismus_flash_cards.clone(),
alex_sem2_physik.clone(),
alex_sem2_systemtheorie.clone(),
],
},
DocumentTable {
title: "Kategorie: Verschiedenes".to_string(),
documents: vec![alex_sem2_kalender.clone()],
},
];
let documents_grouped_by_module = vec![
DocumentTable {
title: "Modul: Algorithmen und Datenstrukturen".to_string(),
documents: vec![
max_sem2_algorithmen_und_datenstrukturen.clone(),
alex_sem2_algorithmen_und_datenstrukturen.clone(),
],
},
DocumentTable {
title: "Modul: Analysis 1".to_string(),
documents: vec![
max_sem1_analysis_1.clone(),
alex_sem1_analysis_1.clone(),
alex_sem1_analysis_1_copy.clone(),
],
},
DocumentTable {
title: "Modul: Analysis 2".to_string(),
documents: vec![max_sem2_analysis_2.clone(), alex_sem2_analysis_2.clone()],
},
DocumentTable {
title: "Modul: Computertechnik".to_string(),
documents: vec![alex_sem1_computertechnik.clone()],
},
DocumentTable {
title: "Modul: Digitaltechnik".to_string(),
documents: vec![
moritz_sem1_digitaltechnik.clone(),
max_sem1_digitaltechnik.clone(),
],
},
DocumentTable {
title: "Modul: Elektrizität und Magnetismus".to_string(),
documents: vec![
alex_sem2_elektrizitaet_und_magnetismus.clone(),
alex_sem2_elektrizitaet_und_magnetismus_flash_cards.clone(),
],
},
DocumentTable {
title: "Modul: Lineare Algebra".to_string(),
documents: vec![
alex_sem1_linare_algebra_2.clone(),
alex_sem1_linare_algebra.clone(),
],
},
DocumentTable {
title: "Modul: Physik".to_string(),
documents: vec![alex_sem2_physik.clone()],
},
DocumentTable {
title: "Modul: Schaltungstheorie".to_string(),
documents: vec![
moritz_sem1_schaltungstheorie.clone(),
alex_sem1_schaltungstheorie.clone(),
],
},
DocumentTable {
title: "Modul: Systemtheorie".to_string(),
documents: vec![alex_sem2_systemtheorie.clone()],
},
DocumentTable {
title: "Modul: Verschiedenes".to_string(),
documents: vec![alex_sem2_kalender.clone()],
},
];
return (
documents,
documents_grouped_by_authors,
documents_grouped_by_date,
documents_grouped_by_tag,
documents_grouped_by_module,
);
}
}