rustyphoenixlecture 1.7.2

This project aims to provide a simple a powerfull lecture compilation to generate html web sites
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/

use std::{collections::{HashMap, HashSet}, fs, path::PathBuf};

use crate::pcontent::{
	PAbstractContent, PAbstractLectureBackend,
	PReferenceUrl,
	PLabelId
};

use crate::pinvitation::{PEvent, PInvitation};

///Block of a time table
#[derive(Debug, Clone, PartialEq)]
pub struct PContentTimeBlock{
	// ///Url of the main site where this generated timetable is hosted
	// p_main_url: String,
	///Title of the block
	p_title: String,
	///Name of the invitation file to be generated for this block session
	p_invitation: String,
	///Style of the section (CSS class name of the section)
	p_style: String,
	///Number of time rows on which the current block is
	p_nb_time_row: usize,
	///True if the block is an empty block
	p_is_empty_block: bool,
	///Date when the block begins
	p_date: String,
	///Time when the block begins
	p_begin_time: String,
	///Time when the block ends
	p_end_time: String,
	///Duration of the block
	p_duration: String,
	///Location of the event
	p_location: String,
}

impl PContentTimeBlock{
	///Constructor of the PContentTimeBlock
	/// # Parameters
	/// - `title` : title of the block
	/// - `invitation` : invitation of the block
	pub fn new(title: &String, invitation: &String) -> Self{
		PContentTimeBlock {
			p_title: title.clone(),
			p_invitation: invitation.clone(),
			p_style: String::from(""),
			p_nb_time_row: 0,
			p_is_empty_block: true,
			p_date: String::from(""),
			p_begin_time: String::from(""),
			p_end_time: String::from(""),
			p_duration: String::from(""),
			p_location: String::from(""),
		}
	}
	///Set the style of the block
	/// # Parameters
	/// - `style` : style of the block
	pub fn set_style(&mut self, style: &String){
		self.p_style = style.clone();
	}
	///Set the times of the block
	/// # Parameters
	/// - `date` : date when the block occurs
	/// - `begin_time` : time when the block begins
	/// - `end_time` : time when the block ends
	/// - `duration` : duration of the block
	pub fn set_time(&mut self, date: &String, begin_time: &String, end_time: &String, duration: &String){
		self.p_date = date.clone();
		self.p_begin_time = begin_time.clone();
		self.p_end_time = end_time.clone();
		self.p_duration = duration.clone();
	}
	///Set the location of the block
	/// # Parameters
	/// - `location` : location of the block
	pub fn set_location(&mut self, location: &String){
		self.p_location = location.clone();
	}
	///Get the title of the block
	/// # Returns
	/// Title of the block
	pub fn get_title(&self) -> &String{
		&self.p_title
	}
	///Get the invitation of the block
	/// # Returns
	/// Invitation of the block
	pub fn get_invitation(&self) -> &String{
		&self.p_invitation
	}
	///Get the date of the block
	/// # Returns
	/// Date of the block
	pub fn get_date(&self) -> &String{
		&self.p_date
	}
	///Get the begin time of the block
	/// # Returns
	/// Begin of the block
	pub fn get_begin_time(&self) -> &String{
		&self.p_begin_time
	}
	///Get the end time of the block
	/// # Returns
	/// End of the block
	pub fn get_end_time(&self) -> &String{
		&self.p_end_time
	}
	///Get the location of the block
	/// # Returns
	/// Location of the block
	pub fn get_location(&self) -> &String{
		&self.p_location
	}
}

///Manage a row of block starting at the same time
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PContentTimeRow{
	///Map of block ordered by day with the same starting time
	p_map_day: HashMap<String, PContentTimeBlock>,
}

impl PContentTimeRow {
	///Add a block in the row
	/// # Parameters
	/// - `block` : block to be added
	fn add_block(&mut self, block: &PContentTimeBlock){
		self.p_map_day.insert(block.p_date.clone(), block.clone());
	}
}

///Manage and describe the time table of a week
#[derive(Debug, Clone, PartialEq)]
pub struct PContentWeek{
	///Title of the block for the entire week
	p_week_title: String,
	///Name of the invitation file to be generated for this week
	p_invitation: String,
	///Map of all the blocks of the row ordered by starting time
	p_map_row: HashMap<String, PContentTimeRow>,
	///Map of all time with index
	p_map_time: HashMap<String, usize>,
	///Set of date used in the current week
	p_map_date: HashSet<String>,
	///Time when the last session ends
	p_last_session_end_time: String,
}

