Compare commits
21 commits
1b0c7c7863
...
2f42d4de91
Author | SHA1 | Date | |
---|---|---|---|
2f42d4de91 | |||
642387dcd3 | |||
0e714fd4d3 | |||
ff32a67ce6 | |||
b7581584dc | |||
8b1d7c5b57 | |||
aa287b87c2 | |||
526cd46747 | |||
3fe5529842 | |||
f62c8498ca | |||
c771c249c5 | |||
95bd3a53a9 | |||
07caadc77f | |||
5bc1ddb64d | |||
e4aa7c624c | |||
ab416848ef | |||
fa51950060 | |||
ac6dbcf54c | |||
7e5461ed81 | |||
29117f7e28 | |||
f6b6972ee5 |
5 changed files with 230 additions and 12 deletions
|
@ -24,11 +24,12 @@ $ cargo build --release && cd target/release/
|
||||||
$ ./cal8tor --help
|
$ ./cal8tor --help
|
||||||
```
|
```
|
||||||
|
|
||||||
## ***WIP:*** See the calendar in your terminal
|
## See the calendar in your terminal
|
||||||
For the L2-X, run:
|
For the L2-X, run:
|
||||||
```bash
|
```bash
|
||||||
$ ./cal8tor l2-X
|
$ ./cal8tor l2-X
|
||||||
```
|
```
|
||||||
|
> The rendering can sometimes be unreadable and/or hard to read.
|
||||||
|
|
||||||
## Export the calendar in .ics format
|
## Export the calendar in .ics format
|
||||||
For the L1-A, run:
|
For the L1-A, run:
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -41,26 +41,34 @@ async fn main() {
|
||||||
.name("letter")
|
.name("letter")
|
||||||
.map(|c| c.as_str().chars().next().expect("Error in letter"));
|
.map(|c| c.as_str().chars().next().expect("Error in letter"));
|
||||||
|
|
||||||
|
// Show a separator only if we need one
|
||||||
|
let seperator = match letter {
|
||||||
|
Some(_) => "-",
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Fetch the timetable for L{}{}...",
|
"Récupération de l'emploi du temps des L{}{}{}...",
|
||||||
year,
|
year,
|
||||||
letter.unwrap_or_default()
|
seperator,
|
||||||
|
letter.unwrap_or_default().to_uppercase()
|
||||||
);
|
);
|
||||||
let timetable = timetable::timetable(year, args.semester, letter).await;
|
let timetable = timetable::timetable(year, args.semester, letter).await;
|
||||||
|
|
||||||
println!("Fetch informations about the year...");
|
println!("Récupération des informations par rapport à l'année...");
|
||||||
let info = info::info().await;
|
let info = info::info().await;
|
||||||
|
|
||||||
if args.export.is_some() {
|
if args.export.is_some() {
|
||||||
// Export the calendar
|
// Export the calendar
|
||||||
let filename = args.export.unwrap();
|
let filename = args.export.unwrap();
|
||||||
println!("Build the ICS file at {}...", filename);
|
println!("Fichier .ICS construit et exporté ici : {}...", filename);
|
||||||
|
|
||||||
let builded_timetable = timetable::build(timetable, info);
|
let builded_timetable = timetable::build(timetable, info);
|
||||||
ics::export(builded_timetable, filename);
|
ics::export(builded_timetable, filename);
|
||||||
} else {
|
} else {
|
||||||
// Show the calendar
|
// Show the calendar
|
||||||
println!("Displaying...");
|
println!("Affichage...");
|
||||||
timetable::display();
|
timetable::display(timetable);
|
||||||
|
println!("Vous devrez peut-être mettre votre terminal en plein écran si ce n'est pas déjà le cas.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
use chrono::{Datelike, Duration, TimeZone, Utc};
|
use chrono::{Datelike, Duration, TimeZone, Utc};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use scraper::{Html, Selector};
|
use scraper::{Html, Selector};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::utils::{
|
||||||
|
self,
|
||||||
|
models::{Position, TabChar},
|
||||||
|
};
|
||||||
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
|
||||||
|
@ -175,7 +181,7 @@ async fn get_webpage(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the timetable is well built
|
/// Check if the timetable is well built
|
||||||
fn check_consistency(schedules: &Vec<String>, timetable: &Vec<models::Day>) -> bool {
|
fn check_consistency(schedules: &[String], timetable: &Vec<models::Day>) -> bool {
|
||||||
let mut checker = true;
|
let mut checker = true;
|
||||||
for day in timetable {
|
for day in timetable {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
@ -211,7 +217,7 @@ type T = (
|
||||||
(usize, Vec<models::Day>),
|
(usize, Vec<models::Day>),
|
||||||
);
|
);
|
||||||
// Data builded in the info webpage
|
// Data builded in the info webpage
|
||||||
type D = std::collections::HashMap<
|
type D = HashMap<
|
||||||
// Semester
|
// Semester
|
||||||
usize,
|
usize,
|
||||||
// List of start and repetition of course weeks
|
// List of start and repetition of course weeks
|
||||||
|
@ -299,7 +305,7 @@ fn get_semester(semester: Option<i8>, letter: Option<char>) -> i8 {
|
||||||
None => match letter {
|
None => match letter {
|
||||||
// Based on letter (kinda accurate)
|
// Based on letter (kinda accurate)
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
if c as i8 > 77 {
|
if c.to_ascii_uppercase() as i8 > 77 {
|
||||||
// If letter is N or after
|
// If letter is N or after
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
|
@ -322,6 +328,89 @@ fn get_semester(semester: Option<i8>, letter: Option<char>) -> i8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display the timetable
|
/// Display the timetable
|
||||||
pub fn display() {
|
pub fn display(timetable: (Vec<String>, (usize, Vec<models::Day>))) {
|
||||||
todo!("WIP")
|
// Cell length
|
||||||
|
let cl = 35;
|
||||||
|
// Cell length for hours
|
||||||
|
let clh = 11;
|
||||||
|
// Cell number
|
||||||
|
let cn = 6;
|
||||||
|
|
||||||
|
let sep = TabChar::Bv.val();
|
||||||
|
|
||||||
|
// Top of the tab
|
||||||
|
utils::line_table(clh, cl, cn, Position::Top, HashMap::new());
|
||||||
|
|
||||||
|
// First empty case
|
||||||
|
print!("{}{:^clh$}{}", sep, "", sep);
|
||||||
|
|
||||||
|
// Print day's of the week
|
||||||
|
let mut days = HashMap::new();
|
||||||
|
for (i, data) in (&timetable.1 .1).iter().enumerate() {
|
||||||
|
days.insert(i, &data.name);
|
||||||
|
print!("{:^cl$}{}", &data.name, sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the data of the course for utils::line_table
|
||||||
|
let mut next_skip = HashMap::new();
|
||||||
|
// For each hours -- i the hour's number
|
||||||
|
for (i, hour) in timetable.0.into_iter().enumerate() {
|
||||||
|
// Draw separator line
|
||||||
|
utils::line_table(clh, cl, cn, Position::Middle, next_skip);
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
next_skip = HashMap::new();
|
||||||
|
|
||||||
|
// Print hour
|
||||||
|
print!("{}{:^clh$}", sep, hour);
|
||||||
|
|
||||||
|
// For all the days - `j` the day's number
|
||||||
|
for (j, day) in (&timetable.1 .1).iter().enumerate() {
|
||||||
|
// True if we found something about the slot we are looking for
|
||||||
|
let mut info_slot = false;
|
||||||
|
|
||||||
|
// For all the courses of each days - `k` the possible course.start
|
||||||
|
for (k, course_opt) in (&day.courses).iter().enumerate() {
|
||||||
|
match course_opt {
|
||||||
|
// If there is a course
|
||||||
|
Some(course) => {
|
||||||
|
// Check the course's hour
|
||||||
|
if i == course.start {
|
||||||
|
if course.size > 1 {
|
||||||
|
// If the course uses more than one time slot
|
||||||
|
next_skip.insert(j, &course.name);
|
||||||
|
print!("{}{:^cl$}", sep, "");
|
||||||
|
info_slot = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Else simply print the course
|
||||||
|
print!("{}{:^cl$}", sep, &course.name);
|
||||||
|
info_slot = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no course was found
|
||||||
|
None => {
|
||||||
|
// Verify the "no course" is in the correct day and hour
|
||||||
|
if *days.get(&j).unwrap() == &day.name.to_string() && k == i {
|
||||||
|
// If yes print empty row because there is no course
|
||||||
|
print!("{}{:^cl$}", sep, "");
|
||||||
|
info_slot = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Else it was a course of another day/time
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if !info_slot {
|
||||||
|
// We found nothing about the slot because the precedent course
|
||||||
|
// takes more place than one slot
|
||||||
|
print!("{}{:^cl$}", sep, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("{}", sep);
|
||||||
|
}
|
||||||
|
// Bottom of the table
|
||||||
|
utils::line_table(clh, cl, cn, Position::Bottom, HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
69
src/utils.rs
69
src/utils.rs
|
@ -1,3 +1,5 @@
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
/// Panic if an error happened
|
/// Panic if an error happened
|
||||||
pub fn check_errors(html: &String, loc: &str) {
|
pub fn check_errors(html: &String, loc: &str) {
|
||||||
match html {
|
match html {
|
||||||
|
@ -13,3 +15,70 @@ pub fn check_errors(html: &String, loc: &str) {
|
||||||
fn err_code(code: i32) -> String {
|
fn err_code(code: i32) -> String {
|
||||||
format!("HTTP Code : {}", code)
|
format!("HTTP Code : {}", code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print a line for the table
|
||||||
|
pub fn line_table(
|
||||||
|
cell_length_hours: usize,
|
||||||
|
cell_length: usize,
|
||||||
|
number_cell: usize,
|
||||||
|
pos: models::Position,
|
||||||
|
skip_with: std::collections::HashMap<usize, &str>,
|
||||||
|
) {
|
||||||
|
// Left side
|
||||||
|
let ls = match pos {
|
||||||
|
models::Position::Top => models::TabChar::Jtl.val(),
|
||||||
|
models::Position::Middle => models::TabChar::Jl.val(),
|
||||||
|
models::Position::Bottom => models::TabChar::Jbl.val(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Middle
|
||||||
|
let ms = match pos {
|
||||||
|
models::Position::Top => models::TabChar::Jtb.val(),
|
||||||
|
models::Position::Middle => models::TabChar::Jm.val(),
|
||||||
|
models::Position::Bottom => models::TabChar::Jtt.val(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Right side
|
||||||
|
let rs = match pos {
|
||||||
|
models::Position::Top => models::TabChar::Jtr.val(),
|
||||||
|
models::Position::Middle => models::TabChar::Jr.val(),
|
||||||
|
models::Position::Bottom => models::TabChar::Jbr.val(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
let line = models::TabChar::Bh.val().to_string().repeat(cell_length);
|
||||||
|
let line_h = models::TabChar::Bh
|
||||||
|
.val()
|
||||||
|
.to_string()
|
||||||
|
.repeat(cell_length_hours);
|
||||||
|
|
||||||
|
// Hours column
|
||||||
|
match skip_with.get(&0) {
|
||||||
|
Some(_) => print!("\n{}{}{}", ls, line_h, rs_bbc),
|
||||||
|
None => print!("\n{}{}{}", ls, line_h, ms),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Courses columns
|
||||||
|
for i in 0..number_cell - 2 {
|
||||||
|
// Check if it's a big cell
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("{}{}", line, rs);
|
||||||
|
}
|
||||||
|
|
51
src/utils/models.rs
Normal file
51
src/utils/models.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/// Collection of char for the table
|
||||||
|
pub enum TabChar {
|
||||||
|
/// Vertical bar
|
||||||
|
Bv,
|
||||||
|
/// Horizontal bar
|
||||||
|
Bh,
|
||||||
|
/// Joint left
|
||||||
|
Jl,
|
||||||
|
/// Joint right
|
||||||
|
Jr,
|
||||||
|
/// Joint bottom left
|
||||||
|
Jbl,
|
||||||
|
/// Joint bottom right
|
||||||
|
Jbr,
|
||||||
|
/// Joint top left
|
||||||
|
Jtl,
|
||||||
|
/// Joint top right
|
||||||
|
Jtr,
|
||||||
|
/// Joint to top
|
||||||
|
Jtt,
|
||||||
|
/// Joint to bottom
|
||||||
|
Jtb,
|
||||||
|
/// Joint of the middle
|
||||||
|
Jm,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabChar {
|
||||||
|
/// Value of the element
|
||||||
|
pub fn val(&self) -> char {
|
||||||
|
match *self {
|
||||||
|
Self::Bv => '│',
|
||||||
|
Self::Bh => '─',
|
||||||
|
Self::Jl => '├',
|
||||||
|
Self::Jr => '┤',
|
||||||
|
Self::Jbl => '└',
|
||||||
|
Self::Jbr => '┘',
|
||||||
|
Self::Jtl => '┌',
|
||||||
|
Self::Jtr => '┐',
|
||||||
|
Self::Jtt => '┴',
|
||||||
|
Self::Jtb => '┬',
|
||||||
|
Self::Jm => '┼',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Position for lines inside the table
|
||||||
|
pub enum Position {
|
||||||
|
Top,
|
||||||
|
Middle,
|
||||||
|
Bottom
|
||||||
|
}
|
Reference in a new issue