rustyphoenixlecture 1.8.0

This project aims to provide a simple a powerfull lecture compilation to generate html web sites
/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/

use std::fs;
use std::collections::HashMap;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use toml::{self, de::Error};

use crate::phighlighter::PLocation;

use crate::pcontent::{
	PContentType, PReferenceUrl, PVecContent
};

use crate::plectureparser::PLectureData;

///Configuration of the lecture citation
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
struct LectureCitationConfig{
	///Name of the lecture
	name: String,
	///Url where to find the documentation of the project or the generated lecture (i.e. pages for gitlab)
	documentation_url: String,
	///Label defined in the lecture
	label: HashMap<String, String>,
}

impl LectureCitationConfig {
	///Get the reference url of the LectureCitationConfig
	/// # Parameters
	/// - `label_name` : name of the label to be used
	/// Returns
	/// Corresponding PReferenceUrl or None if the label is not found
	fn get_reference_url(&self, label_name: &String) -> Option<PReferenceUrl> {
		match self.label.get(label_name) {
			Some(text) => Some(PReferenceUrl::from_url(&String::from(format!("{}/redirection.html?label={}", self.documentation_url, label_name)), text)),
			None => None,
		}
	}
}

///Load the lecture citation configuration file
/// # Parameters
/// - `filename` : name of the configuration file (in toml)
/// # Returns
/// Lecture citation configuration or error on fail
fn load_lecture_ctation_config(filename: &PathBuf) -> Result<LectureCitationConfig, Error> {
	let config: LectureCitationConfig = match fs::read_to_string(filename) {
		Ok(str) => match toml::from_str(&str){
			Ok(value) => value,
			Err(error) => panic!("load_lecture_ctation_config : cannot parse file {:?}\n\tParsing error {}", filename, error)
		},
		Err(err) => panic!("load_lecture_ctation_config : cannot read file {:?}\n\tError: {}", filename, err)
	};
	Ok(config)
}

///Manage all references and labels of the lecture
#[derive(Debug, Clone, Default)]
pub struct PReferenceManager{
	///Map of the labels of corresponding PContent id
	p_map_label: HashMap<String, usize>,
	///Map of the used labels (by reference) in the lecture
	p_map_reference: HashMap<String, PLocation>,
	///Map of the references to other lectures
	p_map_lecture_reference: HashMap<String, HashMap<String, PLocation>>,
	///Map of the PContent which need a label because they have a reference
	p_map_label_id: HashMap<usize, String>,
	///Map of the solved reference url
	p_map_reference_url: HashMap<String, PReferenceUrl>,
	///Map of the lecture dependencies (key: LectureName, value: {key: labelName, value: text of link})
	p_map_lecture_dependency: HashMap<String, LectureCitationConfig>,
}