impl PContentWeek {
	///Constructor of a PContentWeek
	/// # Parameters
	/// - `title` : title of the week
	/// - `invitation` : invitation of the week
	fn new(title: &String, invitation: &String) -> Self{
		PContentWeek {
			p_week_title: title.clone(),
			p_invitation: invitation.clone(),
			p_map_row: Default::default(),
			p_map_time: Default::default(),
			p_map_date: Default::default(),
			p_last_session_end_time: String::from(""),
		}
	}
	///Add a block in the week
	/// # Parameters
	/// - `block` : block to be added
	fn add_block(&mut self, block: &PContentTimeBlock){
		//First, we update the map of time and map of date
		self.p_map_time.insert(block.p_begin_time.clone(), 0);
		self.p_map_date.insert(block.p_date.clone());
		match self.p_map_row.get_mut(&block.p_begin_time) {
			Some(row) => row.add_block(block),
			None => {
				let mut row: PContentTimeRow = Default::default();
				row.add_block(block);
				self.p_map_row.insert(block.p_begin_time.clone(), row);
			}
		}
		if !self.p_map_time.contains_key(&block.p_end_time) {
			self.p_map_time.insert(block.p_end_time.clone(), 0);
		}
	}
	///Update the number of rows of each block
	fn update_block_row(&mut self){
		//We have to update the time index
		let mut vec_time: Vec<String> = self.p_map_time.iter().map(|(key, _)| key.clone()).collect::<Vec<String>>();
		vec_time.sort();
		// println!("PContentTimeTable::update_block_row : vec_time = {:?}", vec_time);
		for (i, key) in vec_time.iter().enumerate() {
			*self.p_map_time.get_mut(key).unwrap() = i;
		}
		//Then, we use the time index to update the number of rows of the block
		for (start_time, row) in self.p_map_row.iter_mut() {
			let index_start_time: usize = *self.p_map_time.get(start_time).unwrap();
			for (_, block) in row.p_map_day.iter_mut() {
				let index_end_time: usize = *self.p_map_time.get(&block.p_end_time).unwrap();
				// println!("PContentTimeTable::update_block_row : start_time = '{}', index_start_time = {}, index_end_time = {}", start_time, index_start_time, index_end_time);
				block.p_nb_time_row = index_end_time - index_start_time;
			}
		}
	}
}

///Manage and describe the full time table of an event
#[derive(Debug, Clone, PartialEq)]
pub struct PContentTimeTable{
	///Url of the main site where this generated timetable is hosted
	p_main_url: String,
	///Name of the invitation file to be generated for this event
	p_invitation: String,
	///Vector of week names
	p_vec_week_name: Vec<String>,
	///Map of the weeks of the tile table
	p_map_week: HashMap<String, PContentWeek>,
	///Current date of the invitation creation
	p_current_date: String,
	///Counter of block to have unique id of invitation
	p_block_counter: usize,
	///Invitation of the whole time table
	p_main_invitation: PInvitation,
	///Map of the invitation for the weeks
	p_map_invitation_week: HashMap<String, PInvitation>,
	///Map of the invitation for the dates
	p_map_invitation_date: HashMap<String, PInvitation>,
	///Map of the invitation for the sessions with the same invitation
	p_map_invitation_session: HashMap<String, PInvitation>,
}

