Core is done

This commit is contained in:
Love 2025-01-04 18:18:38 +01:00
parent b9843d65a1
commit 272c5d15c8
4 changed files with 150 additions and 29 deletions

7
Cargo.lock generated
View File

@ -66,6 +66,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -209,6 +215,7 @@ dependencies = [
name = "ecb-exchange"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"quick-xml",
"reqwest",

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.95"
clap = { version = "4.5.23", features = ["derive"] }
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
reqwest = "0.12.12"

View File

@ -1,4 +1,6 @@
use clap::{arg, Parser, Subcommand};
use clap::{arg, Parser, ValueEnum};
use crate::ecb_url;
#[derive(Debug, Parser)]
#[command(author, version, about)]
@ -7,25 +9,44 @@ pub struct Cli {
/// Which currencies do you want to fetch rates for?
#[arg(long = "currencies", short = 'c')]
pub currencies: Vec<String>,
/// Which subcommand (output format) we are using
#[command(subcommand)]
pub command: FormatCommand,
}
/// Subcommand enum for output format
#[derive(Debug, Subcommand)]
pub enum FormatCommand {
/// Minimal JSON output
JSONMin,
/// Pretty-printed JSON output
JSONPretty,
/// Plain line-by-line output (with extra flags)
Plain {
#[arg(value_enum, default_value_t = FormatOption::Plain)]
pub command: FormatOption,
/// Show the time in the output
#[arg(long = "display-time")]
display_time: bool,
pub display_time: bool,
/// Print currencies in a compact single line
#[arg(long = "compact")]
compact: bool,
},
pub compact: bool,
/// Amount of data
#[arg(value_enum, default_value_t = Resolution::TODAY, long="resolution", short='r')]
pub resolution: Resolution,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum Resolution {
TODAY,
HistDays90,
HistDay,
}
impl Resolution {
pub fn to_ecb_url(&self) -> &'static str {
match self {
Resolution::TODAY => ecb_url::TODAY,
Resolution::HistDays90 => ecb_url::hist::DAYS_90,
Resolution::HistDay => ecb_url::hist::DAILY,
}
}
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum FormatOption {
/// JSON output
Json,
/// Plain line-by-line output (with extra flags)
Plain,
}

View File

@ -1,14 +1,106 @@
use ecb_exchange::ecb_url;
use reqwest::Client;
use std::error::Error;
use clap::Parser as _;
use ecb_exchange::{cli::Cli, models::ExchangeRateResult};
use reqwest::{Client, IntoUrl};
use std::{borrow::BorrowMut, collections::HashMap, error::Error, process::ExitCode};
use ecb_exchange::parsing::parse;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn Error>> {
async fn get_and_parse(url: impl IntoUrl) -> Result<Vec<ExchangeRateResult>, Box<dyn Error>> {
let client = Client::new();
let xml_content = client.get(ecb_url::TODAY).send().await?.text().await?;
let parsed = parse(&xml_content).unwrap();
println!("{}", serde_json::to_string_pretty(&parsed).unwrap());
Ok(())
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<String, f64> = &mut exchange_rate.rates;
exchange_rate
.rates
.keys()
.filter(|x| !currencies.contains(x))
.for_each(|key_to_remove| {
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();
let mut 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 !cli.currencies.is_empty() {
filter_currencies(&mut parsed, &cli.currencies);
}
let output = match cli.command {
ecb_exchange::cli::FormatOption::Json => {
let mut json_values = parsed
.iter()
.map(|x| serde_json::to_value(x).expect("Failed to parse content as JSON value"))
.collect::<Vec<_>>();
if !cli.display_time {
for json_value in json_values.iter_mut() {
if let Some(map) = json_value.as_object_mut() {
map.remove_entry("time");
}
}
}
if cli.compact {
serde_json::to_string(&json_values)
} else {
serde_json::to_string_pretty(&json_values)
}
.expect("Failed to parse content as JSON")
}
ecb_exchange::cli::FormatOption::Plain => {
struct StringCur<'a> {
time: &'a String,
cur: String,
}
let separator = if cli.compact { ", " } else { "\n" };
let string_curred = parsed.iter().map(|entry| {
let s = entry
.rates
.iter()
.map(|(cur, rate)| format!("{}: {}", cur, rate))
.collect::<Vec<_>>()
.join(&separator);
StringCur {
time: &entry.time,
cur: s,
}
});
let time_sep = if cli.compact { ": " } else { "\n" };
let mut buf = String::new();
for sc in string_curred {
if cli.display_time {
buf.push_str(&sc.time);
buf.push_str(time_sep);
}
buf.push_str(&sc.cur);
buf.push_str(&separator);
buf.push('\n');
}
buf
}
};
println!("{}", &output);
ExitCode::SUCCESS
}