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
use std::time::SystemTime;
use crate::raw_types::RawWebRegMeeting;
use crate::types::MeetingDay;
/// Gets the meeting type (e.g. Lecture, Final Exam, Discussion, etc.) and the meeting time from
/// an arbitrary `WebRegMeeting`.
///
/// # Parameters
/// - `w_meeting`: The WebReg meeting to check.
///
/// # Returns
/// A tuple where:
/// - the first element is the meeting type
/// - the second element is/are the day(s) that this meeting occurs
#[inline]
pub fn parse_meeting_type_date(w_meeting: &RawWebRegMeeting) -> (&str, MeetingDay) {
let special_meeting = w_meeting.special_meeting.trim();
if !special_meeting.is_empty() && special_meeting != "TBA" {
assert!(!w_meeting.section_start_date.is_empty());
return (
special_meeting,
MeetingDay::OneTime(w_meeting.start_date.to_string()),
);
}
let regular_meeting = w_meeting.meeting_type.trim();
let day_code = w_meeting.day_code.trim();
assert!(day_code.chars().into_iter().all(|x| x.is_numeric()));
if day_code.is_empty() {
(regular_meeting, MeetingDay::None)
} else {
(
regular_meeting,
MeetingDay::Repeated(parse_day_code(day_code)),
)
}
}
/// Parses the days of the week from a day code string.
///
/// # Parameters
/// - `day_code_str`: The day code string. This should only contain integers between 0 and 6, both
/// inclusive.
///
/// # Returns
/// A string with the days of the week.
///
/// # Example
/// An input of `135` would return `["M", "W", "F"]`.
pub fn parse_day_code(day_code_str: &str) -> Vec<String> {
let mut s = vec![];
day_code_str.chars().for_each(|c| {
if !c.is_numeric() {
return;
}
match c {
'0' => s.push("Su".to_string()),
'1' => s.push("M".to_string()),
'2' => s.push("Tu".to_string()),
'3' => s.push("W".to_string()),
'4' => s.push("Th".to_string()),
'5' => s.push("F".to_string()),
'6' => s.push("Sa".to_string()),
_ => {}
};
});
s
}
const DAYS: [&str; 7] = ["M", "Tu", "W", "Th", "F", "Sa", "Su"];
/// Parses a binary string representing the days that are active.
///
/// # Parameters
/// - `bin_str`: The binary string. Must be length 7. The first bit
/// represents Monday, the second bit represents Tuesday, and so on.
/// The `1` bit means that the day is active, and the `0` bit means
/// the day is inactive.
///
/// # Returns
/// A string with the days of the week.
///
/// # Example
/// An input of `1010101` would return `["M", "W", "F", "Su"]`.
pub fn parse_binary_days(bin_str: &str) -> Vec<String> {
let mut days = vec![];
if bin_str.len() == 7 {
let day_vec = bin_str.chars().collect::<Vec<_>>();
for (idx, day) in DAYS.iter().enumerate() {
if day_vec[idx] == '1' {
days.push(day.to_string());
}
}
}
days
}
const TERM_ARR: [(&str, (i64, i64)); 6] = [
("SP", (5200, 22)), // SP22
("S1", (5210, 22)), // S122
("S2", (5220, 22)), // S222
("S3", (5230, 22)), // S322
("FA", (5250, 22)), // FA22
("WI", (5260, 23)), // WI23
];
/// Gets the term ID based on the term that was passed in.
///
/// # Parameters
/// - `term`: The term
///
/// # Returns
/// The term ID, if valid. If `0` is returned, then the input
/// is invalid.
pub fn get_term_seq_id(term: impl AsRef<str>) -> i64 {
let term = term.as_ref();
if term.len() != 4 {
return 0;
}
let term_init = &term[..2];
let (base_seq_id, base_year) = match TERM_ARR.iter().find(|(term, _)| *term == term_init) {
Some((_, data)) => *data,
None => return 0,
};
let quarter_yr = match term[2..].parse::<i64>() {
Ok(o) => o,
Err(_) => return 0,
};
// 70 is the difference between each term, apparently
// For example, the seqid of FA22 and FA23 has a difference of 70
70 * (quarter_yr - base_year) + base_seq_id
}
/// Gets the formatted course code so that it can be recognized by
/// WebReg's internal API.
///
/// # Parameters
/// - `course_code`: The course code, e.g. if you have the course
/// `CSE 110`, you would put `110`.
///
/// # Returns
/// The formatted course code for WebReg.
#[inline(always)]
pub fn get_formatted_course_num(course_code: &str) -> String {
// If the course code only has 1 digit (excluding any letters), then we need to prepend 2
// spaces to the course code.
//
// If the course code has 2 digits (excluding any letters), then we need to prepend 1
// space to the course code.
//
// Otherwise, don't need to prepend any spaces to the course code.
//
// For now, assume that no digits will ever appear *after* the letters. Weird thing is that
// WebReg uses '+' to offset the course code but spaces are accepted.
match course_code.chars().filter(|x| x.is_ascii_digit()).count() {
1 => format!(" {}", course_code),
2 => format!(" {}", course_code),
_ => course_code.to_string(),
}
}
/// Gets the current epoch time.
///
/// # Returns
/// The current time.
#[inline(always)]
pub(crate) fn get_epoch_time() -> u128 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis()
}
/// Gets the instructor's names.
///
/// # Parameters
/// - `instructor_name`: The raw name.
///
/// # Returns
/// The parsed instructor's names, as a vector.
#[inline(always)]
pub(crate) fn get_instructor_names(instructor_name: &str) -> Vec<String> {
// The instructor string is in the form
// name1 ;pid1:name2 ;pid2:...:nameN ;pidN
instructor_name
.split(':')
.map(|x| {
if x.contains(';') {
x.split_once(';').unwrap().0.trim().to_string()
} else {
x.trim().to_string()
}
})
.collect()
}
/// Removes duplicate names from the list of instructors that are given.
///
/// # Parameters
/// - `instructors`: An iterator of instructors, potentially with duplicates.
///
/// # Returns
/// A vector of instructors, with no duplicates.
#[inline(always)]
pub(crate) fn get_all_instructors<I>(instructors: I) -> Vec<String>
where
I: Iterator<Item = String>,
{
let mut all_inst = instructors.collect::<Vec<_>>();
all_inst.sort();
all_inst.dedup();
all_inst
}
/// Formats multiple course inputs into a string that WebReg can recognize
/// for its search queries.
///
/// # Parameters
/// - `query`: The vector of courses to format. Each element can either be a
/// full course code (e.g., `CSE 100`) or a partial course code (e.g., `CSE`
/// or `100`).
///
/// # Returns
/// The formatted string.
pub fn format_multiple_courses<T: AsRef<str>>(query: &[T]) -> String {
// The way the string query is formatted is
// - each course (or part of course) is separated by ';'
// - each whitespace within the course is replaced with ':'
query
.iter()
.map(|x| {
x.as_ref()
.split_whitespace()
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
})
.filter(|x| !x.is_empty())
// Essentially, for each course...
.map(|course| {
// For each course vector, let's see what we should do.
// There are several cases to consider for each item; an item can either be one of:
// 1. SubjCode
// 2. Subj
// 3. Code
// 4. Subj, Code [handled implicitly by cases 2 and 3]
//
// For now, we'll go through each individual item in the vector and
// process it
course
.iter()
.map(|item| {
match item.chars().next() {
// Case 3
Some(c) if c.is_ascii_digit() => get_formatted_course_num(item),
// Case 1 or 2
Some(_) => {
// See if case 1 is what we're working with
if let Some(idx) = item.find(|c: char| c.is_ascii_digit()) {
let subj = &item[..idx];
let csrc = &item[idx..];
format!("{}:{}", subj, get_formatted_course_num(csrc))
} else {
item.to_string()
}
}
// This should never hit
_ => "".to_string(),
}
})
.collect::<Vec<_>>()
.join(":")
})
.collect::<Vec<_>>()
.join(";")
.to_uppercase()
}