rustyphoenixlecture 1.7.2

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::{collections::HashSet, fs, path::PathBuf};
use serde::Deserialize;
use toml::{self, de::Error, value::{Date, Time}};

use crate::{pcontent::{PContentItem, PContentReference, PItemType}, phighlighter::PFileParser};
use crate::{pcontent::{PContentTimeBlock, PContentTimeTable, PContentTitle, PContentType, PLabeler, PVecContent}, phighlighter::PLocation};

use crate::plectureparser::{PLectureParser, PLectureData};

///Block of a time table
#[derive(Debug, Clone, Deserialize)]
pub struct PTimeTableBlock{
	///Title of the week if there is one
	pub week_title: Option<String>,
	///Date the of block or week
	pub date: Option<Date>,
	///Title of the block if there is one
	pub title: Option<String>,
	///Invitation to the block or week
	pub invitation: Option<String>,
	///Style of the block
	pub style: Option<String>,
	///Speaker of the block
	pub speakers: Option<Vec<String>>,
	///Description of the block
	pub description: Option<String>,
	///Location of the block
	pub location: Option<String>,
	///Begin time of the block
	pub begin: Option<Time>,
	///Duration of the block
	pub duration: Option<Time>,
}

///Destription of the time table
#[derive(Debug, Clone, Deserialize)]
pub struct PTimeTableDescription{
	///Invitation of the time table
	pub invitation: String,
	///Main url of the web site where the time table is defined
	pub main_url: String,
	///Main location of the time table (will be the default for the block where it is not defined)
	pub location: String,
	///Label of the time table to cite it in the lecture
	pub label: String,
}

///Full time table to be loaded
#[derive(Debug, Clone, Deserialize)]
pub struct PTimeTable{
	///Description of the time table
	pub timetable: PTimeTableDescription,
	///Block of the whole time table
	pub block: Vec<PTimeTableBlock>,
}

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

///Get the option value of the default without the stupid behaviour of unwrap_or
/// # Parameters
/// - `option` : `Option<String>` to read
/// - `default_value` : default value to be used if the option is None
/// # Returns
/// Corresponding value
fn phoenix_default_option(option: &Option<String>, default_value: &String) -> String{
	match option {
		Some(value) => value.clone(),
		None => default_value.clone()
	}
}

///Update the date time of block
/// # Parameters
/// - `block` : block to be updated
/// - `option_date` : current date
/// - `option_begin` : begin time of the block
/// - `duration` : duration of the block
fn update_date_of_block(block: &mut PContentTimeBlock, option_date: &Option<Date>, option_begin: &mut Option<Time>, duration: &Time){
	let date = match option_date {
		Some(value) => value,
		None => panic!("update_date_of_block : missing date of block '{}'", block.get_title())
	};
	let begin_time = match option_begin {
		Some(value) => value,
		None => panic!("update_date_of_block : missing begin of block '{}'", block.get_title())
	};
	let mut end_time: Time = begin_time.clone();
	end_time.hour += duration.hour;
	end_time.minute += duration.minute;
	if end_time.minute >= 60 {
		let nb_hour: u8 = end_time.minute / 60;
		end_time.minute = end_time.minute % 60;
		end_time.hour += nb_hour;
	}
	
	block.set_time(&date.to_string(), &begin_time.to_string(), &end_time.to_string(), &duration.to_string());
	
	*option_begin = Some(end_time);
}

///Update the time of the last session in the week
/// # Parameters
/// - `last_end_time` : time of the last session to be updated
/// - `current_time` : current itme to be used
fn update_last_session_time(last_end_time: &mut Option<Time>, current_time: &Option<Time>){
	match last_end_time {
		Some(last_time) => {
			match current_time{
				Some(current) => {
					if last_time.hour < current.hour {
						*last_time = current.clone();
					}else if last_time.hour == current.hour {
						if last_time.minute < current.minute {
							*last_time = current.clone();
						}
					}
				},
				None => {}
			}
		},
		None => {}
	}
}

