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/is
| Author | SHA1 | Date | |
|---|---|---|---|
| 8158f8d42b | |||
| 61deca69c6 | |||
|
|
292c9d4f34 | ||
| 0f921e978e | |||
| 3fce153c1c | |||
| 416233afde | |||
| f2f27a25b6 |
897
Cargo.lock
generated
897
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
41
Cargo.toml
41
Cargo.toml
@@ -1,40 +1,37 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ecb-rates"
|
name = "ecb-rates"
|
||||||
description = "Query exchange rates from the European Central Bank (ECB)"
|
description = "Query exchange rates from the European Central Bank (ECB)"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Love Billenius <lovebillenius@disroot.org>"]
|
authors = ["Love Billenius <lovebillenius@disroot.org>"]
|
||||||
|
license = "Zlib"
|
||||||
license-file = "LICENSE"
|
license-file = "LICENSE"
|
||||||
keywords = [
|
keywords = [
|
||||||
"ECB",
|
"ECB",
|
||||||
"European Central Bank",
|
|
||||||
"Bank",
|
"Bank",
|
||||||
"Central Bank",
|
"Central",
|
||||||
"exchange",
|
"exchange",
|
||||||
"rates",
|
"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]]
|
[[bin]]
|
||||||
name = "ecb-rates"
|
name = "ecb-rates"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0"
|
||||||
chrono = { version = "0.4.39", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
clap = { version = "4.5.23", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
colored = "3.0.0"
|
colored = "3.0"
|
||||||
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
|
quick-xml = { version = "0.38", features = ["async-tokio", "tokio"] }
|
||||||
reqwest = "0.12.12"
|
reqwest = "0.12"
|
||||||
serde = { version = "1.0.217", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.134"
|
serde_json = "1.0"
|
||||||
tokio = { version = "1.42.0", features = ["macros"] }
|
tokio = { version = "1.48", features = ["macros"] }
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -7,7 +7,7 @@
|
|||||||
alt="European Central Bank Logo"
|
alt="European Central Bank Logo"
|
||||||
align="left"
|
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 />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@@ -19,12 +19,18 @@
|
|||||||
|
|
||||||
## Install
|
## 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.
|
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:
|
Now, run the following cargo command:
|
||||||
|
|
||||||
```sh
|
```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.
|
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 available day.
|
||||||
- Last 90 days
|
- Last 90 days
|
||||||
- Since the dawn of the *EUR*
|
- Since the dawn of the _EUR_
|
||||||
|
|
||||||
#### Display select currencies
|
#### 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.
|
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
|
#### 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
|
### Examples
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
|
|
||||||
|
use crate::models::Currency;
|
||||||
|
|
||||||
#[derive(Debug, ValueEnum, Clone)]
|
#[derive(Debug, ValueEnum, Clone)]
|
||||||
pub enum SortBy {
|
pub enum SortBy {
|
||||||
Currency,
|
Currency,
|
||||||
@@ -7,9 +9,9 @@ pub enum SortBy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SortBy {
|
impl SortBy {
|
||||||
pub fn get_comparer(&self) -> fn(&(&str, f64), &(&str, f64)) -> std::cmp::Ordering {
|
pub fn get_comparer(&self) -> fn(&(&Currency, f64), &(&Currency, f64)) -> std::cmp::Ordering {
|
||||||
match self {
|
match self {
|
||||||
Self::Currency => |a, b| a.0.cmp(&b.0),
|
Self::Currency => |a, b| a.0.as_ref().cmp(b.0.as_ref()),
|
||||||
Self::Rate => |a, b| a.1.total_cmp(&b.1),
|
Self::Rate => |a, b| a.1.total_cmp(&b.1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@@ -3,9 +3,10 @@ use ecb_rates::cache::{Cache, CacheLine};
|
|||||||
use ecb_rates::HeaderDescription;
|
use ecb_rates::HeaderDescription;
|
||||||
use reqwest::{Client, IntoUrl};
|
use reqwest::{Client, IntoUrl};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ecb_rates::cli::{Cli, FormatOption};
|
use ecb_rates::cli::{Cli, FormatOption};
|
||||||
use ecb_rates::models::ExchangeRateResult;
|
use ecb_rates::models::{Currency, ExchangeRateResult};
|
||||||
use ecb_rates::parsing::parse;
|
use ecb_rates::parsing::parse;
|
||||||
use ecb_rates::table::{TableRef, TableTrait as _};
|
use ecb_rates::table::{TableRef, TableTrait as _};
|
||||||
use ecb_rates::utils_calc::{change_perspective, filter_currencies, invert_rates, round};
|
use ecb_rates::utils_calc::{change_perspective, filter_currencies, invert_rates, round};
|
||||||
@@ -79,8 +80,18 @@ async fn main() -> ExitCode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cli.perspective = cli.perspective.map(|s| s.to_uppercase());
|
cli.perspective = cli.perspective.map(|s| s.to_uppercase());
|
||||||
if let Some(currency) = cli.perspective.as_ref() {
|
let parsed_currency = match cli.perspective.as_ref() {
|
||||||
header_description.replace_eur(¤cy);
|
Some(currency) => match Currency::from_str(currency) {
|
||||||
|
Ok(k) => Some(k),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("The currency code '{}' is invalid: {:?}", currency, e);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(currency) = parsed_currency.as_ref() {
|
||||||
|
header_description.replace_eur(currency.as_ref());
|
||||||
let error_occured = change_perspective(&mut parsed, ¤cy).is_none();
|
let error_occured = change_perspective(&mut parsed, ¤cy).is_none();
|
||||||
if error_occured {
|
if error_occured {
|
||||||
eprintln!("The currency wasn't in the data from the ECB!");
|
eprintln!("The currency wasn't in the data from the ECB!");
|
||||||
@@ -96,11 +107,19 @@ async fn main() -> ExitCode {
|
|||||||
round(&mut parsed, cli.max_decimals);
|
round(&mut parsed, cli.max_decimals);
|
||||||
|
|
||||||
if !cli.currencies.is_empty() {
|
if !cli.currencies.is_empty() {
|
||||||
let currencies = cli
|
let currencies = match cli
|
||||||
.currencies
|
.currencies
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_uppercase())
|
.map(|x| x.to_uppercase())
|
||||||
.collect::<Vec<_>>();
|
.map(|x| Currency::from_str(&x))
|
||||||
|
.collect::<anyhow::Result<Vec<_>>>()
|
||||||
|
{
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to parse currenc(y/ies): {:?}", e);
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
filter_currencies(&mut parsed, ¤cies);
|
filter_currencies(&mut parsed, ¤cies);
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/models/currency.rs
Normal file
97
src/models/currency.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
ops::Index,
|
||||||
|
slice::Iter,
|
||||||
|
str::{self, FromStr},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{de, Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Currency {
|
||||||
|
name: [u8; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Currency {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
// SAFETY: We validate that bytes are ASCII in FromStr.
|
||||||
|
unsafe { str::from_utf8_unchecked(&self.name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> Iter<'_, u8> {
|
||||||
|
self.name.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for Currency {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Currency {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Currency {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
FromStr::from_str(&s).map_err(de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Currency {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.len() != 3 {
|
||||||
|
anyhow::bail!("Currency code must be exactly 3 chars");
|
||||||
|
}
|
||||||
|
if !s.is_ascii() {
|
||||||
|
anyhow::bail!("Currency code must be ASCII");
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = s.as_bytes();
|
||||||
|
Ok(Self {
|
||||||
|
name: [b[0], b[1], b[2]],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for Currency {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
Currency::from_str(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Currency {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Currency {
|
||||||
|
type Output = u8;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.name[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a Currency {
|
||||||
|
type Item = &'a u8;
|
||||||
|
type IntoIter = Iter<'a, u8>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::Currency;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
pub struct ExchangeRateResult {
|
pub struct ExchangeRateResult {
|
||||||
pub time: String,
|
pub time: String,
|
||||||
pub rates: HashMap<String, f64>,
|
pub rates: HashMap<Currency, f64>,
|
||||||
}
|
}
|
||||||
5
src/models/mod.rs
Normal file
5
src/models/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod currency;
|
||||||
|
mod exchange_rate_result;
|
||||||
|
|
||||||
|
pub use currency::Currency;
|
||||||
|
pub use exchange_rate_result::ExchangeRateResult;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
use quick_xml::Reader;
|
use quick_xml::Reader;
|
||||||
|
|
||||||
use crate::models::ExchangeRateResult;
|
use crate::models::{Currency, ExchangeRateResult};
|
||||||
|
|
||||||
pub fn parse(xml: &str) -> anyhow::Result<Vec<ExchangeRateResult>> {
|
pub fn parse(xml: &str) -> anyhow::Result<Vec<ExchangeRateResult>> {
|
||||||
let mut reader = Reader::from_str(xml);
|
let mut reader = Reader::from_str(xml);
|
||||||
@@ -18,7 +18,7 @@ pub fn parse(xml: &str) -> anyhow::Result<Vec<ExchangeRateResult>> {
|
|||||||
e: &quick_xml::events::BytesStart,
|
e: &quick_xml::events::BytesStart,
|
||||||
current_time: &mut Option<String>,
|
current_time: &mut Option<String>,
|
||||||
inside_cube_time: &mut bool,
|
inside_cube_time: &mut bool,
|
||||||
current_rates: &mut HashMap<String, f64>,
|
current_rates: &mut HashMap<Currency, f64>,
|
||||||
results: &mut Vec<ExchangeRateResult>,
|
results: &mut Vec<ExchangeRateResult>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if e.name().local_name().as_ref() != b"Cube" {
|
if e.name().local_name().as_ref() != b"Cube" {
|
||||||
@@ -26,7 +26,7 @@ pub fn parse(xml: &str) -> anyhow::Result<Vec<ExchangeRateResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut time_attr: Option<String> = None;
|
let mut time_attr: Option<String> = None;
|
||||||
let mut currency_attr: Option<String> = None;
|
let mut currency_attr: Option<Currency> = None;
|
||||||
let mut rate_attr: Option<String> = None;
|
let mut rate_attr: Option<String> = None;
|
||||||
|
|
||||||
for attr_result in e.attributes() {
|
for attr_result in e.attributes() {
|
||||||
@@ -39,7 +39,7 @@ pub fn parse(xml: &str) -> anyhow::Result<Vec<ExchangeRateResult>> {
|
|||||||
time_attr = Some(val);
|
time_attr = Some(val);
|
||||||
}
|
}
|
||||||
b"currency" => {
|
b"currency" => {
|
||||||
currency_attr = Some(val);
|
currency_attr = Some(Currency::from_str(&val)?);
|
||||||
}
|
}
|
||||||
b"rate" => {
|
b"rate" => {
|
||||||
rate_attr = Some(val);
|
rate_attr = Some(val);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::cli::SortBy;
|
use crate::cli::SortBy;
|
||||||
use crate::models::ExchangeRateResult;
|
use crate::models::{Currency, ExchangeRateResult};
|
||||||
use crate::DEFAULT_WIDTH;
|
use crate::DEFAULT_WIDTH;
|
||||||
|
|
||||||
use super::table_display::helper_table_print;
|
use super::table_display::helper_table_print;
|
||||||
@@ -11,7 +11,7 @@ pub struct Table {
|
|||||||
pub(super) header: Option<String>,
|
pub(super) header: Option<String>,
|
||||||
pub(super) column_left: String,
|
pub(super) column_left: String,
|
||||||
pub(super) column_right: String,
|
pub(super) column_right: String,
|
||||||
pub(super) rows: Vec<(String, f64)>,
|
pub(super) rows: Vec<(Currency, f64)>,
|
||||||
pub color: bool,
|
pub color: bool,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub left_offset: usize,
|
pub left_offset: usize,
|
||||||
@@ -21,7 +21,7 @@ impl<'a> TableTrait<'a> for Table {
|
|||||||
type Header = String;
|
type Header = String;
|
||||||
type ColumnLeft = String;
|
type ColumnLeft = String;
|
||||||
type ColumnRight = String;
|
type ColumnRight = String;
|
||||||
type RowLeft = String;
|
type RowLeft = Currency;
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
header: Option<Self::Header>,
|
header: Option<Self::Header>,
|
||||||
@@ -59,7 +59,7 @@ impl<'a> TableTrait<'a> for Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TableGet for Table {
|
impl TableGet for Table {
|
||||||
type RowLeftRef = String;
|
type RowLeftRef = Currency;
|
||||||
type RowRightRef = String;
|
type RowRightRef = String;
|
||||||
|
|
||||||
fn get_header(&self) -> Option<&str> {
|
fn get_header(&self) -> Option<&str> {
|
||||||
@@ -77,7 +77,7 @@ impl TableGet for Table {
|
|||||||
fn get_width(&self) -> usize {
|
fn get_width(&self) -> usize {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_left_offset(&self) -> usize {
|
fn get_left_offset(&self) -> usize {
|
||||||
self.left_offset
|
self.left_offset
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::cli::SortBy;
|
use crate::cli::SortBy;
|
||||||
use crate::models::ExchangeRateResult;
|
use crate::models::{Currency, ExchangeRateResult};
|
||||||
use crate::DEFAULT_WIDTH;
|
use crate::DEFAULT_WIDTH;
|
||||||
|
|
||||||
use super::table_display::helper_table_print;
|
use super::table_display::helper_table_print;
|
||||||
@@ -13,7 +13,7 @@ pub struct TableRef<'a> {
|
|||||||
header: Option<&'a str>,
|
header: Option<&'a str>,
|
||||||
column_left: &'a str,
|
column_left: &'a str,
|
||||||
column_right: &'a str,
|
column_right: &'a str,
|
||||||
rows: Vec<(&'a str, f64)>,
|
rows: Vec<(&'a Currency, f64)>,
|
||||||
pub color: bool,
|
pub color: bool,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub left_offset: usize,
|
pub left_offset: usize,
|
||||||
@@ -23,7 +23,7 @@ impl<'a> TableTrait<'a> for TableRef<'a> {
|
|||||||
type Header = &'a str;
|
type Header = &'a str;
|
||||||
type ColumnLeft = &'a str;
|
type ColumnLeft = &'a str;
|
||||||
type ColumnRight = &'a str;
|
type ColumnRight = &'a str;
|
||||||
type RowLeft = &'a str;
|
type RowLeft = &'a Currency;
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
header: Option<Self::Header>,
|
header: Option<Self::Header>,
|
||||||
@@ -60,7 +60,7 @@ impl<'a> TableTrait<'a> for TableRef<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TableGet for TableRef<'a> {
|
impl<'a> TableGet for TableRef<'a> {
|
||||||
type RowLeftRef = &'a str;
|
type RowLeftRef = &'a Currency;
|
||||||
type RowRightRef = &'a str;
|
type RowRightRef = &'a str;
|
||||||
|
|
||||||
fn get_header(&self) -> Option<&str> {
|
fn get_header(&self) -> Option<&str> {
|
||||||
@@ -78,7 +78,7 @@ impl<'a> TableGet for TableRef<'a> {
|
|||||||
fn get_width(&self) -> usize {
|
fn get_width(&self) -> usize {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_left_offset(&self) -> usize {
|
fn get_left_offset(&self) -> usize {
|
||||||
self.left_offset
|
self.left_offset
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ impl<'a> From<&'a Table> for TableRef<'a> {
|
|||||||
let rows = table
|
let rows = table
|
||||||
.rows
|
.rows
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(left, right)| (left.as_str(), *right))
|
.map(|(left, right)| (left, *right))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
TableRef {
|
TableRef {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use std::{borrow::BorrowMut, collections::HashMap, ops::Deref};
|
use std::{borrow::BorrowMut, collections::HashMap, ops::Deref, str::FromStr};
|
||||||
|
|
||||||
use crate::models::ExchangeRateResult;
|
use crate::models::{Currency, ExchangeRateResult};
|
||||||
|
|
||||||
pub fn filter_currencies(exchange_rate_results: &mut [ExchangeRateResult], currencies: &[String]) {
|
pub fn filter_currencies(
|
||||||
|
exchange_rate_results: &mut [ExchangeRateResult],
|
||||||
|
currencies: &[Currency],
|
||||||
|
) {
|
||||||
for exchange_rate in exchange_rate_results {
|
for exchange_rate in exchange_rate_results {
|
||||||
let rates_ptr: *mut HashMap<String, f64> = &mut exchange_rate.rates;
|
let rates_ptr: *mut HashMap<Currency, f64> = &mut exchange_rate.rates;
|
||||||
exchange_rate
|
exchange_rate
|
||||||
.rates
|
.rates
|
||||||
.keys()
|
.keys()
|
||||||
@@ -22,7 +25,7 @@ pub fn filter_currencies(exchange_rate_results: &mut [ExchangeRateResult], curre
|
|||||||
|
|
||||||
pub fn change_perspective(
|
pub fn change_perspective(
|
||||||
exchange_rate_results: &mut [ExchangeRateResult],
|
exchange_rate_results: &mut [ExchangeRateResult],
|
||||||
currency: &str,
|
currency: &Currency,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
for rate_res in exchange_rate_results {
|
for rate_res in exchange_rate_results {
|
||||||
let currency_rate = rate_res.rates.remove(currency)?;
|
let currency_rate = rate_res.rates.remove(currency)?;
|
||||||
@@ -32,7 +35,10 @@ pub fn change_perspective(
|
|||||||
*iter_rate = eur_rate * iter_rate.deref();
|
*iter_rate = eur_rate * iter_rate.deref();
|
||||||
}
|
}
|
||||||
|
|
||||||
rate_res.rates.insert("EUR".to_string(), eur_rate);
|
rate_res.rates.insert(
|
||||||
|
unsafe { Currency::from_str("EUR").unwrap_unchecked() },
|
||||||
|
eur_rate,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user