Skip to content

Commit

Permalink
Merge pull request #60 from EATSTEAK/dev
Browse files Browse the repository at this point in the history
[release] 0.3.0
  • Loading branch information
EATSTEAK authored May 5, 2024
2 parents 56cb856 + 3f032fd commit 10597d6
Show file tree
Hide file tree
Showing 58 changed files with 1,301 additions and 772 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rusaint"
version = "0.2.0"
version = "0.3.0"
description = "Easy-to-use SSU u-saint client"
keywords = ["ssu", "u-saint", "scraping", "parser"]
categories = ["web-programming"]
Expand Down
88 changes: 33 additions & 55 deletions src/application/course_grades/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ use crate::{
selection::{ComboBoxSelectCommand, ReadComboBoxLSDataCommand},
},
element::{
action::Button,
action::ButtonDef,
complex::sap_table::{
cell::{SapTableCell, SapTableCellWrapper},
property::SapTableCellType,
SapTable, SapTableRow,
SapTable,
},
definition::ElementDef,
definition::ElementDefinition,
layout::PopupWindow,
selection::ComboBox,
sub::SubElement,
text::InputField,
Element, ElementWrapper, SubElement,
Element, ElementDefWrapper, ElementWrapper,
},
error::{BodyError, ElementError, WebDynproError},
event::Event,
Expand Down Expand Up @@ -98,7 +99,7 @@ impl<'a> CourseGrades {
.unwrap();
let mut popup_iter = body.document().select(&popup_selector);
popup_iter.next().and_then(|elem| {
let elem_wrapped = ElementWrapper::dyn_elem(elem).ok()?;
let elem_wrapped = ElementWrapper::dyn_element(elem).ok()?;
if let ElementWrapper::PopupWindow(popup) = elem_wrapped {
popup.close().ok()
} else {
Expand Down Expand Up @@ -180,34 +181,6 @@ impl<'a> CourseGrades {
Ok(())
}

fn row_to_string(&'a self, row: &'a SapTableRow<'a>) -> Option<Vec<String>> {
if row.len() == 0
|| !row[0]
.clone()
.from_body(self.client.body())
.is_ok_and(|cell| !cell.is_empty_row())
{
return None;
};
let iter = row.iter_value(self.client.body());
Some(
iter.map(|val| {
match val {
Ok(cell) => match cell.content() {
Some(ElementWrapper::TextView(tv)) => Some(tv.text().to_owned()),
Some(ElementWrapper::Caption(cap)) => {
Some(cap.lsdata().text().unwrap_or(&String::default()).to_owned())
}
_ => None,
},
Err(_) => None,
}
.unwrap_or("".to_string())
})
.collect::<Vec<String>>(),
)
}

fn value_as_f32(field: InputField<'_>) -> Result<f32, WebDynproError> {
let Some(value) = field.lsdata().value() else {
return Err(ElementError::NoSuchData {
Expand Down Expand Up @@ -328,8 +301,8 @@ impl<'a> CourseGrades {
let table_elem = Self::GRADES_SUMMARY_TABLE.from_body(self.client.body())?;
let table = table_elem.table()?;
let ret = table
.iter()
.filter_map(|row| self.row_to_string(row))
.try_table_into::<Vec<String>>(self.client.body())?
.into_iter()
.filter_map(|values| {
if values.len() == 14 {
Some(SemesterGrade::new(
Expand Down Expand Up @@ -368,9 +341,9 @@ impl<'a> CourseGrades {
Ok(ret.collect())
}

async fn class_detail_in_popup<'f>(
async fn class_detail_in_popup(
&mut self,
open_button: ElementDef<'f, Button<'f>>,
open_button: ButtonDef,
) -> Result<HashMap<String, f32>, WebDynproError> {
self.client
.send(ButtonPressCommand::new(open_button))
Expand All @@ -383,17 +356,17 @@ impl<'a> CourseGrades {
let table_ref = table_inside_popup
.next()
.ok_or(BodyError::NoSuchElement("Table in popup".to_string()))?;
let table_elem: SapTable<'_> = ElementWrapper::dyn_elem(table_ref)?.try_into()?;
let zip = (|| Some(table_elem.table().ok()?.zip_header().next()?))()
.and_then(|(header, row)| {
let header = self.row_to_string(header)?;
let row = self.row_to_string(row)?;
Some(header.into_iter().zip(row.into_iter()))
})
let table_elem: SapTable<'_> = ElementWrapper::dyn_element(table_ref)?.try_into()?;
let table_body = table_elem.table()?;
let zip = table_body
.iter()
.next()
.ok_or(ElementError::InvalidContent {
element: table_elem.id().to_string(),
content: "header and first row".to_string(),
})?;
})?
.try_row_into::<Vec<(String, String)>>(table_body.header(), body)?
.into_iter();
zip.skip(4)
.map(|(key, val)| {
Ok((
Expand Down Expand Up @@ -460,29 +433,34 @@ impl<'a> CourseGrades {
self.select_semester(year, semester).await?;
let class_grades: Vec<(Option<String>, Vec<String>)> = {
let grade_table_elem = Self::GRADE_BY_CLASSES_TABLE.from_body(self.client.body())?;
let iter = grade_table_elem.table()?.iter();
let grade_table_body = grade_table_elem.table()?;
let iter = grade_table_body.iter();
iter.map(|row| {
let btn_id = row[4]
.clone()
.from_body(self.client.body())
.ok()
.and_then(|cell| {
if let Some(ElementWrapper::Button(btn)) = cell.content() {
if let Some(ElementDefWrapper::Button(btn)) = cell.content() {
Some(btn.id().to_owned())
} else {
None
}
});
(btn_id, row)
})
.filter_map(|(btn_id, row)| self.row_to_string(row).and_then(|row| Some((btn_id, row))))
.filter_map(|(btn_id, row)| {
row.try_row_into::<Vec<String>>(grade_table_body.header(), self.client.body())
.ok()
.and_then(|row| Some((btn_id, row)))
})
.collect()
};
let mut ret: Vec<ClassGrade> = vec![];
for (btn_id, values) in class_grades {
let detail: Option<HashMap<String, f32>> = if let Some(btn_id) = btn_id {
if include_details {
let btn_def = ElementDef::<Button<'_>>::new_dynamic(btn_id);
let btn_def = ButtonDef::new_dynamic(btn_id);
Some(self.class_detail_in_popup(btn_def).await?)
} else {
None
Expand Down Expand Up @@ -545,8 +523,10 @@ impl<'a> CourseGrades {
.iter()
.find(|row| match row[8].clone().from_body(self.client.body()) {
Ok(cell) => {
if let Some(ElementWrapper::TextView(code_elem)) = cell.content() {
code_elem.text() == code
if let Some(ElementDefWrapper::TextView(code_elem)) = cell.content() {
code_elem
.from_body(self.client.body())
.is_ok_and(|elem| elem.text() == code)
} else {
false
}
Expand All @@ -555,10 +535,8 @@ impl<'a> CourseGrades {
})
.and_then(|row| match row[4].clone().from_body(self.client.body()) {
Ok(cell) => {
if let Some(ElementWrapper::Button(btn)) = cell.content() {
Some(ElementDef::<'_, Button<'_>>::new_dynamic(
btn.id().to_owned(),
))
if let Some(ElementDefWrapper::Button(btn)) = cell.content() {
Some(ButtonDef::new_dynamic(btn.id().to_owned()))
} else {
None
}
Expand Down
10 changes: 4 additions & 6 deletions src/application/course_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
model::SemesterType,
webdynpro::{
client::body::Body,
element::{action::Button, complex::SapTable, layout::TabStrip, selection::ComboBox},
element::{action::Button, complex::SapTable, definition::ElementDefinition, layout::TabStrip, selection::ComboBox},
error::WebDynproError,
},
RusaintError,
Expand Down Expand Up @@ -118,9 +118,7 @@ mod test {
use crate::{
application::{course_schedule::CourseSchedule, USaintClientBuilder},
webdynpro::element::{
complex::sap_table::cell::{SapTableCell, SapTableCellWrapper},
selection::list_box::{item::ListBoxItemWrapper, ListBoxWrapper},
ElementWrapper,
complex::sap_table::cell::{SapTableCell, SapTableCellWrapper}, definition::ElementDefinition, selection::list_box::{item::ListBoxItemWrapper, ListBoxWrapper}, ElementWrapper
},
};

Expand All @@ -132,7 +130,7 @@ mod test {
.unwrap();
let ct_selector = scraper::Selector::parse("[ct]").unwrap();
for elem_ref in app.body().document().select(&ct_selector) {
let elem = ElementWrapper::dyn_elem(elem_ref);
let elem = ElementWrapper::dyn_element(elem_ref);
if let Ok(elem) = elem {
println!("{:?}", elem);
}
Expand Down Expand Up @@ -176,7 +174,7 @@ mod test {
app.load_edu().await.unwrap();
let table = app.read_edu_raw().unwrap();
if let Ok(table) = table.table() {
for row in table.with_header() {
for row in table.iter() {
print!("row: ");
for col in row.iter_value(app.body()) {
match col {
Expand Down
1 change: 1 addition & 0 deletions src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
command::WebDynproCommand,
element::{
define_elements,
definition::ElementDefinition,
system::{ClientInspector, Custom, CustomClientInfo, LoadingPlaceholder},
},
error::WebDynproError,
Expand Down
2 changes: 1 addition & 1 deletion src/application/student_information.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ mod test {
.unwrap();
let ct_selector = scraper::Selector::parse("[ct]").unwrap();
for elem_ref in app.body().document().select(&ct_selector) {
let elem = ElementWrapper::dyn_elem(elem_ref);
let elem = ElementWrapper::dyn_element(elem_ref);
if let Ok(elem) = elem {
println!("{:?}", elem);
}
Expand Down
2 changes: 1 addition & 1 deletion src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub async fn obtain_ssu_sso_token(id: &str, password: &str) -> Result<String, Ss

#[cfg(test)]
mod test {
use crate::session::{obtain_ssu_sso_token, USaintSession};
use crate::session::USaintSession;
use dotenv::dotenv;

#[tokio::test]
Expand Down
1 change: 1 addition & 0 deletions src/webdynpro/client/body/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type BodyUpdateControlId = String;

#[derive(Debug)]
pub(super) enum BodyUpdateType {
#[allow(dead_code)]
Full(BodyUpdateWindowId, BodyUpdateContentId, String),
Delta(BodyUpdateWindowId, HashMap<BodyUpdateControlId, String>),
}
Expand Down
3 changes: 2 additions & 1 deletion src/webdynpro/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use reqwest::{cookie::Jar, header::*, RequestBuilder};
use std::sync::Arc;
use url::Url;

use super::command::WebDynproCommand;
use super::{command::WebDynproCommand, element::definition::ElementDefinition};

/// WebDynpro 애플리케이션의 웹 요청 및 페이지 문서 처리를 담당하는 클라이언트
pub struct WebDynproClient {
Expand Down Expand Up @@ -111,6 +111,7 @@ impl<'a> WebDynproClient {
command.dispatch(self).await
}

#[allow(dead_code)]
/// 특정 WebDynpro 애플리케이션으로 탐색합니다.
pub(crate) async fn navigate(&mut self, base_url: &Url, name: &str) -> Result<(), ClientError> {
let raw_body = self
Expand Down
14 changes: 8 additions & 6 deletions src/webdynpro/command/element/action.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
use crate::webdynpro::{
client::EventProcessResult, command::WebDynproCommand, element::{action::Button, definition::ElementDef},
client::EventProcessResult,
command::WebDynproCommand,
element::{action::ButtonDef, definition::ElementDefinition},
error::WebDynproError,
};

/// 주어진 [`Button`]을 누름
pub struct ButtonPressCommand<'a> {
element_def: ElementDef<'a, Button<'a>>,
pub struct ButtonPressCommand {
element_def: ButtonDef,
}

impl<'a> ButtonPressCommand<'a> {
impl ButtonPressCommand {
/// 새로운 명령 객체를 생성합니다.
pub fn new(element_def: ElementDef<'a, Button<'a>>) -> ButtonPressCommand<'a> {
pub fn new(element_def: ButtonDef) -> ButtonPressCommand {
ButtonPressCommand { element_def }
}
}

impl<'a> WebDynproCommand for ButtonPressCommand<'a> {
impl WebDynproCommand for ButtonPressCommand {
type Result = EventProcessResult;

async fn dispatch(
Expand Down
39 changes: 20 additions & 19 deletions src/webdynpro/command/element/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@ use crate::webdynpro::{
client::EventProcessResult,
command::WebDynproCommand,
element::{
selection::{ComboBox, ComboBoxLSData},
Element, definition::ElementDef,
definition::ElementDefinition,
selection::{ComboBoxDef, ComboBoxLSData},
Element,
},
error::WebDynproError,
};

/// [`ComboBox`]의 선택지를 선택하도록 함
pub struct ComboBoxSelectCommand<'a> {
element_def: ElementDef<'a, ComboBox<'a>>,
pub struct ComboBoxSelectCommand {
element_def: ComboBoxDef,
key: String,
by_enter: bool,
}

impl<'a> ComboBoxSelectCommand<'a> {
/// 새로운 명령 객체를 생성합니다.
pub fn new(element_def: ElementDef<'a, ComboBox<'a>>, key: &str, by_enter: bool) -> ComboBoxSelectCommand<'a> {
Self {
element_def,
key: key.to_string(),
by_enter
impl ComboBoxSelectCommand {
/// 새로운 명령 객체를 생성합니다.
pub fn new(element_def: ComboBoxDef, key: &str, by_enter: bool) -> ComboBoxSelectCommand {
Self {
element_def,
key: key.to_string(),
by_enter,
}
}
}
}

impl<'a> WebDynproCommand for ComboBoxSelectCommand<'a> {
impl WebDynproCommand for ComboBoxSelectCommand {
type Result = EventProcessResult;

async fn dispatch(
Expand All @@ -42,18 +43,18 @@ impl<'a> WebDynproCommand for ComboBoxSelectCommand<'a> {
}

/// [`ComboBoxLSData`]를 반환
pub struct ReadComboBoxLSDataCommand<'a> {
element_def: ElementDef<'a, ComboBox<'a>>,
pub struct ReadComboBoxLSDataCommand {
element_def: ComboBoxDef,
}

impl<'a> ReadComboBoxLSDataCommand<'a> {
/// 새로운 명령 객체를 생성합니다.
pub fn new(element_def: ElementDef<'a, ComboBox<'a>>) -> ReadComboBoxLSDataCommand<'a> {
impl ReadComboBoxLSDataCommand {
/// 새로운 명령 객체를 생성합니다.
pub fn new(element_def: ComboBoxDef) -> ReadComboBoxLSDataCommand {
Self { element_def }
}
}

impl<'a> WebDynproCommand for ReadComboBoxLSDataCommand<'a> {
impl WebDynproCommand for ReadComboBoxLSDataCommand {
type Result = ComboBoxLSData;

async fn dispatch(
Expand Down
2 changes: 2 additions & 0 deletions src/webdynpro/element/action/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub mod property {
define_element_interactable! {
#[doc = "누를 수 있는 버튼"]
Button<"B", "Button"> {},
#[doc = "[`Button`]의 정의"]
ButtonDef,
#[doc = "[`Button`]의 내부 데이터"]
ButtonLSData {
text: String => "0",
Expand Down
2 changes: 2 additions & 0 deletions src/webdynpro/element/action/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::webdynpro::{
define_element_interactable! {
#[doc = "액션을 수행하거나 링크로 이동하는 하이퍼링크"]
Link<"LN", "Link"> {},
#[doc = "[`Link`]의 정의"]
LinkDef,
#[doc ="[`Link`] 내부 데이터"]
LinkLSData {
tooltip: String => "0",
Expand Down
Loading

0 comments on commit 10597d6

Please sign in to comment.