///Create a PContentTimeTable from a timetable configuration
/// # Parameters
/// - `content` : content of the lecture to be updated
/// - `lecture_data` : data of the lecture
/// - `lecture_parser` : parser to be used
/// - `filename` : name of the timetable configuration file (in toml)
/// # Errors
/// This function will panic if the configuration file cannot be loaded properly
pub fn create_content_timetable(content: &mut PVecContent, lecture_data: &mut PLectureData, lecture_parser: &PLectureParser, filename: &PathBuf){
	let timetable_config: PTimeTable = match load_timetable(filename){
		Ok(value) => value,
		Err(err) => panic!("load_timetable : cannot read file {:?}\n\tError: {}", filename, err)
	};
	let config_location = PLocation::new(filename, 0, 0);
	let mut content_time_table = PContentTimeTable::new(&timetable_config.timetable.main_url, &timetable_config.timetable.invitation);
	let mut week_invitation = String::from("");
	let mut current_date: Option<Date> = None;
	let mut current_time: Option<Time> = None;
	let mut last_end_time: Option<Time> = None;
	//Set of content by respect to the session invitation
	let mut set_session_invitation: HashSet<String> = Default::default();
	//Vector to remember the order of the session
	let mut vec_session_content: Vec<PVecContent> = vec![];
	//Let's iterate on all block of the time table
	for block_config in timetable_config.block.iter(){
		match &block_config.date {
			Some(date) => current_date = Some(date.clone()),
			None => {},
		}
		match &block_config.begin {
			Some(value) => {
				if last_end_time == None {
					last_end_time = current_time.clone();
				}
				current_time = Some(value.clone())
			},
			None => {},
		}
		match &block_config.week_title {
			Some(week_title) => {	//Here, we create a new week
				let invitation: String = match &block_config.invitation {
					Some(value) => value.clone(),
					None => String::from("")
				};
				if !week_invitation.is_empty() {
					match last_end_time {
						Some(last_time) => content_time_table.set_last_time(&week_invitation, &last_time.to_string()),
						None => {}
					}
					last_end_time = None;
				}
				if !invitation.is_empty(){
					week_invitation = invitation.clone();	//We update the week invitation for the next block to add
				}
				content_time_table.add_week(week_title, &invitation);
			},
			None => {	//Here, this is a simple block
				let mut block = PContentTimeBlock::new(&phoenix_default_option(&block_config.title, &String::from("")),
					&phoenix_default_option(&block_config.invitation, &String::from("")));
				block.set_location(&phoenix_default_option(&block_config.location, &timetable_config.timetable.location));
				block.set_style(&phoenix_default_option(&block_config.style, &String::from("timetableBlockStyle")));
				
				update_date_of_block(&mut block, &current_date, &mut current_time, &block_config.duration.unwrap());
				update_last_session_time(&mut last_end_time, &current_time);
				create_session_content(&mut vec_session_content, &mut set_session_invitation, lecture_data, lecture_parser, &block, block_config, &config_location);
				content_time_table.add_block(&week_invitation, &block);
			}
		}
	}
	if !week_invitation.is_empty() {	//Set the last time for the last week
		match last_end_time {
			Some(last_time) => content_time_table.set_last_time(&week_invitation, &last_time.to_string()),
			None => {}
		}
	}
	//Let's finalized the block of the time table
	content_time_table.udpate_block_row();
	content_time_table.save_invitation(lecture_parser.get_output_path());
	//Let's add the PContentTimeTable in the content
	content.add_child(&PContentType::TimeTable(PLabeler::new(lecture_data.get_current_id(), &content_time_table)));
	if !timetable_config.timetable.label.is_empty() {
		lecture_data.add_label(content, &timetable_config.timetable.label);
	}
	//Then we iterate again on the configuration block to create the section of the individual block
	// and we will use the lecture_parser and the lecture_data for the description
	
	for session in vec_session_content.iter(){
		content.append_content(session);
	}
}
///Create the session content
/// # Parameters
/// - `vec_session_content` : set of content by respect to the session invitation
/// - `set_session_invitation` : vector to remember the order of the session
/// - `lecture_data` : data of the lecture
/// - `lecture_parser` : parser to be used
/// - `block` : block of the session
/// - `block_config` : configuration of the block
/// - `config_location` : location of the config file
fn create_session_content(vec_session_content: &mut Vec<PVecContent>, set_session_invitation: &mut HashSet<String>,
	lecture_data: &mut PLectureData, lecture_parser: &PLectureParser, block: &PContentTimeBlock, block_config: &PTimeTableBlock,
	config_location: &PLocation)
{
	if block.get_invitation().is_empty() {
		return;		//No invitation, so no session
	}
	if set_session_invitation.contains(block.get_invitation()){
		return;	//We already have the session
	}
	let mut content = PVecContent::new();
	let mut session = PContentTitle::new(2, true, config_location);
	session.get_title_mut().add_child(&PContentType::from_text(lecture_data.get_current_id(), block.get_title()));
	content.add_child(&PContentType::Title(PLabeler::new(lecture_data.get_current_id(), &session)));
	lecture_data.add_label(&mut content, &String::from(format!("sec_{}", block.get_invitation())));
	
	let mut session_header = String::from(format!("<a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>", block.get_invitation()));
	session_header += &format!("<b>Date</b> : {}<br />", block.get_date());
	session_header += &format!("<b>Location</b> : {}<br />", block.get_location());
	session_header += &format!("<b>Start at</b> : {}<br />", block.get_begin_time());
	session_header += &format!("<b>Stop at</b> : {}<br />", block.get_end_time());
	content.add_child(&PContentType::from_text(lecture_data.get_current_id(), &session_header));
	match &block_config.speakers {
		Some(vec_speaker) => {
			if vec_speaker.len() != 0 {
				content.add_child(&PContentType::from_text(lecture_data.get_current_id(), &String::from("<h3>Speakers</h3>")));
				let mut list_speaker = PContentItem::new(0, &PItemType::List);
				//TODO : add a reference to the speaker to be able to replace their label by their name
				for speaker in vec_speaker.iter() {
					let mut item_speaker = PContentItem::new(0, &PItemType::Item);
					item_speaker.get_content_mut().add_child(&PContentType::Reference(PLabeler::new(lecture_data.get_current_id(), &PContentReference::new(speaker))));
					list_speaker.get_content_mut().add_child(&PContentType::ListItem(PLabeler::new(lecture_data.get_current_id(), &item_speaker)));
				}
				content.add_child(&PContentType::ListItem(PLabeler::new(lecture_data.get_current_id(), &list_speaker)));
			}
		},
		None => {}
	}
	match &block_config.description {
		Some(description) => {
			let trim_description = String::from(description.trim());
			if !trim_description.is_empty() {
				content.add_child(&PContentType::from_text(lecture_data.get_current_id(), &String::from("<h3>Description</h3>")));
				let parser: PFileParser = PFileParser::from_content(&trim_description);
				let mut data = PLectureData::new(lecture_data.get_current_id(), &parser);
				lecture_parser.parse(&mut content, &mut data);
				lecture_data.set_current_id(data.get_current_id());
				lecture_data.append_map_data(&data);
			}
		},
		None => {}
	}
	
	//Save the content
	vec_session_content.push(content);
	set_session_invitation.insert(block.get_invitation().clone());
}