impl PReferenceManager{
	///Load a lecture dependency
	/// # Parameters
	/// - `lecture_name` : name of the lecture to be loaded
	/// - `dependency_config` : configuration
	/// # Errors
	/// This method will panic if the configuration cannot be loaded properly
	pub fn load_lecture_dependency(&mut self, lecture_name: &String, dependency_config: &PathBuf){
		let config: LectureCitationConfig = match load_lecture_ctation_config(dependency_config) {
			Ok(value) => value,
			Err(err) => panic!("load_lecture_dependency : cannot load lecture '{}' configuration\n\tIn file {:?}\n\tError {}", lecture_name, dependency_config, err)
		};
		self.p_map_lecture_dependency.insert(lecture_name.clone(), config.clone());
		
	}
	///Add reference from parsing data
	/// # Parameters
	/// - `data` : parsing data from a lecture file
	pub fn add_reference(&mut self, data: &PLectureData){
		//Let's update the declared label in the map fo label of the lecture
		for (name, id) in data.get_map_label().iter(){
			// println!("add_reference: add label '{}'", name);
			self.p_map_label.insert(name.clone(), *id);
		}
		//Let's udpate the set of reference we will need
		for (reference_name, reference_location) in data.get_map_reference().iter(){
			// println!("add_reference: add reference '{}'", reference_name);
			self.p_map_reference.insert(reference_name.clone(), reference_location.clone());
		}
		for (lecture_name, map_reference) in data.get_map_lecture_reference().iter(){
			for (reference_name, reference_location) in map_reference.iter() {
				match self.p_map_lecture_reference.get_mut(lecture_name) {
					Some(lecture_ref) => {
						lecture_ref.insert(reference_name.clone(), reference_location.clone());
					},
					None => {
						let mut lecture_ref: HashMap<String, PLocation> = Default::default();
						lecture_ref.insert(reference_name.clone(), reference_location.clone());
						self.p_map_lecture_reference.insert(lecture_name.clone(), lecture_ref.clone());
					}
				}
			}
		}
	}
	///Solve all reference of the lecture
	/// # Parameters
	/// - `vec_content` : vector of content of the whole lecture
	/// # Errors
	/// This function will panic if some references are not solved, and the un solved references will be listed
	pub fn solve_reference(&mut self, vec_content: &mut PVecContent){
		println!("PReferenceManager::solve_reference : solving references and labels");
		self.build_all_reference(vec_content);
		self.build_reference_url(vec_content);
		self.update_reference(vec_content);
	}
	///Save the redirection html page
	/// # Parameters
	/// - `output_dir` : output directory of the redirection.html page
	pub fn save_redirection_html(&self, output_dir: &PathBuf){
		let mut body = String::from(r#"<!DOCTYPE html>
<html lang="fr">
	<head>
		<meta charset="utf-8" />
		<title>Page redirection</title>
		<link rel="stylesheet" href="book/dark.css" />
		<script type="text/javascript">
			function redirectionWithLabelReference(){
				var parameters = location.search.substring(1).split("?");
				var tmp = parameters[0].split("=");
				referenceName = unescape(tmp[1]);
				var dictReference = {
"#);
		let mut comma = String::from("");
		for (label, url) in self.p_map_reference_url.iter(){
			body += &String::from(format!("{}\t\t\t\t\t\"{}\": \"{}\"", comma, label, url.get_url()));
			comma = String::from(",\n");
		}
		body += &String::from(r#"
				};
				if(referenceName in dictReference){
					document.location.href=dictReference[referenceName];
				}else{
					document.location.href="index.html";
				}
			}
		</script>
	</head>
	<body onLoad="setTimeout('redirectionWithLabelReference()', 100)">
		<div>In 1 second you will get the page you asked for... normally</div>
	</body>
</html>
"#);
		let redirection_page: PathBuf = output_dir.join(&PathBuf::from("redirection.html"));
		match fs::write(&redirection_page, body) {
			Ok(_) => {},
			Err(err) => panic!("save_redirection_html : cannot write file {:?}\n\tError {}", redirection_page, err)
		};
	}
	///Save the lecture configuration to allow citation from other lectures
	/// # Parameters
	/// - `output_dir` : output directory of the lecture
	/// - `lecture_name` : name of the current lecture (how to cite it)
	/// - `documentation_url` : url where the current lecture is accessible
	pub fn save_lecture_citation_config(&self, output_dir: &PathBuf, lecture_name: &String, documentation_url: &String){
		let dependency_config: PathBuf = output_dir.join(PathBuf::from("lecture_citation.toml"));
		//Let's create the configuration of the current lecture
		let mut lecture_config: LectureCitationConfig = LectureCitationConfig {
			name: lecture_name.clone(),
			documentation_url: documentation_url.clone(),
			label: Default::default()
		};
		for (label_name, reference) in self.p_map_reference_url.iter(){
			lecture_config.label.insert(label_name.clone(), reference.get_simple_text().clone());
		}
		fs::write(dependency_config, toml::to_string(&lecture_config).unwrap()).unwrap();
	}
	///Build all needed references
	/// # Parameters
	/// - `vec_content` : vector of content of the whole lecture
	fn build_all_reference(&mut self, vec_content: &PVecContent){
		for content in vec_content.get_vec_child().iter(){
			match content {
				PContentType::Paragraph(paragraph) => self.build_all_reference(paragraph.get_data().get_content()),
				//Let's update a reference in the list
				PContentType::ListItem(listitem) => self.build_all_reference(listitem.get_data().get_content()),
				//Let's update a reference in the environment
				PContentType::Environment(environement) => self.build_all_reference(environement.get_data().get_content()),
				//Let's update a reference in the table
				PContentType::Table(table) => {
					for row in table.get_data().get_vec_row().iter() {
						for cell in row.get_vec_cell().iter() {
							self.build_all_reference(cell.get_content());
						}
					}
				}
				other => {
					match other.get_label() {
						Some(label) => {
							if !label.is_empty() {
								// println!("build_all_reference: create label, {} => {}", other.get_id(), label.clone());
								self.p_map_label_id.insert(other.get_id(), label.clone());
							}
						},
						None => {}
					}
				}
			}
		}
	}
	///Build all references url of the lecture
	/// # Parameters
	/// - `vec_content` : vector of content of the whole lecture
	fn build_reference_url(&mut self, vec_content: &PVecContent){
		let mut current_file = String::from("index.html");
		self.build_reference_url_sub(vec_content, &mut current_file);
	}
	///Build all references url of the lecture
	/// # Parameters
	/// - `vec_content` : vector of content of the whole lecture
	/// - `current_file` : current file where the content is
	fn build_reference_url_sub(&mut self, vec_content: &PVecContent, current_file: &mut String){
		for content in vec_content.get_vec_child().iter(){
			match content {
				//Let's update the current file
				PContentType::Title(title) => {
					if title.get_data().is_enumerated() {
						*current_file = title.get_data().get_output_filename().clone()
					}
				},
				_ => {},	//Nothing to do for the other
			};
			match content {
				PContentType::Paragraph(paragraph) => self.build_reference_url_sub(paragraph.get_data().get_content(), current_file),
				//Let's update a reference in the list
				PContentType::ListItem(listitem) => self.build_reference_url_sub(listitem.get_data().get_content(), current_file),
				//Let's update a reference in the list
				PContentType::Environment(environment) => self.build_reference_url_sub(environment.get_data().get_content(), current_file),
				//Let's update a reference in the table
				PContentType::Table(table) => {
					for row in table.get_data().get_vec_row().iter() {
						for cell in row.get_vec_cell().iter() {
							self.build_reference_url_sub(cell.get_content(), current_file);
						}
					}
				}
				other => {
					//If the content needed a label, we update the p_map_reference_url to update the PReferenceUrl later
					match self.p_map_label_id.get(&other.get_id()) {
						Some(label) => {
							// println!("PReferenceManager::build_reference_url : build reference '{}' on file '{}'", label.clone(), current_file);
							self.p_map_reference_url.insert(label.clone(), other.get_reference_url(current_file));
						},
						None => {}
					};
				}
			}
		}
	}
	///Update the references in the vec_content with solved ones
	/// # Parameters
	/// - `vec_content` : PVecContent to udpate reference of
	fn update_reference(&self, vec_content: &mut PVecContent){
		for content in vec_content.get_vec_child_mut().iter_mut(){
			match content {
				//Let's update a reference in the paragraph
				PContentType::Paragraph(paragraph) => self.update_reference(paragraph.get_data_mut().get_content_mut()),
				//Let's update a reference in the list
				PContentType::ListItem(listitem) => self.update_reference(listitem.get_data_mut().get_content_mut()),
				//Let's update a reference in the list
				PContentType::Environment(environment) => self.update_reference(environment.get_data_mut().get_content_mut()),
				//Let's update a reference in the table
				PContentType::Table(table) => {
					for row in table.get_data_mut().get_vec_row_mut().iter_mut() {
						for cell in row.get_vec_cell_mut().iter_mut() {
							self.update_reference(cell.get_content_mut());
						}
					}
				}
				PContentType::Reference(reference) => {
					//Let's get the corresponding PReferenceUrl
					let lecture_name = reference.get_data().get_other_lecture_name();
					if lecture_name.is_empty(){
						match self.p_map_reference_url.get(reference.get_data().get_reference()) {
							Some(refurl) => reference.get_data_mut().set_url(refurl),
							None => {
								panic!("PReferenceManager::update_reference : cannot get the reference '{}'", reference.get_data().get_reference());
							}	//Not possible here
						};
					}else{
						//Here we need to get the lecture
						match self.p_map_lecture_dependency.get(lecture_name) {
							Some(lecture_map) => {
								let label_name = reference.get_data().get_reference();
								match lecture_map.get_reference_url(&label_name){
									Some(value_ref) => reference.get_data_mut().set_url(&value_ref),
									None => panic!("PReferenceManager::update_reference : no reference '{}' in lecture '{}'", label_name, lecture_name)
								}
							},
							None => panic!("PReferenceManager::update_reference : cannot get the lecture '{}' to have reference from", lecture_name)
						}
					}
				},
				//TODO : mabe we will have to update references in environments, wip, etc
				_ => {}	//We care only about references here
			};
		}
	}
}


#[cfg(test)]
mod tests{
	use super::*;
	
	///Test the load lecture citation config
	#[test]
	fn test_load_lecture_citation_config(){
		let citation_config = PathBuf::from("tests/LectureWithDependency/dependencies/performance_with_stencil.toml");
		let config: LectureCitationConfig = load_lecture_ctation_config(&citation_config).unwrap();
		assert_eq!(config.name, String::from("performance_with_stencil"));
		assert_eq!(config.documentation_url, String::from("https://cta-lapp.pages.in2p3.fr/PHOENIX_LIBS2/static-site-generator/RustyPhoenixLecture"));
		
		assert_eq!(config.label.len(), 2);
		assert_eq!(config.get_reference_url(&String::from("figTweety")), Some(PReferenceUrl::from_url(&String::from("https://cta-lapp.pages.in2p3.fr/PHOENIX_LIBS2/static-site-generator/RustyPhoenixLecture/redirection.html?label=figTweety"), &String::from("Figure 26"))));
	}
}