diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/iceportal.rs | 109 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/main.rs | 433 | ||||
-rw-r--r-- | src/serde.rs | 46 | ||||
-rw-r--r-- | src/travelynx.rs | 86 | ||||
-rw-r--r-- | src/types.rs | 260 |
6 files changed, 485 insertions, 457 deletions
diff --git a/src/iceportal.rs b/src/iceportal.rs index 0d2076e..fb5bca8 100644 --- a/src/iceportal.rs +++ b/src/iceportal.rs @@ -2,99 +2,98 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use serde::Deserialize; use serde_json::Value; -use crate::{travelynx::TrainRef, types::IsStation, serde::*}; +use crate::{serde::*, travelynx::TrainRef, types::IsStation}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct TripInfo { - trip: Trip, - connection: Option<Value>, - selected_route: Option<Value>, - active: Option<Value> + trip: Trip, + connection: Option<Value>, + selected_route: Option<Value>, + active: Option<Value> } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct Trip { - train_type: String, - vzn: String, // train number - // some position info here - actual_position: u64, // distance along track, presumably - stops: Vec<Stop> + train_type: String, + vzn: String, // train number + // some position info here + actual_position: u64, // distance along track, presumably + stops: Vec<Stop> } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Stop { - info: StopInfo, - station: Station, - timetable: Timetable + info: StopInfo, + station: Station, + timetable: Timetable } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct StopInfo { - distance_from_start: u64, - position_status: String // one of "departed", "future", ... ? + distance_from_start: u64, + position_status: String // one of "departed", "future", ... ? } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct Station { - eva_nr: String, - name: String + eva_nr: String, + name: String } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct Timetable { - #[serde(deserialize_with = "option_naive_read_unixtime_db")] - scheduled_arrival_time: Option<DateTime<Utc>>, - #[serde(deserialize_with = "option_naive_read_unixtime_db")] - actual_arrival_time: Option<DateTime<Utc>> + #[serde(deserialize_with = "option_naive_read_unixtime_db")] + scheduled_arrival_time: Option<DateTime<Utc>>, + #[serde(deserialize_with = "option_naive_read_unixtime_db")] + actual_arrival_time: Option<DateTime<Utc>> } impl IsStation for Stop { - fn name (&self) -> &str { - &self.station.name - } + fn name(&self) -> &str { + &self.station.name + } - fn scheduled_arrival (&self) -> Option<&chrono::DateTime<Utc>> { - self.timetable.scheduled_arrival_time.as_ref() - } + fn scheduled_arrival(&self) -> Option<&chrono::DateTime<Utc>> { + self.timetable.scheduled_arrival_time.as_ref() + } - fn real_arrival (&self) -> Option<&chrono::DateTime<Utc>> { - self.timetable.scheduled_arrival_time.as_ref() - } + fn real_arrival(&self) -> Option<&chrono::DateTime<Utc>> { + self.timetable.scheduled_arrival_time.as_ref() + } - fn ds100 (&self) -> &str { - "??" - } + fn ds100(&self) -> &str { + "??" + } } - impl TripInfo { - - pub fn guess_last_station (&self) -> Option<String> { - let current_pos = self.trip.actual_position; - self.trip - .stops - .iter() - .rev() - .map(|stop| (stop.info.distance_from_start, stop)) - .filter(|(dist,_)| dist <= ¤t_pos) - .next() - .map(|(_,stop)| stop.station.name.clone()) - } - - pub fn get_train_ref (&self) -> TrainRef { - TrainRef { - _type: self.trip.train_type.clone(), - no: self.trip.vzn.clone() - } + pub fn guess_last_station(&self) -> Option<String> { + let current_pos = self.trip.actual_position; + self + .trip + .stops + .iter() + .rev() + .map(|stop| (stop.info.distance_from_start, stop)) + .filter(|(dist, _)| dist <= ¤t_pos) + .next() + .map(|(_, stop)| stop.station.name.clone()) + } + + pub fn get_train_ref(&self) -> TrainRef { + TrainRef { + _type: self.trip.train_type.clone(), + no: self.trip.vzn.clone() } + } - pub fn trip (&self) -> crate::types::Trip<'_,Stop> { - crate::types::Trip(&self.trip.stops) - } + pub fn trip(&self) -> crate::types::Trip<'_, Stop> { + crate::types::Trip(&self.trip.stops) + } } @@ -1,6 +1,4 @@ - - -pub mod types; -pub mod travelynx; pub mod iceportal; -pub (crate) mod serde; +pub(crate) mod serde; +pub mod travelynx; +pub mod types; diff --git a/src/main.rs b/src/main.rs index 356db21..eacbeb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,249 +7,264 @@ use traveltext::{iceportal::*, travelynx::*}; #[derive(Parser)] struct Cli { - #[clap(subcommand)] - command: Command, - /// print requests that couldn't be parsed to help debugging - #[clap(long)] - debug: bool, - #[clap(default_value = "https://travelynx.de")] - baseurl: String, - /// API token to use in requests - token: Option<String>, + #[clap(subcommand)] + command: Command, + /// print requests that couldn't be parsed to help debugging + #[clap(long)] + debug: bool, + #[clap(default_value = "https://travelynx.de")] + baseurl: String, + /// API token to use in requests + token: Option<String> } #[derive(Subcommand)] enum Command { - /// Get current travelynx status - Status, - /// Check in to a train using travelynx - Checkin { - from: String, - to: String, - // TODO: make this optional and guess which train if not given - #[clap(flatten)] - train: TrainRef, - }, - /// (If already checked in) change the trip's destination - Destination { - to: String - }, - Arewethereyet, - /// If iceportal.de is available, ask it which train we're in and - /// check in - Autocheckin, - /// Undo the last checkin (if any). - Undo, - /// Query iceportal.de (for testing) - ICEPortal, + /// Get current travelynx status + Status, + /// Check in to a train using travelynx + Checkin { + from: String, + to: String, + // TODO: make this optional and guess which train if not given + #[clap(flatten)] + train: TrainRef + }, + /// (If already checked in) change the trip's destination + Destination { + to: String + }, + Arewethereyet, + /// If iceportal.de is available, ask it which train we're in and + /// check in + Autocheckin, + /// Undo the last checkin (if any). + Undo, + /// Query iceportal.de (for testing) + ICEPortal } #[derive(Deserialize)] struct Config { - token_status: String, - token_travel: String, + token_status: String, + token_travel: String } fn main() -> Result<(), ureq::Error> { - let cli = Cli::parse(); - - let traveltext = format!( - "{}{}el{}{}", - "tr".cyan(), - "av".bright_magenta(), - "te".bright_magenta(), - "xt".cyan() - ); - - let configpath = { - let mut path = dirs::config_dir().unwrap(); - path.push("traveltext.toml"); - path - }; - - let config: Config = match std::fs::read_to_string(&configpath) { - Ok(text) => match toml::from_str(&text) { - Ok(config) => config, - Err(err) => exit_err( - &err.to_string(), - &format!( - "failed parsing config file {}", - configpath.to_string_lossy() - ), + let cli = Cli::parse(); + + let traveltext = format!( + "{}{}el{}{}", + "tr".cyan(), + "av".bright_magenta(), + "te".bright_magenta(), + "xt".cyan() + ); + + let configpath = { + let mut path = dirs::config_dir().unwrap(); + path.push("traveltext.toml"); + path + }; + + let config: Config = match std::fs::read_to_string(&configpath) { + Ok(text) => match toml::from_str(&text) { + Ok(config) => config, + Err(err) => exit_err( + &err.to_string(), + &format!( + "failed parsing config file {}", + configpath.to_string_lossy() + ) + ) + }, + Err(err) => exit_err( + &err.to_string(), + &format!("failed reading config at: {}", configpath.to_string_lossy()) + ) + }; + + match cli.command { + Command::Status => { + let status: Status = exiting_get_request( + &format!("{}/api/v1/status/{}", cli.baseurl, config.token_status), + cli.debug + ); + + println!("{}: {}", traveltext, status); + } + Command::Arewethereyet => { + let status: Status = exiting_get_request( + &format!("{}/api/v1/status/{}", cli.baseurl, config.token_status), + cli.debug + ); + + match status.to_station { + None => println!("{}: Fahrt ins {}", traveltext, "Blaue".blue()), + Some(to) => { + let now = chrono::Utc::now(); + let duration = to.real_arrival().map(|dt| *dt - now); + match duration { + Some(d) => println!( + "{}: we'll be there in {} minutes", + traveltext, + d.num_minutes() ), - }, - Err(err) => exit_err( - &err.to_string(), - &format!("failed reading config at: {}", configpath.to_string_lossy()), - ), - }; - - match cli.command { - Command::Status => { - let status: Status = exiting_get_request( - &format!("{}/api/v1/status/{}", cli.baseurl, config.token_status), - cli.debug, - ); - - println!("{}: {}", traveltext, status); + None => println!("{}: I have no idea", traveltext) + } } - Command::Arewethereyet => { - let status: Status = exiting_get_request( - &format!("{}/api/v1/status/{}", cli.baseurl, config.token_status), - cli.debug, - ); - - match status.to_station { - None => println!("{}: Fahrt ins {}", traveltext, "Blaue".blue()), - Some(to) => { - let now = chrono::Utc::now(); - let duration = to.real_arrival().map(|dt| *dt - now); - match duration { - Some (d) => println!( - "{}: we'll be there in {} minutes", - traveltext, - d.num_minutes() - ), - None => println!("{}: I have no idea", traveltext) - } - } - } + } + } + Command::Checkin { from, to, train } => { + let resp: Response = exiting_post_request( + &format!("{}/api/v1/travel", cli.baseurl), + Action::CheckIn { + train, + from_station: from, + to_station: Some(to), + comment: None, + token: format!("{}", config.token_travel) }, - Command::Checkin { from, to, train } => { - let resp: Response = exiting_post_request( - &format!("{}/api/v1/travel", cli.baseurl), - Action::CheckIn { - train, - from_station: from, - to_station: Some(to), - comment: None, - token: format!("{}", config.token_travel), - }, - cli.debug, - ); - - println!("{}: {}", traveltext, resp); + cli.debug + ); + + println!("{}: {}", traveltext, resp); + } + Command::Destination { to } => { + let resp: Response = exiting_post_request( + &format!("{}/api/v1/travel", cli.baseurl), + Action::CheckOut { + to_station: to, + force: false, + token: config.token_travel, + comment: None }, - Command::Destination { to } => { - let resp: Response = exiting_post_request( - &format!("{}/api/v1/travel", cli.baseurl), - Action::CheckOut { - to_station: to, - force: false, - token: config.token_travel, - comment: None - }, - cli.debug - ); - println!("{}: {}", traveltext, resp); - } - Command::Undo => { - let resp: Response = exiting_post_request( - &format!("{}/api/v1/travel", cli.baseurl), - Action::Undo { - token: config.token_travel.to_owned(), - }, - cli.debug, - ); - - println!("{}: {}", traveltext, resp); - } - Command::Autocheckin => { - let iceportal: TripInfo = - exiting_get_request("https://iceportal.de/api1/rs/tripInfo/trip", cli.debug); - - let last_stop = iceportal.guess_last_station().unwrap(); - let train = iceportal.get_train_ref(); - println!( - "{}: guessing you got onto {} {} in {}, checking in …", - traveltext, train._type, train.no, last_stop - ); - let resp: Response = exiting_post_request( - &format!("{}/api/v1/travel", cli.baseurl), - Action::CheckIn { - train, - from_station: last_stop, - to_station: None, - comment: None, - token: format!("{}", config.token_travel), - }, - cli.debug, - ); - - // eprintln!("{:?}", resp); - println!("{}: {}", traveltext, resp); + cli.debug + ); + println!("{}: {}", traveltext, resp); + } + Command::Undo => { + let resp: Response = exiting_post_request( + &format!("{}/api/v1/travel", cli.baseurl), + Action::Undo { + token: config.token_travel.to_owned() + }, + cli.debug + ); + + println!("{}: {}", traveltext, resp); + } + Command::Autocheckin => { + let iceportal: TripInfo = exiting_get_request( + "https://iceportal.de/api1/rs/tripInfo/trip", + cli.debug + ); + + let last_stop = iceportal.guess_last_station().unwrap(); + let train = iceportal.get_train_ref(); + println!( + "{}: guessing you got onto {} {} in {}, checking in …", + traveltext, train._type, train.no, last_stop + ); + let resp: Response = exiting_post_request( + &format!("{}/api/v1/travel", cli.baseurl), + Action::CheckIn { + train, + from_station: last_stop, + to_station: None, + comment: None, + token: format!("{}", config.token_travel) + }, + cli.debug + ); + + // eprintln!("{:?}", resp); + println!("{}: {}", traveltext, resp); + } + Command::ICEPortal => { + match get_request::<TripInfo>( + "https://iceportal.de/api1/rs/tripInfo/trip" + ) { + Ok(resp) => { + println!( + "{}: Currently in {}\n", + traveltext, + resp.get_train_ref().to_string().green() + ); + println!("guessing last stop was: {:?}\n", resp.guess_last_station()); + println!("Stops:\n{}", resp.trip()) } - Command::ICEPortal => { - match get_request::<TripInfo>("https://iceportal.de/api1/rs/tripInfo/trip") { - Ok(resp) => { - println!("{}: Currently in {}\n", traveltext, resp.get_train_ref().to_string().green()); - println!("guessing last stop was: {:?}\n", resp.guess_last_station()); - println!("Stops:\n{}", resp.trip()) - } - Err(err) => { - if cli.debug { - eprintln!("{:?}", err); - } - println!("either this tool or the iceportal broke or you're not actually on an ICE\n\ + Err(err) => { + if cli.debug { + eprintln!("{:?}", err); + } + println!("either this tool or the iceportal broke or you're not actually on an ICE\n\ (get a response but couldn't parse it)"); - } - } } + } } - Ok(()) + } + Ok(()) } fn get_request<R>(uri: &str) -> Result<R, (serde_json::Error, String)> where - R: serde::de::DeserializeOwned, + R: serde::de::DeserializeOwned { - let resp: String = ureq::get(uri) - .call() - .unwrap_or_else(|err| exit_err(&err.to_string(), "get request failed")) - .into_string() - .unwrap_or_else(|err| exit_err(&err.to_string(), "get request response failed")); - - match serde_json::from_str::<R>(&resp) { - Ok(obj) => Ok(obj), - Err(err) => Err((err, resp)), - } + let resp: String = ureq::get(uri) + .call() + .unwrap_or_else(|err| exit_err(&err.to_string(), "get request failed")) + .into_string() + .unwrap_or_else(|err| { + exit_err(&err.to_string(), "get request response failed") + }); + + match serde_json::from_str::<R>(&resp) { + Ok(obj) => Ok(obj), + Err(err) => Err((err, resp)) + } } -fn exiting_get_request<R: serde::de::DeserializeOwned>(uri: &str, debug: bool) -> R { - match get_request(uri) { - Ok(obj) => obj, - Err((err, resp)) => { - if debug { - eprintln!("DEBUG: {}", resp); - } - exit_err(&err.to_string(), "parsing response failed") - } +fn exiting_get_request<R: serde::de::DeserializeOwned>( + uri: &str, + debug: bool +) -> R { + match get_request(uri) { + Ok(obj) => obj, + Err((err, resp)) => { + if debug { + eprintln!("DEBUG: {}", resp); + } + exit_err(&err.to_string(), "parsing response failed") } + } } fn exiting_post_request<R, P>(uri: &str, payload: P, debug: bool) -> R where - P: serde::Serialize, - R: serde::de::DeserializeOwned, + P: serde::Serialize, + R: serde::de::DeserializeOwned { - let resp: String = ureq::post(uri) - .send_json(payload) - .unwrap_or_else(|err| exit_err(&err.to_string(), "post request failed")) - .into_string() - .unwrap_or_else(|err| exit_err(&err.to_string(), "post request response failed")); - - match serde_json::from_str::<R>(&resp) { - Ok(obj) => obj, - Err(err) => { - if debug { - eprintln!("DEBUG: {}", resp); - } - exit_err(&err.to_string(), "parsing response failed") - } + let resp: String = ureq::post(uri) + .send_json(payload) + .unwrap_or_else(|err| exit_err(&err.to_string(), "post request failed")) + .into_string() + .unwrap_or_else(|err| { + exit_err(&err.to_string(), "post request response failed") + }); + + match serde_json::from_str::<R>(&resp) { + Ok(obj) => obj, + Err(err) => { + if debug { + eprintln!("DEBUG: {}", resp); + } + exit_err(&err.to_string(), "parsing response failed") } + } } fn exit_err(msg: &str, scope: &str) -> ! { - eprintln!("{}: {}", scope, msg); - std::process::exit(1) + eprintln!("{}: {}", scope, msg); + std::process::exit(1) } diff --git a/src/serde.rs b/src/serde.rs index 486929a..13ef8bc 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,33 +1,43 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use serde::{Deserialize, Deserializer}; - pub fn naive_read_unixtime<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error> where - D: Deserializer<'de>, + D: Deserializer<'de> { - let ts = <i64>::deserialize(d)?; - Ok(DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc)) + let ts = <i64>::deserialize(d)?; + Ok(DateTime::<Utc>::from_utc( + NaiveDateTime::from_timestamp(ts, 0), + Utc + )) } -pub fn option_naive_read_unixtime<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error> +pub fn option_naive_read_unixtime<'de, D>( + d: D +) -> Result<Option<DateTime<Utc>>, D::Error> where - D: Deserializer<'de>, + D: Deserializer<'de> { - match <i64>::deserialize(d) { - Ok(ts) => - Ok(Some(DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc))), - Err(_) => Ok(None) - } + match <i64>::deserialize(d) { + Ok(ts) => Ok(Some(DateTime::<Utc>::from_utc( + NaiveDateTime::from_timestamp(ts, 0), + Utc + ))), + Err(_) => Ok(None) + } } -pub fn option_naive_read_unixtime_db<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error> +pub fn option_naive_read_unixtime_db<'de, D>( + d: D +) -> Result<Option<DateTime<Utc>>, D::Error> where - D: Deserializer<'de>, + D: Deserializer<'de> { - match <i64>::deserialize(d) { - Ok(ts) => - Ok(Some(DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(ts/1000, 0), Utc))), - Err(_) => Ok(None) - } + match <i64>::deserialize(d) { + Ok(ts) => Ok(Some(DateTime::<Utc>::from_utc( + NaiveDateTime::from_timestamp(ts / 1000, 0), + Utc + ))), + Err(_) => Ok(None) + } } diff --git a/src/travelynx.rs b/src/travelynx.rs index a6c59e1..c5351eb 100644 --- a/src/travelynx.rs +++ b/src/travelynx.rs @@ -1,72 +1,74 @@ use clap::Args; -use serde::{Serialize, Deserialize}; use colored::*; +use serde::{Deserialize, Serialize}; use crate::types::Status; #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct Travel { - token: String, - #[serde(flatten)] - action: Action, + token: String, + #[serde(flatten)] + action: Action } #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] #[serde(tag = "action")] pub enum Action { - #[serde(rename = "checkin")] - #[serde(rename_all = "camelCase")] - CheckIn { - token: String, - train: TrainRef, - from_station: String, - #[serde(skip_serializing_if = "Option::is_none")] - to_station: Option<String>, - #[serde(skip_serializing_if = "Option::is_none")] - comment: Option<String>, - }, - #[serde(rename = "checkout")] - #[serde(rename_all = "camelCase")] - CheckOut { - to_station: String, - force: bool, - #[serde(skip_serializing_if = "Option::is_none")] - comment: Option<String>, - token: String - }, - Undo {token: String}, + #[serde(rename = "checkin")] + #[serde(rename_all = "camelCase")] + CheckIn { + token: String, + train: TrainRef, + from_station: String, + #[serde(skip_serializing_if = "Option::is_none")] + to_station: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + comment: Option<String> + }, + #[serde(rename = "checkout")] + #[serde(rename_all = "camelCase")] + CheckOut { + to_station: String, + force: bool, + #[serde(skip_serializing_if = "Option::is_none")] + comment: Option<String>, + token: String + }, + Undo { + token: String + } } #[derive(Args, Serialize, Debug)] pub struct TrainRef { - #[clap(name = "TRAIN TYPE")] - #[serde(rename = "type")] - pub _type: String, - #[clap(name = "NUMBER")] - pub no: String, + #[clap(name = "TRAIN TYPE")] + #[serde(rename = "type")] + pub _type: String, + #[clap(name = "NUMBER")] + pub no: String } impl std::fmt::Display for TrainRef { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} {}", self._type, self.no) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self._type, self.no) + } } #[derive(Deserialize, Debug)] pub struct Response { - success: Option<bool>, - deprecated: bool, - status: Status, - error: Option<String> + success: Option<bool>, + deprecated: bool, + status: Status, + error: Option<String> } impl std::fmt::Display for Response { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.error { - Some(msg) => write!(f, "{}", msg.red()), - None => write!(f, "{}\n\n{}", "Success!".green(), self.status) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.error { + Some(msg) => write!(f, "{}", msg.red()), + None => write!(f, "{}\n\n{}", "Success!".green(), self.status) } + } } diff --git a/src/types.rs b/src/types.rs index f4947e2..22ba077 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,181 +8,185 @@ use crate::serde::*; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Station { - name: String, - ds100: String, - uic: u64, - latitude: f64, - longitude: f64, - #[serde(deserialize_with = "naive_read_unixtime")] - scheduled_time: DateTime<Utc>, - #[serde(deserialize_with = "naive_read_unixtime")] - real_time: DateTime<Utc>, + name: String, + ds100: String, + uic: u64, + latitude: f64, + longitude: f64, + #[serde(deserialize_with = "naive_read_unixtime")] + scheduled_time: DateTime<Utc>, + #[serde(deserialize_with = "naive_read_unixtime")] + real_time: DateTime<Utc> } pub fn parse_optional_station<'de, D>(d: D) -> Result<Option<Station>, D::Error> where - D: Deserializer<'de>, + D: Deserializer<'de> { - let val = <serde_json::Value>::deserialize(d)?; - match serde_json::from_value(val) { - Ok(station) => Ok(Some(station)), - Err(_) => Ok(None), - } + let val = <serde_json::Value>::deserialize(d)?; + match serde_json::from_value(val) { + Ok(station) => Ok(Some(station)), + Err(_) => Ok(None) + } } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Stop { - name: String, - #[serde(deserialize_with = "option_naive_read_unixtime")] - scheduled_arrival: Option<DateTime<Utc>>, - #[serde(deserialize_with = "option_naive_read_unixtime")] - real_arrival: Option<DateTime<Utc>>, - #[serde(deserialize_with = "option_naive_read_unixtime")] - scheduled_departure: Option<DateTime<Utc>>, - #[serde(deserialize_with = "option_naive_read_unixtime")] - real_departure: Option<DateTime<Utc>>, + name: String, + #[serde(deserialize_with = "option_naive_read_unixtime")] + scheduled_arrival: Option<DateTime<Utc>>, + #[serde(deserialize_with = "option_naive_read_unixtime")] + real_arrival: Option<DateTime<Utc>>, + #[serde(deserialize_with = "option_naive_read_unixtime")] + scheduled_departure: Option<DateTime<Utc>>, + #[serde(deserialize_with = "option_naive_read_unixtime")] + real_departure: Option<DateTime<Utc>> } pub trait IsStation { - fn name(&self) -> &str; - fn scheduled_arrival(&self) -> Option<&DateTime<Utc>>; - fn real_arrival(&self) -> Option<&DateTime<Utc>>; - fn ds100(&self) -> &str; - - fn to_fancy_string(&self) -> String { - format!( - "{} {} – {} ({})", - self.real_arrival() // chrono's API for timezones is expressive, but reads like c++ … - .map(|t| <DateTime<Local>>::from(*t).time().to_string()) - .unwrap_or("??:??:??".to_string()) - .blue(), - { - let delay = match (self.real_arrival(), self.scheduled_arrival()) { - (Some(a), Some(s)) => (a.time() - s.time()).num_minutes(), - _ => 0, - }; - let text = format!("({:+})", delay); - if delay > 0 { - text.red() - } else { - text.green() - } - }, - self.ds100().red(), - self.name() - ) - } + fn name(&self) -> &str; + fn scheduled_arrival(&self) -> Option<&DateTime<Utc>>; + fn real_arrival(&self) -> Option<&DateTime<Utc>>; + fn ds100(&self) -> &str; + + fn to_fancy_string(&self) -> String { + format!( + "{} {} – {} ({})", + self + .real_arrival() // chrono's API for timezones is expressive, but reads like c++ … + .map(|t| <DateTime<Local>>::from(*t).time().to_string()) + .unwrap_or("??:??:??".to_string()) + .blue(), + { + let delay = match (self.real_arrival(), self.scheduled_arrival()) { + (Some(a), Some(s)) => (a.time() - s.time()).num_minutes(), + _ => 0 + }; + let text = format!("({:+})", delay); + if delay > 0 { + text.red() + } else { + text.green() + } + }, + self.ds100().red(), + self.name() + ) + } } impl IsStation for Station { - fn name(&self) -> &str { - &self.name - } - fn scheduled_arrival(&self) -> Option<&DateTime<Utc>> { - Some(&self.scheduled_time) - } - fn real_arrival(&self) -> Option<&DateTime<Utc>> { - Some(&self.real_time) - } - - fn ds100(&self) -> &str { - &self.ds100 - } + fn name(&self) -> &str { + &self.name + } + fn scheduled_arrival(&self) -> Option<&DateTime<Utc>> { + Some(&self.scheduled_time) + } + fn real_arrival(&self) -> Option<&DateTime<Utc>> { + Some(&self.real_time) + } + + fn ds100(&self) -> &str { + &self.ds100 + } } impl IsStation for Stop { - fn name(&self) -> &str { - &self.name - } - fn scheduled_arrival(&self) -> Option<&DateTime<Utc>> { - self.scheduled_arrival.as_ref() - } - fn real_arrival(&self) -> Option<&DateTime<Utc>> { - self.real_arrival.as_ref() - } - - fn ds100(&self) -> &str { - "[??]" - } + fn name(&self) -> &str { + &self.name + } + fn scheduled_arrival(&self) -> Option<&DateTime<Utc>> { + self.scheduled_arrival.as_ref() + } + fn real_arrival(&self) -> Option<&DateTime<Utc>> { + self.real_arrival.as_ref() + } + + fn ds100(&self) -> &str { + "[??]" + } } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Train { - #[serde(rename = "type")] - _type: String, - line: Option<String>, - no: String, - id: String, + #[serde(rename = "type")] + _type: String, + line: Option<String>, + no: String, + id: String } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Status { - deprecated: bool, - checked_in: bool, - from_station: Station, - #[serde(deserialize_with = "parse_optional_station")] - pub to_station: Option<Station>, - intermediate_stops: Vec<Stop>, - train: Option<Train>, - action_time: u64, + deprecated: bool, + checked_in: bool, + from_station: Station, + #[serde(deserialize_with = "parse_optional_station")] + pub to_station: Option<Station>, + intermediate_stops: Vec<Stop>, + train: Option<Train>, + action_time: u64 } #[allow(dead_code)] pub struct Ds100 { - inner: String, + inner: String } pub struct Trip<'a, S: IsStation>(pub &'a Vec<S>); impl std::fmt::Display for Train { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} {}", self._type, self.no) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self._type, self.no) + } } #[allow(unstable_name_collisions)] impl<S: IsStation> std::fmt::Display for Trip<'_, S> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.0.len() != 0 { - self.0 - .iter() - .map(|stop| stop.to_fancy_string()) - // .intersperse(" ↓".to_string()) - .for_each(|l| writeln!(f, " {}\n ↓", l).unwrap()); - } - Ok(()) + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.0.len() != 0 { + self + .0 + .iter() + .map(|stop| stop.to_fancy_string()) + // .intersperse(" ↓".to_string()) + .for_each(|l| writeln!(f, " {}\n ↓", l).unwrap()); } + Ok(()) + } } impl std::fmt::Display for Status { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.checked_in { - false => write!( - f, - "not checked in. \n\n\ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.checked_in { + false => write!( + f, + "not checked in. \n\n\ last trip: \n {} {}", - self.from_station.to_fancy_string(), - self.to_station.as_ref().unwrap().to_fancy_string() - ), - true => write!( - f, - "checked in to: {}.\n\n\ + self.from_station.to_fancy_string(), + self.to_station.as_ref().unwrap().to_fancy_string() + ), + true => write!( + f, + "checked in to: {}.\n\n\ stops:\n {}\n ↓\n{} {}", - self.train - .as_ref() - .map(|t| t.to_string()) - .unwrap_or("".to_string()) - .green(), - self.from_station.to_fancy_string(), - Trip(&self.intermediate_stops), - self.to_station - .as_ref() - .map(|s| s.to_fancy_string()) - .unwrap_or_else(|| "🚄 Fahrt ins Blaue".blue().to_string()) - ), - } + self + .train + .as_ref() + .map(|t| t.to_string()) + .unwrap_or("".to_string()) + .green(), + self.from_station.to_fancy_string(), + Trip(&self.intermediate_stops), + self + .to_station + .as_ref() + .map(|s| s.to_fancy_string()) + .unwrap_or_else(|| "🚄 Fahrt ins Blaue".blue().to_string()) + ) } + } } |