summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rustfmt.toml3
-rw-r--r--src/iceportal.rs109
-rw-r--r--src/lib.rs8
-rw-r--r--src/main.rs433
-rw-r--r--src/serde.rs46
-rw-r--r--src/travelynx.rs86
-rw-r--r--src/types.rs260
7 files changed, 488 insertions, 457 deletions
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..fb47ac3
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,3 @@
+max_width = 80
+tab_spaces = 2
+trailing_comma = "never"
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 <= &current_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 <= &current_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)
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index e1ced8a..4172b8a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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())
+ )
}
+ }
}