diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index f6b6187..6fb5cbc 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -1,8 +1,9 @@ name: Build & Test on: + workflow_dispatch: pull_request: - branches: ["main", "dev"] + branches: [ "main", "dev" ] schedule: - cron: "23 3 * * *" env: @@ -14,6 +15,8 @@ jobs: env: SSO_ID: ${{ vars.SSO_ID }} SSO_PASSWORD: ${{ secrets.SSO_PASSWORD }} + TARGET_YEAR: ${{ vars.TARGET_YEAR }} + TARGET_SEMESTER: ${{ vars.TARGET_SEMESTER }} steps: - uses: actions/checkout@v4 - name: Setup Rust diff --git a/.github/workflows/dry-run.yml b/.github/workflows/dry-run.yml index 253ae69..3697ace 100644 --- a/.github/workflows/dry-run.yml +++ b/.github/workflows/dry-run.yml @@ -2,7 +2,7 @@ name: Dry-run before publish on: pull_request: - branches: ["main"] + branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: @@ -12,6 +12,8 @@ jobs: env: SSO_ID: ${{ vars.SSO_ID }} SSO_PASSWORD: ${{ secrets.SSO_PASSWORD }} + TARGET_YEAR: ${{ vars.TARGET_YEAR }} + TARGET_SEMESTER: ${{ vars.TARGET_SEMESTER }} steps: - name: Checkout uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index 0b9cbcb..bf8223f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["packages/rusaint", "packages/rusaint-ffi", "uniffi-bindgen"] resolver = "2" [workspace.package] -version = "0.7.4" +version = "0.8.0" keywords = ["ssu", "u-saint", "scraping", "parser"] authors = ["Hyomin Koo "] @@ -14,5 +14,5 @@ edition = "2021" readme = "README.md" [workspace.dependencies] -thiserror = "1.0.44" +thiserror = "2.0.8" tokio = { version = "1.29.1" } diff --git a/languages/kotlin/lib/build.gradle.kts b/languages/kotlin/lib/build.gradle.kts index b6e3c5f..9d74b62 100644 --- a/languages/kotlin/lib/build.gradle.kts +++ b/languages/kotlin/lib/build.gradle.kts @@ -13,7 +13,7 @@ plugins { group = "dev.eatsteak" description = "Easy and Reliable SSU u-saint scraper" -version = "0.7.4" +version = "0.8.0" android { namespace = "dev.eatsteak.rusaint" diff --git a/packages/rusaint-ffi/Cargo.toml b/packages/rusaint-ffi/Cargo.toml index f4ac337..676d2bf 100644 --- a/packages/rusaint-ffi/Cargo.toml +++ b/packages/rusaint-ffi/Cargo.toml @@ -14,10 +14,10 @@ crate-type = ["lib", "cdylib", "staticlib"] name = "rusaint_ffi" [dependencies] -uniffi = { version = "0.28.1", features = ["tokio"] } +uniffi = { version = "0.28.3", features = ["tokio"] } rusaint = { path = "../rusaint", features = ["uniffi"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"] } [build-dependencies] -uniffi = { version = "0.28.1", features = ["build"] } +uniffi = { version = "0.28.3", features = ["build"] } diff --git a/packages/rusaint-ffi/src/application/course_grades.rs b/packages/rusaint-ffi/src/application/course_grades.rs index 66d62da..c9bb358 100644 --- a/packages/rusaint-ffi/src/application/course_grades.rs +++ b/packages/rusaint-ffi/src/application/course_grades.rs @@ -52,7 +52,7 @@ impl CourseGradesApplication { pub async fn classes( &self, course_type: CourseType, - year: &str, + year: u32, semester: SemesterType, include_details: bool, ) -> Result, RusaintError> { @@ -68,7 +68,7 @@ impl CourseGradesApplication { pub async fn class_detail( &self, course_type: CourseType, - year: &str, + year: u32, semester: SemesterType, code: &str, ) -> Result, RusaintError> { diff --git a/packages/rusaint/Cargo.toml b/packages/rusaint/Cargo.toml index 02897c8..7deaffd 100644 --- a/packages/rusaint/Cargo.toml +++ b/packages/rusaint/Cargo.toml @@ -19,9 +19,9 @@ application = ["element"] uniffi = ["dep:uniffi", "application"] [dependencies] -uniffi = { version = "0.28.1", optional = true } -derive_builder = "0.20.0" -reqwest = { version = "0.12.7", features = [ +uniffi = { version = "0.28.3", optional = true } +derive_builder = "0.20.2" +reqwest = { version = "0.12.9", features = [ "charset", "http2", "macos-system-configuration", @@ -33,10 +33,10 @@ reqwest = { version = "0.12.7", features = [ thiserror = { workspace = true } tokio = { workspace = true, features = ["sync"] } html-escape = "0.2.13" -url = "2.5.2" +url = "2.5.4" roxmltree = "0.20.0" lol_html = "1.2.1" -scraper = { version = "0.20.0", features = ["atomic"], optional = true } +scraper = { version = "0.22.0", features = ["atomic"], optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" custom_debug_derive = "0.6.1" @@ -52,4 +52,4 @@ tokio-test = "0.4.4" tokio = { workspace = true, features = ["macros", "test-util"] } [build-dependencies] -uniffi = { version = "0.28.1", features = ["build"] } +uniffi = { version = "0.28.3", features = ["build"] } diff --git a/packages/rusaint/src/application/course_grades/mod.rs b/packages/rusaint/src/application/course_grades/mod.rs index 5c508a5..8604a0d 100644 --- a/packages/rusaint/src/application/course_grades/mod.rs +++ b/packages/rusaint/src/application/course_grades/mod.rs @@ -362,7 +362,7 @@ impl<'a> CourseGradesApplication { /// # use rusaint::model::SemesterType; /// # let session = Arc::new(USaintSession::with_password("20212345", "password").await.unwrap()); /// let mut app = USaintClientBuilder::new().session(session).build_into::().await.unwrap(); - /// let classes = app.classes(CourseType::Bachelor, "2022", SemesterType::Two, false).await.unwrap(); + /// let classes = app.classes(CourseType::Bachelor, 2022, SemesterType::Two, false).await.unwrap(); /// println!("{:?}", classes); // around 3s(depends on network environment) /// // [ClassGrade { ... }, ClassGrade { ... }] /// # }) @@ -377,7 +377,7 @@ impl<'a> CourseGradesApplication { /// # use rusaint::application::USaintClientBuilder; /// # let session = Arc::new(USaintSession::with_password("20212345", "password").await.unwrap()); /// let mut app = USaintClientBuilder::new().session(session).build_into::().await.unwrap(); - /// let classes = app.classes(CourseType::Bachelor, "2022", SemesterType::Two, true).await.unwrap(); + /// let classes = app.classes(CourseType::Bachelor, 2022, SemesterType::Two, true).await.unwrap(); /// println!("{:?}", classes); // around 10s(depends on network environment) /// // [ClassGrade { ... }, ClassGrade { ... }] /// # }) @@ -385,15 +385,16 @@ impl<'a> CourseGradesApplication { pub async fn classes( &mut self, course_type: CourseType, - year: &str, + year: u32, semester: SemesterType, include_details: bool, ) -> Result, RusaintError> { + let year = year.to_string(); { self.close_popups().await?; let parser = ElementParser::new(self.client.body()); self.select_course(&parser, course_type).await?; - self.select_semester(&parser, year, semester).await?; + self.select_semester(&parser, &year, semester).await?; } let parser = ElementParser::new(self.client.body()); let class_grades: Vec<(Option, HashMap)> = { @@ -461,9 +462,9 @@ impl<'a> CourseGradesApplication { /// # use rusaint::application::USaintClientBuilder; /// # let session = Arc::new(USaintSession::with_password("20212345", "password").await.unwrap()); /// let mut app = USaintClientBuilder::new().session(session).build_into::().await.unwrap(); - /// let classes = app.classes(CourseType::Bachelor, "2022", SemesterType::Two, false).await.unwrap(); + /// let classes = app.classes(CourseType::Bachelor, 2022, SemesterType::Two, false).await.unwrap(); /// let class = classes.iter().next().unwrap(); - /// let class_detail = app.class_detail(CourseType::Bachelor, "2022", SemesterType::Two, class.code()).await.unwrap(); + /// let class_detail = app.class_detail(CourseType::Bachelor, 2022, SemesterType::Two, class.code()).await.unwrap(); /// println!("{:?}", class_detail); /// // {"출석(20.000)": 20.0, "중간고사(30.000)": 30.0, "과제(20.000)": 20.0, "기말고사(30.000)": 28.0} /// # }) @@ -471,15 +472,16 @@ impl<'a> CourseGradesApplication { pub async fn class_detail( &mut self, course_type: CourseType, - year: &str, + year: u32, semester: SemesterType, code: &str, ) -> Result, RusaintError> { + let year = year.to_string(); { self.close_popups().await?; let parser = ElementParser::new(self.client.body()); self.select_course(&parser, course_type).await?; - self.select_semester(&parser, year, semester).await?; + self.select_semester(&parser, &year, semester).await?; } let parser = ElementParser::new(self.client.body()); let table = parser.read(SapTableBodyCommand::new(Self::GRADE_BY_CLASSES_TABLE))?; diff --git a/packages/rusaint/src/lib.rs b/packages/rusaint/src/lib.rs index 2cf3db8..7536d24 100644 --- a/packages/rusaint/src/lib.rs +++ b/packages/rusaint/src/lib.rs @@ -87,11 +87,10 @@ pub mod global_test_utils { use dotenv::dotenv; use std::sync::{Arc, OnceLock}; - use crate::USaintSession; - - pub static SESSION: OnceLock> = OnceLock::new(); + use crate::{model::SemesterType, USaintSession}; pub async fn get_session() -> Result> { + static SESSION: OnceLock> = OnceLock::new(); if let Some(session) = SESSION.get() { Ok(session.to_owned()) } else { @@ -106,4 +105,41 @@ pub mod global_test_utils { .ok_or(Error::msg("Session is not initsiated")) } } + + #[cfg(feature = "application")] + pub async fn get_year() -> Result { + static TARGET_YEAR: OnceLock = OnceLock::new(); + if let Some(year) = TARGET_YEAR.get() { + Ok(*year) + } else { + let year = std::env::var("TARGET_YEAR")?.parse()?; + let _ = TARGET_YEAR.set(year); + TARGET_YEAR + .get() + .copied() + .ok_or(Error::msg("Year is not initsiated")) + } + } + + #[cfg(feature = "application")] + pub fn get_semester() -> Result { + static TARGET_SEMESTER: OnceLock = OnceLock::new(); + if let Some(semester) = TARGET_SEMESTER.get() { + Ok(*semester) + } else { + let semester = std::env::var("TARGET_SEMESTER")?; + let semester_type = match semester.to_uppercase().as_str() { + "1" | "ONE" => SemesterType::One, + "SUMMER" => SemesterType::Summer, + "2" | "TWO" => SemesterType::Two, + "WINTER" => SemesterType::Winter, + _ => return Err(Error::msg("Invalid semester")), + }; + let _ = TARGET_SEMESTER.set(semester_type); + TARGET_SEMESTER + .get() + .copied() + .ok_or(Error::msg("Semester is not initsiated")) + } + } } diff --git a/packages/rusaint/src/webdynpro/element/complex/sap_table/body.rs b/packages/rusaint/src/webdynpro/element/complex/sap_table/body.rs index be8834d..fe0a3a0 100644 --- a/packages/rusaint/src/webdynpro/element/complex/sap_table/body.rs +++ b/packages/rusaint/src/webdynpro/element/complex/sap_table/body.rs @@ -22,6 +22,7 @@ pub struct SapTableBody { } impl<'a> SapTableBody { + /// TODO: Implement unit test for rowspan/colspan handling pub(super) fn new( table_def: SapTableDef, elem_ref: ElementRef<'a>, @@ -96,7 +97,7 @@ impl<'a> SapTableBody { if rowspan > 1 { spans.insert(col_counter, (cell.clone(), rowspan, colspan)); } - for _ in 0..rowspan { + for _ in 0..colspan { cells.push(cell.clone()); col_counter += 1; } diff --git a/packages/rusaint/tests/application/chapel.rs b/packages/rusaint/tests/application/chapel.rs index ed4f093..3c5d8d0 100644 --- a/packages/rusaint/tests/application/chapel.rs +++ b/packages/rusaint/tests/application/chapel.rs @@ -1,12 +1,11 @@ +use crate::{get_semester, get_session, get_year}; +use rusaint::model::SemesterType; use rusaint::{ application::{chapel::ChapelApplication, USaintClientBuilder}, - model::SemesterType, ApplicationError, RusaintError, }; use serial_test::serial; -use crate::get_session; - #[tokio::test] #[serial] async fn chapel() { @@ -16,7 +15,10 @@ async fn chapel() { .build_into::() .await .unwrap(); - let info = app.information(2022, SemesterType::Two).await.unwrap(); + let info = app + .information(get_year().unwrap(), get_semester().unwrap()) + .await + .unwrap(); println!("{:?}", info); } @@ -29,7 +31,7 @@ async fn no_chapel() { .build_into::() .await .unwrap(); - let info = app.information(2024, SemesterType::Two).await.unwrap_err(); + let info = app.information(2017, SemesterType::Two).await.unwrap_err(); assert!(matches!( info, RusaintError::ApplicationError(ApplicationError::NoChapelInformation) diff --git a/packages/rusaint/tests/application/course_grades.rs b/packages/rusaint/tests/application/course_grades.rs index 9777345..a5e4399 100644 --- a/packages/rusaint/tests/application/course_grades.rs +++ b/packages/rusaint/tests/application/course_grades.rs @@ -1,13 +1,10 @@ -use rusaint::{ - application::{ - course_grades::{model::CourseType, CourseGradesApplication}, - USaintClientBuilder, - }, - model::SemesterType, +use rusaint::application::{ + course_grades::{model::CourseType, CourseGradesApplication}, + USaintClientBuilder, }; use serial_test::serial; -use crate::get_session; +use crate::{get_semester, get_session, get_year}; #[tokio::test] #[serial] @@ -67,7 +64,12 @@ async fn classes_with_detail() { .await .unwrap(); let details = app - .classes(CourseType::Bachelor, "2022", SemesterType::Two, true) + .classes( + CourseType::Bachelor, + get_year().unwrap(), + get_semester().unwrap(), + true, + ) .await .unwrap(); println!("{:?}", details); @@ -80,8 +82,8 @@ async fn classes_with_detail() { let detail = app .class_detail( CourseType::Bachelor, - "2022", - SemesterType::Two, + get_year().unwrap(), + get_semester().unwrap(), detail_code.code(), ) .await diff --git a/packages/rusaint/tests/application/personal_course_schedule.rs b/packages/rusaint/tests/application/personal_course_schedule.rs index 9c98458..a422a36 100644 --- a/packages/rusaint/tests/application/personal_course_schedule.rs +++ b/packages/rusaint/tests/application/personal_course_schedule.rs @@ -1,14 +1,13 @@ +use crate::{get_semester, get_session, get_year}; +use rusaint::model::SemesterType; use rusaint::{ application::{ personal_course_schedule::PersonalCourseScheduleApplication, USaintClientBuilder, }, - model::SemesterType, ApplicationError, RusaintError, }; use serial_test::serial; -use crate::get_session; - #[tokio::test] #[serial] async fn schedule() { @@ -18,7 +17,10 @@ async fn schedule() { .build_into::() .await .unwrap(); - let info = app.schedule(2022, SemesterType::Two).await.unwrap(); + let info = app + .schedule(get_year().unwrap(), get_semester().unwrap()) + .await + .unwrap(); println!("{:?}", info); } @@ -31,7 +33,7 @@ async fn no_schedule() { .build_into::() .await .unwrap(); - let info = app.schedule(2024, SemesterType::Two).await.unwrap_err(); + let info = app.schedule(2017, SemesterType::Two).await.unwrap_err(); assert!(matches!( info, RusaintError::ApplicationError(ApplicationError::NoScheduleInformation) diff --git a/packages/rusaint/tests/tests.rs b/packages/rusaint/tests/tests.rs index a15a70a..d2f12f2 100644 --- a/packages/rusaint/tests/tests.rs +++ b/packages/rusaint/tests/tests.rs @@ -2,11 +2,10 @@ use anyhow::{Error, Result}; use dotenv::dotenv; use std::sync::{Arc, OnceLock}; -use rusaint::USaintSession; - -static SESSION: OnceLock> = OnceLock::new(); +use rusaint::{model::SemesterType, USaintSession}; pub async fn get_session() -> Result> { + static SESSION: OnceLock> = OnceLock::new(); if let Some(session) = SESSION.get() { Ok(session.to_owned()) } else { @@ -22,6 +21,41 @@ pub async fn get_session() -> Result> { } } +pub fn get_year() -> Result { + static TARGET_YEAR: OnceLock = OnceLock::new(); + if let Some(year) = TARGET_YEAR.get() { + Ok(*year) + } else { + let year = std::env::var("TARGET_YEAR")?.parse()?; + let _ = TARGET_YEAR.set(year); + TARGET_YEAR + .get() + .copied() + .ok_or(Error::msg("Year is not initsiated")) + } +} + +pub fn get_semester() -> Result { + static TARGET_SEMESTER: OnceLock = OnceLock::new(); + if let Some(semester) = TARGET_SEMESTER.get() { + Ok(*semester) + } else { + let semester = std::env::var("TARGET_SEMESTER")?; + let semester_type = match semester.to_uppercase().as_str() { + "1" | "ONE" => SemesterType::One, + "SUMMER" => SemesterType::Summer, + "2" | "TWO" => SemesterType::Two, + "WINTER" => SemesterType::Winter, + _ => return Err(Error::msg("Invalid semester")), + }; + let _ = TARGET_SEMESTER.set(semester_type); + TARGET_SEMESTER + .get() + .copied() + .ok_or(Error::msg("Semester is not initsiated")) + } +} + mod application; #[cfg(test)] mod webdynpro;