impl PContentTimeTable {
	///Constructor of the PContentTimeTable
	/// # Parameters
	/// - `main_url` : url of the main site where this generated timetable is hosted
	/// - `invitation` : name of the invitation file to be generated for this event
	pub fn new(main_url: &String, invitation: &String) -> Self{
		//TODO : get the current date in calendar format
		PContentTimeTable {
			p_main_url: main_url.clone(),
			p_invitation: invitation.clone(),
			p_vec_week_name: Default::default(),
			p_map_week: Default::default(),
			p_current_date: String::from("20260607T150300"),
			p_block_counter: 0,
			p_main_invitation: PInvitation::new(invitation),
			p_map_invitation_week: Default::default(),
			p_map_invitation_date: Default::default(),
			p_map_invitation_session: Default::default(),
		}
	}
	///Add a week in the time table
	/// # Parameters
	/// - `title` : title of the week
	/// - `invitation` : invitation of the week
	pub fn add_week(&mut self, title: &String, invitation: &String){
		let week = PContentWeek::new(title, invitation);
		self.p_map_week.insert(week.p_invitation.clone(), week.clone());
		self.p_vec_week_name.push(week.p_invitation.clone());
	}
	///Set the last time of the week
	/// # Parameters
	/// - `week_invitation` : invitation of the week
	/// - `last_session_end_time` : last time of the week
	pub fn set_last_time(&mut self, week_invitation: &String, last_session_end_time: &String){
		match self.p_map_week.get_mut(week_invitation) {
			Some(week) => week.p_last_session_end_time = last_session_end_time.clone(),
			None => panic!("PContentTimeTable::set_last_time : no week with invitation '{}' in the time table", week_invitation)
		}
	}
	///Add a block in the time table
	/// # Parameters
	/// - `week_invitation` : invitation of the week
	/// - `block` : block to be added
	pub fn add_block(&mut self, week_invitation: &String, block: &PContentTimeBlock){
		//Let's add the block in the week
		match self.p_map_week.get_mut(week_invitation) {
			Some(week) => week.add_block(block),
			None => panic!("PContentTimeTable::add_block : no week with invitation '{}' in the time table", week_invitation)
		}
		if block.p_invitation.is_empty() {	//If the block has no invitation, we stop here and skip invitation stuff
			return;
		}
		self.p_block_counter += 1;
		let event = PEvent::new(block.get_title(), &block.p_invitation, &block.p_date, &block.p_begin_time, &block.p_end_time, &block.p_location, self.p_block_counter, &self.p_current_date);
		self.p_main_invitation.add_event(&event);
		//Then, let's udpate the invitation of the week
		add_invitation_in_map(&mut self.p_map_invitation_week, week_invitation, &event);
		//Then, let's udpate the invitation of the date
		add_invitation_in_map(&mut self.p_map_invitation_date, &block.p_date, &event);
		//Then, let's udpate the invitation of the session
		add_invitation_in_map(&mut self.p_map_invitation_session, &block.p_invitation, &event);
	}
	///Update the block row of the week
	pub fn udpate_block_row(&mut self){
		for (_, week) in self.p_map_week.iter_mut(){
			week.update_block_row();
		}
	}
	///Save the invitation in the output_path
	/// # Parameters
	/// - `output_path` : output path to save the lecture
	/// # Errors
	/// This function will panic if the output invitation directory or one of the invitations cannot be created
	pub fn save_invitation(&self, output_path: &PathBuf){
		let output_invitation_path = output_path.join(PathBuf::from("invitation"));
		fs::create_dir_all(&output_invitation_path).expect(&format!("PContentTimeTable::save_invitation : cannot create output directory of invitation {:?}", output_invitation_path));
		self.p_main_invitation.write(&output_invitation_path, &self.p_main_url);
		for (_, invitation) in self.p_map_invitation_week.iter() {
			invitation.write(&output_invitation_path, &self.p_main_url);
		}
		for (_, invitation) in self.p_map_invitation_date.iter() {
			invitation.write(&output_invitation_path, &self.p_main_url);
		}
		for (_, invitation) in self.p_map_invitation_session.iter() {
			invitation.write(&output_invitation_path, &self.p_main_url);
		}
	}
}

///Add a PEvent in the right invitation map
/// # Parameters
/// - `map_invitation` : map of invitation to be updated
/// - `key_name` : name of the key to be used
/// - `event` : event to be added
fn add_invitation_in_map(map_invitation: &mut HashMap<String, PInvitation>, key_name: &String, event: &PEvent){
	match map_invitation.get_mut(key_name) {
		Some(week) => week.add_event(&event),
		None => {
			let mut week = PInvitation::new(key_name);
			week.add_event(&event);
			map_invitation.insert(key_name.clone(), week.clone());
		}
	}
}


