mirror of
https://github.com/lov3b/ecb-rates.git
synced 2025-02-23 02:10:07 +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",
|
"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]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@ -209,6 +215,7 @@ dependencies = [
|
|||||||
name = "ecb-exchange"
|
name = "ecb-exchange"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.95"
|
||||||
clap = { version = "4.5.23", features = ["derive"] }
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
|
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
|
||||||
reqwest = "0.12.12"
|
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)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
@ -7,25 +9,44 @@ pub struct Cli {
|
|||||||
/// Which currencies do you want to fetch rates for?
|
/// Which currencies do you want to fetch rates for?
|
||||||
#[arg(long = "currencies", short = 'c')]
|
#[arg(long = "currencies", short = 'c')]
|
||||||
pub currencies: Vec<String>,
|
pub currencies: Vec<String>,
|
||||||
/// Which subcommand (output format) we are using
|
|
||||||
#[command(subcommand)]
|
#[arg(value_enum, default_value_t = FormatOption::Plain)]
|
||||||
pub command: FormatCommand,
|
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, Clone, Copy, ValueEnum)]
|
||||||
#[derive(Debug, Subcommand)]
|
pub enum Resolution {
|
||||||
pub enum FormatCommand {
|
TODAY,
|
||||||
/// Minimal JSON output
|
HistDays90,
|
||||||
JSONMin,
|
HistDay,
|
||||||
/// Pretty-printed JSON output
|
}
|
||||||
JSONPretty,
|
|
||||||
/// Plain line-by-line output (with extra flags)
|
impl Resolution {
|
||||||
Plain {
|
pub fn to_ecb_url(&self) -> &'static str {
|
||||||
/// Show the time in the output
|
match self {
|
||||||
#[arg(long = "display-time")]
|
Resolution::TODAY => ecb_url::TODAY,
|
||||||
display_time: bool,
|
Resolution::HistDays90 => ecb_url::hist::DAYS_90,
|
||||||
/// Print currencies in a compact single line
|
Resolution::HistDay => ecb_url::hist::DAILY,
|
||||||
#[arg(long = "compact")]
|
}
|
||||||
compact: bool,
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
|
#[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 clap::Parser as _;
|
||||||
use reqwest::Client;
|
use ecb_exchange::{cli::Cli, models::ExchangeRateResult};
|
||||||
use std::error::Error;
|
use reqwest::{Client, IntoUrl};
|
||||||
|
use std::{borrow::BorrowMut, collections::HashMap, error::Error, process::ExitCode};
|
||||||
|
|
||||||
use ecb_exchange::parsing::parse;
|
use ecb_exchange::parsing::parse;
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
async fn get_and_parse(url: impl IntoUrl) -> Result<Vec<ExchangeRateResult>, Box<dyn Error>> {
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let xml_content = client.get(ecb_url::TODAY).send().await?.text().await?;
|
let xml_content = client.get(url).send().await?.text().await?;
|
||||||
let parsed = parse(&xml_content).unwrap();
|
parse(&xml_content)
|
||||||
println!("{}", serde_json::to_string_pretty(&parsed).unwrap());
|
}
|
||||||
Ok(())
|
|
||||||
|
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