mirror of
https://github.com/lov3b/ecb-rates.git
synced 2025-12-20 03:10:38 +01:00
Compare commits
7 Commits
v1.0.0
...
feature/sm
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f930271c7 | |||
| 61deca69c6 | |||
|
|
292c9d4f34 | ||
| 0f921e978e | |||
| 3fce153c1c | |||
| 416233afde | |||
| f2f27a25b6 |
921
Cargo.lock
generated
921
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@@ -1,40 +1,38 @@
|
||||
[package]
|
||||
name = "ecb-rates"
|
||||
description = "Query exchange rates from the European Central Bank (ECB)"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
authors = ["Love Billenius <lovebillenius@disroot.org>"]
|
||||
license = "Zlib"
|
||||
license-file = "LICENSE"
|
||||
keywords = [
|
||||
"ECB",
|
||||
"European Central Bank",
|
||||
"Bank",
|
||||
"Central Bank",
|
||||
"Central",
|
||||
"exchange",
|
||||
"rates",
|
||||
"eur",
|
||||
"sek",
|
||||
"usd",
|
||||
"nok",
|
||||
"gbp",
|
||||
"pln",
|
||||
"dkk",
|
||||
"czk",
|
||||
"isk",
|
||||
"chf",
|
||||
]
|
||||
repository = "https://github.com/lov3b/ecb-rates"
|
||||
rust-version = "1.83"
|
||||
categories = ["finance", "command-line-utilities"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[[bin]]
|
||||
name = "ecb-rates"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
colored = "3.0.0"
|
||||
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
|
||||
reqwest = "0.12.12"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_json = "1.0.134"
|
||||
tokio = { version = "1.42.0", features = ["macros"] }
|
||||
anyhow = "1.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
colored = "3.0"
|
||||
quick-xml = { version = "0.38", features = ["async-tokio", "tokio"] }
|
||||
reqwest = "0.12"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
smol_str = { version = "0.3", features = ["serde"] }
|
||||
tokio = { version = "1.48", features = ["macros"] }
|
||||
|
||||
16
README.md
16
README.md
@@ -7,7 +7,7 @@
|
||||
alt="European Central Bank Logo"
|
||||
align="left"
|
||||
/>
|
||||
A CLI utility to fetch exchange reates from the European Central Bank.
|
||||
A CLI utility to fetch exchange rates from the European Central Bank.
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
@@ -19,12 +19,18 @@
|
||||
|
||||
## Install
|
||||
|
||||
### Binary
|
||||
|
||||
If you're on Debian Linux, then just go over to the releases, and install the latest _.deb_ package with `dpkg`
|
||||
|
||||
### Source
|
||||
|
||||
First, make sure that you have the rust toolchain installed. If not, then go to [rustup](https://rustup.rs) to install it.
|
||||
|
||||
Now, run the following cargo command:
|
||||
|
||||
```sh
|
||||
cargo install --git https://github.com/lov3b/ecb-rates.git
|
||||
cargo install ecb-rates
|
||||
```
|
||||
|
||||
Congratulations! Now the cli binary `ecb-rates` will be in your cargo bin folder.
|
||||
@@ -37,7 +43,7 @@ It will fetch any of the following api nodes, and reduce them for you.
|
||||
|
||||
- Last available day.
|
||||
- Last 90 days
|
||||
- Since the dawn of the *EUR*
|
||||
- Since the dawn of the _EUR_
|
||||
|
||||
#### Display select currencies
|
||||
|
||||
@@ -53,11 +59,11 @@ It features an extensive cache, which will [calculate hollidays](src/holiday.rs)
|
||||
|
||||
Change the rates for the perspective of any currency with the `--perspective` or `-p` flag.
|
||||
|
||||
Flip it from `EUR to ALL` to `ALL to EUR` with the `--invert` or `-i` flag. It will work as expected with the *perspective* option.
|
||||
Flip it from `EUR to ALL` to `ALL to EUR` with the `--invert` or `-i` flag. It will work as expected with the _perspective_ option.
|
||||
|
||||
#### Fast
|
||||
|
||||
It wouldn't be a rust project without being *BLAZINGLY FAST*! When the cache is valid a single day will on my computer be shown in 3 ms. When the cache isn't being used it will be ~90ms. The cache speed will largely depend on your drive, the latter will depend on your network speed. Both options are fast enought to be in a `.bashrc` or `.zshrc`
|
||||
It wouldn't be a rust project without being _BLAZINGLY FAST_! When the cache is valid a single day will on my computer be shown in 3 ms. When the cache isn't being used it will be ~90ms. The cache speed will largely depend on your drive, the latter will depend on your network speed. Both options are fast enought to be in a `.bashrc` or `.zshrc`
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use clap::{arg, Parser, ValueEnum};
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use super::{ShowDays, SortBy};
|
||||
|
||||
@@ -8,7 +9,7 @@ use super::{ShowDays, SortBy};
|
||||
pub struct Cli {
|
||||
/// Which currencies do you want to fetch rates for?
|
||||
#[arg(long = "currencies", short = 'c')]
|
||||
pub currencies: Vec<String>,
|
||||
pub currencies: Vec<SmolStr>,
|
||||
|
||||
#[arg(value_enum, default_value_t = FormatOption::Plain)]
|
||||
pub command: FormatOption,
|
||||
@@ -35,7 +36,7 @@ pub struct Cli {
|
||||
|
||||
/// Recalculate to the perspective from an included currency
|
||||
#[arg(long = "perspective", short = 'p')]
|
||||
pub perspective: Option<String>,
|
||||
pub perspective: Option<SmolStr>,
|
||||
|
||||
/// Invert the rate
|
||||
#[arg(long = "invert", short = 'i')]
|
||||
|
||||
@@ -2,6 +2,7 @@ use clap::Parser as _;
|
||||
use ecb_rates::cache::{Cache, CacheLine};
|
||||
use ecb_rates::HeaderDescription;
|
||||
use reqwest::{Client, IntoUrl};
|
||||
use smol_str::StrExt;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use ecb_rates::cli::{Cli, FormatOption};
|
||||
@@ -78,7 +79,7 @@ async fn main() -> ExitCode {
|
||||
parsed
|
||||
};
|
||||
|
||||
cli.perspective = cli.perspective.map(|s| s.to_uppercase());
|
||||
cli.perspective = cli.perspective.map(|s| s.to_uppercase_smolstr());
|
||||
if let Some(currency) = cli.perspective.as_ref() {
|
||||
header_description.replace_eur(¤cy);
|
||||
let error_occured = change_perspective(&mut parsed, ¤cy).is_none();
|
||||
@@ -99,7 +100,7 @@ async fn main() -> ExitCode {
|
||||
let currencies = cli
|
||||
.currencies
|
||||
.iter()
|
||||
.map(|x| x.to_uppercase())
|
||||
.map(|x| x.to_uppercase_smolstr())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
filter_currencies(&mut parsed, ¤cies);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smol_str::SmolStr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct ExchangeRateResult {
|
||||
pub time: String,
|
||||
pub rates: HashMap<String, f64>,
|
||||
pub time: SmolStr,
|
||||
pub rates: HashMap<SmolStr, f64>,
|
||||
}
|
||||
|
||||
@@ -2,37 +2,44 @@ use std::collections::HashMap;
|
||||
|
||||
use quick_xml::events::Event;
|
||||
use quick_xml::Reader;
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::models::ExchangeRateResult;
|
||||
|
||||
fn smol_from_utf8(bytes: &[u8]) -> SmolStr {
|
||||
str::from_utf8(bytes)
|
||||
.map(SmolStr::new)
|
||||
.unwrap_or_else(|_| SmolStr::new(String::from_utf8_lossy(bytes)))
|
||||
}
|
||||
|
||||
pub fn parse(xml: &str) -> anyhow::Result<Vec<ExchangeRateResult>> {
|
||||
let mut reader = Reader::from_str(xml);
|
||||
reader.config_mut().trim_text(true);
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut current_time: Option<String> = None;
|
||||
let mut current_time: Option<SmolStr> = None;
|
||||
let mut inside_cube_time = false;
|
||||
let mut current_rates = HashMap::new();
|
||||
|
||||
fn handle_cube_element(
|
||||
e: &quick_xml::events::BytesStart,
|
||||
current_time: &mut Option<String>,
|
||||
current_time: &mut Option<SmolStr>,
|
||||
inside_cube_time: &mut bool,
|
||||
current_rates: &mut HashMap<String, f64>,
|
||||
current_rates: &mut HashMap<SmolStr, f64>,
|
||||
results: &mut Vec<ExchangeRateResult>,
|
||||
) -> anyhow::Result<()> {
|
||||
if e.name().local_name().as_ref() != b"Cube" {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut time_attr: Option<String> = None;
|
||||
let mut currency_attr: Option<String> = None;
|
||||
let mut rate_attr: Option<String> = None;
|
||||
let mut time_attr: Option<SmolStr> = None;
|
||||
let mut currency_attr: Option<SmolStr> = None;
|
||||
let mut rate_attr: Option<SmolStr> = None;
|
||||
|
||||
for attr_result in e.attributes() {
|
||||
let attr = attr_result?;
|
||||
let key = attr.key.as_ref();
|
||||
let val = String::from_utf8_lossy(attr.value.as_ref()).to_string();
|
||||
let val = smol_from_utf8(attr.value.as_ref());
|
||||
|
||||
match key {
|
||||
b"time" => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::cli::SortBy;
|
||||
use crate::models::ExchangeRateResult;
|
||||
use crate::DEFAULT_WIDTH;
|
||||
@@ -8,20 +10,20 @@ use super::table_display::helper_table_print;
|
||||
use super::{TableGet, TableTrait};
|
||||
|
||||
pub struct Table {
|
||||
pub(super) header: Option<String>,
|
||||
pub(super) column_left: String,
|
||||
pub(super) column_right: String,
|
||||
pub(super) rows: Vec<(String, f64)>,
|
||||
pub(super) header: Option<SmolStr>,
|
||||
pub(super) column_left: SmolStr,
|
||||
pub(super) column_right: SmolStr,
|
||||
pub(super) rows: Vec<(SmolStr, f64)>,
|
||||
pub color: bool,
|
||||
pub width: usize,
|
||||
pub left_offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> TableTrait<'a> for Table {
|
||||
type Header = String;
|
||||
type ColumnLeft = String;
|
||||
type ColumnRight = String;
|
||||
type RowLeft = String;
|
||||
type Header = SmolStr;
|
||||
type ColumnLeft = SmolStr;
|
||||
type ColumnRight = SmolStr;
|
||||
type RowLeft = SmolStr;
|
||||
|
||||
fn new(
|
||||
header: Option<Self::Header>,
|
||||
@@ -59,8 +61,8 @@ impl<'a> TableTrait<'a> for Table {
|
||||
}
|
||||
|
||||
impl TableGet for Table {
|
||||
type RowLeftRef = String;
|
||||
type RowRightRef = String;
|
||||
type RowLeftRef = SmolStr;
|
||||
type RowRightRef = SmolStr;
|
||||
|
||||
fn get_header(&self) -> Option<&str> {
|
||||
self.header.as_deref()
|
||||
@@ -85,7 +87,7 @@ impl TableGet for Table {
|
||||
|
||||
impl From<ExchangeRateResult> for Table {
|
||||
fn from(value: ExchangeRateResult) -> Self {
|
||||
let mut table = Table::new(Some(value.time), "Currency".to_string(), "Rate".to_string());
|
||||
let mut table = Table::new(Some(value.time), "Currency".into(), "Rate".into());
|
||||
for (key, val) in value.rates.into_iter() {
|
||||
table.add_row(key, val);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::{borrow::BorrowMut, collections::HashMap, ops::Deref};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
|
||||
use crate::models::ExchangeRateResult;
|
||||
|
||||
pub fn filter_currencies(exchange_rate_results: &mut [ExchangeRateResult], currencies: &[String]) {
|
||||
pub fn filter_currencies(exchange_rate_results: &mut [ExchangeRateResult], currencies: &[SmolStr]) {
|
||||
for exchange_rate in exchange_rate_results {
|
||||
let rates_ptr: *mut HashMap<String, f64> = &mut exchange_rate.rates;
|
||||
let rates_ptr: *mut HashMap<_, _> = &mut exchange_rate.rates;
|
||||
exchange_rate
|
||||
.rates
|
||||
.keys()
|
||||
@@ -32,7 +34,7 @@ pub fn change_perspective(
|
||||
*iter_rate = eur_rate * iter_rate.deref();
|
||||
}
|
||||
|
||||
rate_res.rates.insert("EUR".to_string(), eur_rate);
|
||||
rate_res.rates.insert("EUR".into(), eur_rate);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user