impl PAbstractContent for PContentTimeTable{
	///Say if the PContent has an embeded label
	/// # Returns
	/// True if the PContent has an embeded label, false otherwise
	fn has_embeded_label(&self) -> bool{
		false
	}
	///Get the reference url of the current PContent
	/// # Parameters
	/// - `current_file` : current output file of the PContent
	/// - `id` : id of the current PContent
	/// # Returns
	/// Corresponding PReferenceUrl
	fn get_reference_url(&self, current_file: &String, id: usize) -> PReferenceUrl{
		PReferenceUrl::from_text(current_file, id, &String::from("PContentTimeTable"))
	}
	///Convert the current struct into html
	/// # Parameters
	/// - `backend` : backend which write a lecture in files
	/// - `id` : id and label of the current PContent
	fn to_html<TLectureBackend>(&self, backend: &mut TLectureBackend, id: &PLabelId)
		where TLectureBackend: PAbstractLectureBackend
	{
		backend.write(&String::from(format!("<table id=\"{}\" class=\"timetableStyle\">\n", id.get_id())));
		for week_name in self.p_vec_week_name.iter() {
			let week = self.p_map_week.get(week_name).unwrap();
			backend.write(&String::from("<tr><td class=\"timetableWeekRowStyle\">"));
			if !week.p_invitation.is_empty() {
				backend.write(&String::from(format!("<a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>", week.p_invitation)));
			}
			if !week.p_week_title.is_empty() {
				backend.write(&String::from(format!("<span class=\"timetableWeekTitle\">{}</span>", week.p_week_title)));
			}
			backend.write(&String::from("</td></tr>\n"));
			backend.write(&String::from("\t<tr class=\"timetableMainWeekRowStyle\"><td class=\"timetableMainWeekRowStyle\">\n"));
			backend.write(&String::from("\t\t<table class=\"timetableWeekStyle\">\n"));
			backend.write(&String::from("\t\t\t<tr class=\"timetableTimeRow\">\n"));
			backend.write(&String::from("\t\t\t\t<td class=\"timetableDayStyle\">Period/Day</td>\n"));
			let mut vec_date: Vec<String> = week.p_map_date.iter().map(|key| key.clone()).collect::<Vec<String>>();
			vec_date.sort();
			for date_name in vec_date.iter() {
				backend.write(&String::from(format!("\t\t\t\t<td class=\"timetableDayStyle\"><a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>{}</td>\n", date_name, date_name)));
			}
			backend.write(&String::from("\t\t\t</tr>\n"));
			//Now we have to save all the rows of the lecture
			let mut vec_time: Vec<String> = week.p_map_row.iter().map(|(key, _)| key.clone()).collect::<Vec<String>>();
			vec_time.sort();
			let mut map_already_used_time: HashMap<String, String> = Default::default();
			
			for row_time in vec_time.iter(){
				let row = week.p_map_row.get(row_time).unwrap();
				backend.write(&String::from("\t\t\t<tr class=\"timetableTimeRow\">\n"));
				backend.write(&String::from(format!("\t\t\t\t<td class=\"timetableTimeStyle\"><span class=\"timetableTimeTextStyle\">{}</span></td>\n", row_time)));
				//We have to iterate on the date in order
				if row.p_map_day.is_empty() {
					for _ in 0..vec_date.len() {
						backend.write(&String::from("\t\t\t\t<td class=\"timetableEmptyBlockStyle\"></td>\n"));
					}
				}else{
					for date_name in vec_date.iter() {
						match row.p_map_day.get(date_name) {
							Some(date) => {
								backend.write(&String::from(format!("\t\t\t\t<td rowspan=\"{}\" class=\"{}\">", date.p_nb_time_row, date.p_style)));
								//If there is an invitation
								if !date.p_invitation.is_empty(){
									backend.write(&String::from(format!("<a href=\"invitation/{}.ics\"><div class=\"rendezvousStyle\"></div></a>", date.p_invitation)));
									backend.write(&String::from(format!("<a href=\"redirection.html?label=sec_{}\"><b>{}</b></a>", date.p_invitation, date.p_title)));
								}else{
									backend.write(&String::from(format!("{}", date.p_title)));
								}
								backend.write(&String::from("</td>\n"));
								map_already_used_time.insert(date_name.clone(), date.p_end_time.clone());
							},	//Here, there is no block, so we put an empty
							None => {
								match map_already_used_time.get(date_name) {
									Some(end_time) => {
										if row_time >= end_time {	//If we are not in a block we create an empty cell
											backend.write(&String::from("\t\t\t\t<td class=\"timetableEmptyBlockStyle\"></td>\n"));
											//And we remove the current end time which is no more relevant
											map_already_used_time.remove(row_time);
										}
									},
									None => backend.write(&String::from("\t\t\t\t<td class=\"timetableEmptyBlockStyle\"></td>\n"))
								}
							}
						}
					}
				}
				backend.write(&String::from("\t\t\t</tr>\n"));
			}
			//Let's save a last row
			backend.write(&String::from("\t\t\t<tr class=\"timetableTimeRow\">\n"));
			backend.write(&String::from(format!("\t\t\t\t<td class=\"timetableTimeStyle\"><span class=\"timetableTimeTextStyle\">{}</span></td>\n", week.p_last_session_end_time)));
			for _ in 0..vec_date.len() {
				backend.write(&String::from("\t\t\t\t<td class=\"timetableEmptyBlockStyle\"></td>\n"));
			}
			backend.write(&String::from("\t\t\t</tr>\n"));
			backend.write(&String::from("\t\t</table>\n"));
			backend.write(&String::from("\t</td></tr>\n"));
		}
		backend.write(&String::from("</table>\n"));
	}
}