use clap::Parser as _; use ecb_rates::cache::Cache; use reqwest::{Client, IntoUrl}; use std::{borrow::BorrowMut, collections::HashMap, process::ExitCode}; use ecb_rates::cli::{Cli, FormatOption}; use ecb_rates::models::ExchangeRateResult; use ecb_rates::parsing::parse; use ecb_rates::table::Table; async fn get_and_parse(url: impl IntoUrl) -> anyhow::Result> { let client = Client::new(); let xml_content = client.get(url).send().await?.text().await?; parse(&xml_content) } fn filter_currencies(exchange_rate_results: &mut [ExchangeRateResult], currencies: &[String]) { for exchange_rate in exchange_rate_results { let rates_ptr: *mut HashMap = &mut exchange_rate.rates; exchange_rate .rates .keys() .filter(|x| !currencies.contains(x)) .for_each(|key_to_remove| { /* This is safe, since we: * 1. Already have a mutable reference. * 2. Don't run the code in paralell */ let rates = unsafe { (*rates_ptr).borrow_mut() }; rates.remove_entry(key_to_remove); }); } } #[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { let cli = Cli::parse(); if cli.force_color { colored::control::set_override(true); } let use_cache = !cli.no_cache; let cache = if use_cache { Cache::load() } else { None }; let cache_ok = cache.as_ref().map_or_else(|| false, |c| c.validate()); let mut parsed = if cache_ok { cache.as_ref().unwrap().exchange_rate_results.clone() } else { let parsed = match get_and_parse(cli.resolution.to_ecb_url()).await { Ok(k) => k, Err(e) => { eprintln!("Failed to get/parse data from ECB: {}", e); return ExitCode::FAILURE; } }; if !cache_ok { if let Err(e) = Cache::new(parsed.clone()).save() { eprintln!("Failed to save to cache with: {:?}", e); } } parsed }; if !cli.currencies.is_empty() { let currencies = cli .currencies .iter() .map(|x| x.to_uppercase()) .collect::>(); filter_currencies(&mut parsed, ¤cies); } let output = match cli.command { FormatOption::Json => { let mut json_values = parsed .iter() .map(|x| serde_json::to_value(x).expect("Failed to parse content as JSON value")) .collect::>(); if !cli.display_time { json_values .iter_mut() .filter_map(|json_value| json_value.as_object_mut()) .for_each(|map| { map.remove_entry("time"); }); } let to_string_json = if cli.compact { serde_json::to_string } else { serde_json::to_string_pretty }; to_string_json(&json_values).expect("Failed to parse content as JSON") } FormatOption::Plain => parsed .iter() .map(|x| { let mut t: Table = x.clone().into(); t.sort(); format!("{}", t) }) .collect::>() .join("\n"), }; println!("{}", &output); ExitCode::SUCCESS }