mirror of
https://github.com/lov3b/ecb-rates.git
synced 2025-02-22 18:00:11 +01:00
Core is done
This commit is contained in:
parent
b9843d65a1
commit
272c5d15c8
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
61
src/cli.rs
61
src/cli.rs
@ -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,
|
||||
|
||||
#[arg(value_enum, default_value_t = FormatOption::Plain)]
|
||||
pub command: FormatOption,
|
||||
|
||||
/// Show the time in the output
|
||||
#[arg(long = "display-time")]
|
||||
pub display_time: bool,
|
||||
|
||||
/// Print currencies in a compact single line
|
||||
#[arg(long = "compact")]
|
||||
pub compact: bool,
|
||||
|
||||
/// Amount of data
|
||||
#[arg(value_enum, default_value_t = Resolution::TODAY, long="resolution", short='r')]
|
||||
pub resolution: Resolution,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Show the time in the output
|
||||
#[arg(long = "display-time")]
|
||||
display_time: bool,
|
||||
/// Print currencies in a compact single line
|
||||
#[arg(long = "compact")]
|
||||
compact: bool,
|
||||
},
|
||||
#[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,
|
||||
}
|
||||
|
110
src/main.rs
110
src/main.rs
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user