2024-01-01 12:09:44 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2023-09-28 00:01:48 +02:00
|
|
|
use chrono::{Datelike, Utc};
|
|
|
|
use scraper::Html;
|
|
|
|
|
2024-01-01 12:09:44 +01:00
|
|
|
use crate::timetable::models::{Category, Course, Timetable};
|
|
|
|
|
2022-08-23 13:22:56 +02:00
|
|
|
pub mod models;
|
2022-08-23 13:16:58 +02:00
|
|
|
|
2022-08-16 15:48:13 +02:00
|
|
|
/// Panic if an error happened
|
|
|
|
pub fn check_errors(html: &String, loc: &str) {
|
2023-09-18 19:50:30 +02:00
|
|
|
let no_timetable = "Aucun créneau horaire affecté";
|
2022-08-16 15:48:13 +02:00
|
|
|
match html {
|
2023-09-18 19:50:30 +02:00
|
|
|
t if t.contains(no_timetable) => panic!("URL: {} • {}", loc, no_timetable),
|
2022-08-16 15:48:13 +02:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-23 13:16:58 +02:00
|
|
|
/// Print a line for the table
|
2022-08-23 15:15:29 +02:00
|
|
|
pub fn line_table(
|
|
|
|
cell_length_hours: usize,
|
|
|
|
cell_length: usize,
|
|
|
|
number_cell: usize,
|
|
|
|
pos: models::Position,
|
|
|
|
skip_with: std::collections::HashMap<usize, &str>,
|
|
|
|
) {
|
2022-08-23 13:16:58 +02:00
|
|
|
// Left side
|
|
|
|
let ls = match pos {
|
2022-08-23 13:22:56 +02:00
|
|
|
models::Position::Top => models::TabChar::Jtl.val(),
|
|
|
|
models::Position::Middle => models::TabChar::Jl.val(),
|
|
|
|
models::Position::Bottom => models::TabChar::Jbl.val(),
|
2022-08-23 13:16:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Middle
|
|
|
|
let ms = match pos {
|
2022-08-23 13:22:56 +02:00
|
|
|
models::Position::Top => models::TabChar::Jtb.val(),
|
|
|
|
models::Position::Middle => models::TabChar::Jm.val(),
|
|
|
|
models::Position::Bottom => models::TabChar::Jtt.val(),
|
2022-08-23 13:16:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// Right side
|
|
|
|
let rs = match pos {
|
2022-08-23 13:22:56 +02:00
|
|
|
models::Position::Top => models::TabChar::Jtr.val(),
|
|
|
|
models::Position::Middle => models::TabChar::Jr.val(),
|
|
|
|
models::Position::Bottom => models::TabChar::Jbr.val(),
|
2022-08-23 13:16:58 +02:00
|
|
|
};
|
|
|
|
|
2022-08-23 15:15:29 +02:00
|
|
|
// Right side before big cell
|
|
|
|
let rs_bbc = models::TabChar::Jr.val();
|
|
|
|
// Right side big cell before big cell
|
|
|
|
let rsbc_bbc = models::TabChar::Bv.val();
|
|
|
|
// Right side big cell
|
|
|
|
let rsbc = models::TabChar::Jl.val();
|
|
|
|
|
2022-08-23 13:22:56 +02:00
|
|
|
let line = models::TabChar::Bh.val().to_string().repeat(cell_length);
|
2022-08-23 15:15:29 +02:00
|
|
|
let line_h = models::TabChar::Bh
|
|
|
|
.val()
|
|
|
|
.to_string()
|
|
|
|
.repeat(cell_length_hours);
|
2022-08-23 13:16:58 +02:00
|
|
|
|
2022-08-23 16:54:49 +02:00
|
|
|
// Hours column
|
|
|
|
match skip_with.get(&0) {
|
|
|
|
Some(_) => print!("\n{}{}{}", ls, line_h, rs_bbc),
|
|
|
|
None => print!("\n{}{}{}", ls, line_h, ms),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Courses columns
|
2022-08-30 22:45:58 +02:00
|
|
|
let range = number_cell - 1;
|
|
|
|
let mut last_day = false;
|
|
|
|
for i in 0..range {
|
2022-08-23 15:15:29 +02:00
|
|
|
// Check if it's a big cell
|
2022-08-30 22:45:58 +02:00
|
|
|
if i == range - 1 {
|
|
|
|
// Friday only
|
2023-01-10 11:53:19 +01:00
|
|
|
if let Some(text) = skip_with.get(&i) {
|
|
|
|
println!("{:^cell_length$}{}", text, rsbc_bbc);
|
|
|
|
last_day = true;
|
2022-08-30 22:45:58 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
match skip_with.get(&i) {
|
|
|
|
Some(text) => match skip_with.get(&(i + 1)) {
|
|
|
|
// Match check if the next cell will be big
|
|
|
|
Some(_) => print!("{:^cell_length$}{}", text, rsbc_bbc),
|
|
|
|
None => print!("{:^cell_length$}{}", text, rsbc),
|
|
|
|
},
|
|
|
|
None => match skip_with.get(&(i + 1)) {
|
|
|
|
// Match check if the next cell will be big
|
|
|
|
Some(_) => print!("{}{}", line, rs_bbc),
|
|
|
|
None => print!("{}{}", line, ms),
|
|
|
|
},
|
|
|
|
}
|
2022-08-23 13:42:16 +02:00
|
|
|
}
|
2022-08-23 13:16:58 +02:00
|
|
|
}
|
2022-08-30 22:45:58 +02:00
|
|
|
if !last_day {
|
|
|
|
println!("{}{}", line, rs);
|
|
|
|
}
|
2022-08-23 13:16:58 +02:00
|
|
|
}
|
2022-08-23 18:30:01 +02:00
|
|
|
|
|
|
|
// Split a string in half with respect of words
|
|
|
|
pub fn split_half(text: &str) -> (&str, &str) {
|
|
|
|
let mid = text.len() / 2;
|
|
|
|
for (i, j) in (mid..text.len()).enumerate() {
|
|
|
|
if text.as_bytes()[j] == b' ' {
|
|
|
|
return text.split_at(mid + i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
text.split_at(mid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reduce size of string by adding etc. to it, and cutting some info
|
|
|
|
pub fn etc_str(text: &str) -> String {
|
2022-08-23 18:32:06 +02:00
|
|
|
format!("{}...", split_half(text).0.trim())
|
2022-08-23 18:30:01 +02:00
|
|
|
}
|
2022-08-30 22:59:04 +02:00
|
|
|
|
2023-09-28 00:01:48 +02:00
|
|
|
/// Get timetable webpage
|
|
|
|
pub async fn get_webpage(
|
|
|
|
level: i8,
|
|
|
|
semester: i8,
|
|
|
|
year: &str,
|
|
|
|
user_agent: &str,
|
|
|
|
) -> Result<Html, Box<dyn std::error::Error>> {
|
|
|
|
let url = format!("https://silice.informatique.univ-paris-diderot.fr/ufr/U{}/EDT/visualiserEmploiDuTemps.php?quoi=M{},{}", year, level, semester);
|
|
|
|
|
|
|
|
// Use custom User-Agent
|
|
|
|
let client = reqwest::Client::builder().user_agent(user_agent).build()?;
|
|
|
|
let html = client.get(&url).send().await?.text().await?;
|
|
|
|
|
|
|
|
// Panic on error
|
|
|
|
crate::utils::check_errors(&html, &url);
|
|
|
|
|
|
|
|
// Parse document
|
|
|
|
let document = Html::parse_document(&html);
|
|
|
|
|
|
|
|
Ok(document)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the current semester depending on the current date
|
|
|
|
pub fn get_semester(semester: Option<i8>) -> i8 {
|
|
|
|
match semester {
|
|
|
|
// Force the asked semester
|
|
|
|
Some(n) => n,
|
|
|
|
// Find the potential semester
|
|
|
|
None => {
|
|
|
|
if Utc::now().month() > 6 {
|
|
|
|
// From july to december
|
|
|
|
1
|
|
|
|
} else {
|
|
|
|
// from january to june
|
|
|
|
2
|
|
|
|
}
|
|
|
|
}
|
2022-08-30 22:59:04 +02:00
|
|
|
}
|
2023-09-28 00:01:48 +02:00
|
|
|
}
|
2022-08-30 22:59:04 +02:00
|
|
|
|
2023-09-28 00:01:48 +02:00
|
|
|
/// Get the current year depending on the current date
|
|
|
|
pub fn get_year(year: Option<i32>, semester: i8) -> String {
|
|
|
|
let wanted_year = match year {
|
|
|
|
// Force the asked semester
|
|
|
|
Some(n) => n,
|
|
|
|
// Find the potential semester
|
|
|
|
None => Utc::now().year(),
|
|
|
|
};
|
|
|
|
|
|
|
|
if semester == 1 {
|
|
|
|
format!("{}-{}", wanted_year, wanted_year + 1)
|
|
|
|
} else {
|
|
|
|
format!("{}-{}", wanted_year - 1, wanted_year)
|
|
|
|
}
|
2022-08-30 22:59:04 +02:00
|
|
|
}
|
2023-09-28 22:09:43 +02:00
|
|
|
|
|
|
|
pub trait Capitalize {
|
|
|
|
/// Capitalize string
|
|
|
|
fn capitalize(&self) -> String;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Capitalize for str {
|
|
|
|
fn capitalize(&self) -> String {
|
|
|
|
let mut string = self.to_owned();
|
|
|
|
if let Some(r) = string.get_mut(0..1) {
|
|
|
|
r.make_ascii_uppercase();
|
|
|
|
}
|
|
|
|
|
|
|
|
string
|
|
|
|
}
|
|
|
|
}
|
2023-09-29 00:03:41 +02:00
|
|
|
|
|
|
|
pub fn fill_hours(hours: &mut Vec<String>) {
|
|
|
|
for hour in 8..=20 {
|
|
|
|
for minute in &[0, 15, 30, 45] {
|
|
|
|
let hour_str = format!("{}h{:02}", hour, minute);
|
|
|
|
if let Some(last_hour) = hours.pop() {
|
|
|
|
hours.push(format!("{}-{}", last_hour, hour_str));
|
|
|
|
}
|
|
|
|
hours.push(hour_str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _ in 0..4 {
|
|
|
|
hours.pop();
|
|
|
|
}
|
|
|
|
}
|
2024-01-01 12:09:44 +01:00
|
|
|
|
|
|
|
/// Names showed to the users
|
|
|
|
pub fn get_selection(data: &(&Course, String)) -> String {
|
|
|
|
let mut hours = vec![];
|
|
|
|
fill_hours(&mut hours);
|
|
|
|
|
|
|
|
format!(
|
|
|
|
"{} - {} {}-{}",
|
|
|
|
data.0.name,
|
|
|
|
data.1,
|
|
|
|
hours[data.0.start].split_once('-').unwrap().0,
|
|
|
|
hours[data.0.start + data.0.size - 1]
|
|
|
|
.split_once('-')
|
|
|
|
.unwrap()
|
|
|
|
.1
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Entry's name used for finding duplicates
|
|
|
|
pub fn get_entry(course: &Course) -> String {
|
|
|
|
format!("{} - {:?}", course.name, course.category)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_count<'a>(
|
|
|
|
timetable: &'a mut Timetable,
|
|
|
|
allowed_list: &'a [Category],
|
|
|
|
) -> (Vec<(&'a Course, String)>, HashMap<String, i32>) {
|
|
|
|
// List of courses who will be courses
|
|
|
|
let mut courses = vec![];
|
|
|
|
|
|
|
|
let mut counts = HashMap::new();
|
|
|
|
timetable.1 .1.iter().for_each(|day| {
|
|
|
|
day.courses.iter().for_each(|course_opt| {
|
|
|
|
if let Some(course) = course_opt {
|
|
|
|
if allowed_list.contains(&course.category) {
|
|
|
|
courses.push((course, day.name.to_owned()));
|
|
|
|
let count = counts.entry(get_entry(course)).or_insert(0);
|
|
|
|
*count += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
(courses, counts)
|
|
|
|
}
|