diff --git a/src/header_description.rs b/src/header_description.rs new file mode 100644 index 0000000..6830cf3 --- /dev/null +++ b/src/header_description.rs @@ -0,0 +1,51 @@ +use colored::Colorize; +use std::fmt::Display; + +use crate::DEFAULT_WIDTH; + +pub struct HeaderDescription<'a> { + header_description: [&'a str; 2], +} + +impl<'a> HeaderDescription<'a> { + pub fn new() -> Self { + Self { + header_description: ["EUR", /*"\u{2217}"*/ "ALL"], // Unicode is ∗ + } + } + + pub fn invert(&mut self) { + self.header_description.swap(0, 1); + } + + pub fn replace_eur(&mut self, currency: &'a str) { + self.header_description[0] = currency; + } +} + +impl<'a> Display for HeaderDescription<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let width = DEFAULT_WIDTH - 2; + let formatted = format!( + "{} {} {}", + self.header_description[0].purple().bold(), + "to".italic(), + self.header_description[1].purple().bold() + ); + let unformatted_len = + self.header_description[0].len() + self.header_description[1].len() + 4; + let left_padding = " ".repeat((width - unformatted_len) / 2); + + let vertical = "═".repeat(width); + writeln!(f, " ╔{}╗", &vertical)?; + writeln!( + f, + " {}{}{} ", + &left_padding, + formatted, + " ".repeat(width - left_padding.len() - unformatted_len) + )?; + writeln!(f, " ╚{}╝\n", &vertical)?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8c2f753..3476748 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod cache; pub mod cli; +mod header_description; mod holiday; pub mod models; pub mod os; @@ -7,9 +8,11 @@ pub mod parsing; pub mod table; pub mod utils_calc; +pub use header_description::HeaderDescription; pub use holiday::Hollidays; const APP_NAME: &'static str = "ECB-rates"; +const DEFAULT_WIDTH: usize = 20; pub mod ecb_url { pub const TODAY: &'static str = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"; diff --git a/src/main.rs b/src/main.rs index f82d92a..38be7f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use clap::Parser as _; use ecb_rates::cache::{Cache, CacheLine}; +use ecb_rates::HeaderDescription; use reqwest::{Client, IntoUrl}; use std::process::ExitCode; @@ -17,11 +18,12 @@ async fn get_and_parse(url: impl IntoUrl) -> anyhow::Result ExitCode { - let cli = Cli::parse(); + let mut cli = Cli::parse(); if cli.force_color { colored::control::set_override(true); } + let mut header_description = HeaderDescription::new(); let use_cache = !cli.no_cache; let mut cache = if use_cache { Cache::load() } else { None }; let cache_ok = cache.as_ref().map_or_else( @@ -72,7 +74,9 @@ async fn main() -> ExitCode { parsed }; - if let Some(currency) = cli.perspective.map(|s| s.to_uppercase()) { + cli.perspective = cli.perspective.map(|s| s.to_uppercase()); + if let Some(currency) = cli.perspective.as_ref() { + header_description.replace_eur(¤cy); let error_occured = change_perspective(&mut parsed, ¤cy).is_none(); if error_occured { eprintln!("The currency wasn't in the data from the ECB!"); @@ -82,6 +86,7 @@ async fn main() -> ExitCode { if cli.should_invert { invert_rates(&mut parsed); + header_description.invert(); } round(&mut parsed, cli.max_decimals); @@ -119,18 +124,23 @@ async fn main() -> ExitCode { }; to_string_json(&json_values).expect("Failed to parse content as JSON") } - FormatOption::Plain => parsed - .iter() - .map(|x| { - let mut t: TableRef = x.into(); - if cli.no_time { - t.disable_header(); - } - t.sort(&cli.sort_by); - t.to_string() - }) - .collect::>() - .join("\n"), + FormatOption::Plain => { + let rates = parsed + .iter() + .map(|x| { + let mut t: TableRef = x.into(); + if cli.no_time { + t.disable_header(); + } + t.sort(&cli.sort_by); + t.to_string() + }) + .collect::>() + .join("\n"); + let mut s = header_description.to_string(); + s.push_str(&rates); + s + } }; println!("{}", &output); diff --git a/src/table/table_display.rs b/src/table/table_display.rs index 25821f6..855486a 100644 --- a/src/table/table_display.rs +++ b/src/table/table_display.rs @@ -7,6 +7,7 @@ pub fn helper_table_print( table: &T, ) -> std::fmt::Result { let width = table.get_width(); + let left_offset = " ".repeat(table.get_left_offset()); if let Some(header) = table.get_header() { let middle_padding_amount = (width - header.len()) / 2; @@ -14,7 +15,8 @@ pub fn helper_table_print( let middle_padding = " ".repeat(middle_padding_amount); writeln!( f, - "{}{}{}", + "{}{}{}{}", + &left_offset, middle_padding, header.bold().cyan(), middle_padding @@ -27,19 +29,27 @@ pub fn helper_table_print( let right_padding = " ".repeat(right_padding_amount); writeln!( f, - "{}{}{}", + "{}{}{}{}", + &left_offset, column_left.bold().yellow(), right_padding, column_right.bold().yellow() )?; - writeln!(f, "{}", "-".repeat(width))?; + writeln!(f, "{}{}", &left_offset, "-".repeat(width))?; for (left, right) in table.get_rows().iter() { let left_str = left.as_ref(); let right_str = right.to_string(); let padding_amount = width.saturating_sub(left_str.len() + right_str.len()); let padding = " ".repeat(padding_amount); - writeln!(f, "{}{}{}", left_str.bold().green(), padding, right_str)?; + writeln!( + f, + "{}{}{}{}", + &left_offset, + left_str.bold().green(), + padding, + right_str + )?; } Ok(()) diff --git a/src/table/table_getter.rs b/src/table/table_getter.rs index 4751932..56a069c 100644 --- a/src/table/table_getter.rs +++ b/src/table/table_getter.rs @@ -7,4 +7,5 @@ pub trait TableGet { fn get_column_right(&self) -> &str; fn get_rows(&self) -> &Vec<(Self::RowLeftRef, f64)>; fn get_width(&self) -> usize; + fn get_left_offset(&self) -> usize; } diff --git a/src/table/table_owned.rs b/src/table/table_owned.rs index 34aa372..f4f143a 100644 --- a/src/table/table_owned.rs +++ b/src/table/table_owned.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use crate::cli::SortBy; use crate::models::ExchangeRateResult; +use crate::DEFAULT_WIDTH; use super::table_display::helper_table_print; use super::{TableGet, TableTrait}; @@ -13,6 +14,7 @@ pub struct Table { pub(super) rows: Vec<(String, f64)>, pub color: bool, pub width: usize, + pub left_offset: usize, } impl<'a> TableTrait<'a> for Table { @@ -32,7 +34,8 @@ impl<'a> TableTrait<'a> for Table { column_right, rows: Vec::new(), color: false, - width: 21, + width: DEFAULT_WIDTH, + left_offset: 1, } } @@ -74,6 +77,10 @@ impl TableGet for Table { fn get_width(&self) -> usize { self.width } + + fn get_left_offset(&self) -> usize { + self.left_offset + } } impl From for Table { diff --git a/src/table/table_ref.rs b/src/table/table_ref.rs index 122321c..b7b13ac 100644 --- a/src/table/table_ref.rs +++ b/src/table/table_ref.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use crate::cli::SortBy; use crate::models::ExchangeRateResult; +use crate::DEFAULT_WIDTH; use super::table_display::helper_table_print; use super::table_getter::TableGet; @@ -15,6 +16,7 @@ pub struct TableRef<'a> { rows: Vec<(&'a str, f64)>, pub color: bool, pub width: usize, + pub left_offset: usize, } impl<'a> TableTrait<'a> for TableRef<'a> { @@ -34,7 +36,8 @@ impl<'a> TableTrait<'a> for TableRef<'a> { column_right, rows: Vec::new(), color: false, - width: 21, + width: DEFAULT_WIDTH, + left_offset: 1, } } @@ -75,6 +78,10 @@ impl<'a> TableGet for TableRef<'a> { fn get_width(&self) -> usize { self.width } + + fn get_left_offset(&self) -> usize { + self.left_offset + } } impl<'a> From<&'a ExchangeRateResult> for TableRef<'a> { @@ -109,6 +116,7 @@ impl<'a> From<&'a Table> for TableRef<'a> { rows, color: table.color, width: table.width, + left_offset: table.left_offset, } } }