Release v1.0.0
This is a monumental release in the development of sterling, culminating in the finalization of the command-line API and a stabilization of sterling's features. Woo-hoo!! Sterling's functionality is very basic: conversion of PHB currencies into user-defined ones, basic arithmetic operations, and converting custom currency amounts to the equivalent amount in PHB copper. It's unlikely that I will be adding any additional features ontop of this set, as it already provides functionality appropriate enough for most usecases. Congrats to me on finally finishing a side-project!
This commit is contained in:
parent
f7a6ec53a0
commit
90a3f41c2d
15 changed files with 706 additions and 363 deletions
|
@ -9,16 +9,4 @@ cargo:test:
|
|||
- master
|
||||
script:
|
||||
- cargo test --verbose
|
||||
|
||||
cargo:build:
|
||||
stage: deploy
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- cargo build --release
|
||||
cache:
|
||||
paths:
|
||||
- target/
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/sterling
|
||||
- cargo bench
|
73
.travis.yml
73
.travis.yml
|
@ -1,27 +1,62 @@
|
|||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
os:
|
||||
- osx
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
rust:
|
||||
- stable
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
env:
|
||||
global:
|
||||
# TODO Update this to match the name of your project.
|
||||
- CRATE_NAME=sterling
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Linux
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
|
||||
# OSX
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
|
||||
script:
|
||||
- cargo test --verbose
|
||||
- cargo build --release
|
||||
- bash ci/script.sh
|
||||
|
||||
after_script: set +e
|
||||
|
||||
before_deploy:
|
||||
- git config --local user.name "Zachary Dziura"
|
||||
- git config --local user.email "zcdziura@gmail.com"
|
||||
- git tag "$(date +'%Y%m%d%H%M%S')-$(git log --format=%h -1)-osx"
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "NVH5d3CH0QUyFSu0MbeB3WvSo52qwjxH98wIL7kieD/kbTrTzTCbTWiTCV+/OM41nWOdxTy9T5TLqDsrh4k4xCkb4VbKTcsfIpBaQqwMvT6Co/GgLr4xSiHBI/ENBVwbnyDavMxh/E5AAAPF/HgGci2tEqzNuu9V7jon6uhb8+WovbfZeEA4tSNLsWV5g3MwssMfdaWzDPTHsiWXFPn6AVhkmy4fKAIHoUtp37A7bqx1hGPpFD3OGYN1oDxtJK5jRBSXegyWh08RQkLQ74PJTWD6Xw+Hvp1ewP1vitP69VJgsBC496jPasqAEOVeD3KogtcmBEyaIG+I5LZWLTibs41qF83cxJDdWxw69H827IXSQobM+7Sc51chWJR0H3OA1yDPQvorI1C17zvXd4wPpDfSUeY5ZqAplnYMOxk3jDbbX099bEyRE/skWHRaqL99fV7i5bO3aHDFP/BDjp03hnzpvfKs9zm05e87LStriNYQ5NsCPkdX+W18Q15DLhS2D9cp37PPAUA5jLNUFiEY5x9fwl5XEpefBqrqmE8qbmkc9GTr3MZikmTfB51Nx5NvkybCTKhMoKw5AhNLmw0fnkaqxrei7Uif7WqxTkngJep6VLidmt2pRJ9Qj3AWOXsLZJPm0ZQuo71dWC049EeEVtfQkyz/9K2J+iNVRgdiEeg="
|
||||
file_glob: true
|
||||
file: target/release/sterling
|
||||
skip_cleanup: true
|
||||
file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
|
||||
on:
|
||||
repo: zcdziura/sterling
|
||||
branch: master
|
||||
addons:
|
||||
artifacts: true
|
||||
tags: true
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
|
||||
cache: cargo
|
||||
before_cache:
|
||||
# Travis can't cache files that are not readable by "others"
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
|
||||
branches:
|
||||
only:
|
||||
# release tags
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
# - master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
23
Cargo.toml
23
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "sterling"
|
||||
version = "0.3.0"
|
||||
version = "1.0.0"
|
||||
description = "Converts a given D&D 5e currency value to the Silver Standard."
|
||||
authors = ["Zachary Dziura <zcdziura@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
@ -8,14 +8,31 @@ license = "Unlicense/MIT"
|
|||
repository = "https://gitlab.com/zcdziura/sterling"
|
||||
keywords = ["dnd", "coins", "converter", "currency", "5e"]
|
||||
|
||||
[lib]
|
||||
name = "sterling_ops"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "sterling"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.31"
|
||||
clap = "2.32"
|
||||
lazysort = "0.2"
|
||||
lazy_static = "1.0"
|
||||
regex = "1.0"
|
||||
separator = "0.3"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_yaml = "0.7"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.2"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = "abort"
|
||||
panic = "abort"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
harness = false
|
|
@ -92,7 +92,6 @@ within my own campaign!
|
|||
name: "guilder"
|
||||
rate: 896
|
||||
alias: "g"
|
||||
plural: "guilders"
|
||||
-
|
||||
name: "shilling"
|
||||
rate: 32
|
||||
|
|
73
appveyor.yml
73
appveyor.yml
|
@ -1,29 +1,60 @@
|
|||
os: Visual Studio 2015
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
environment:
|
||||
global:
|
||||
RUST_VERSION: stable
|
||||
CRATE_NAME: sterling
|
||||
|
||||
matrix:
|
||||
- channel: stable
|
||||
target: x86_64-pc-windows-gnu
|
||||
artifacts:
|
||||
- path: target/release/sterling.exe
|
||||
name: sterling
|
||||
# MinGW
|
||||
- TARGET: x86_64-pc-windows-gnu
|
||||
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init -yv --default-toolchain %channel% --default-host %target%
|
||||
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
build: false
|
||||
- ps: >-
|
||||
If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
|
||||
$Env:PATH += ';C:\msys64\mingw64\bin'
|
||||
} ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
|
||||
$Env:PATH += ';C:\msys64\mingw32\bin'
|
||||
}
|
||||
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
|
||||
- rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION%
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -Vv
|
||||
- cargo -V
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose
|
||||
- cargo build --release
|
||||
# we don't run the "test phase" when doing deploys
|
||||
- if [%APPVEYOR_REPO_TAG%]==[true] (
|
||||
cargo test --target %TARGET% &&
|
||||
cargo build --target %TARGET% --release
|
||||
)
|
||||
|
||||
before_deploy:
|
||||
- ps: ci\before_deploy.ps1
|
||||
|
||||
deploy:
|
||||
provider: GitHub
|
||||
description: ''
|
||||
artifact: /.*\.zip/
|
||||
auth_token:
|
||||
secure: bvA/4J1T0h65ur6tsg6k/wlZFjP3qr2QsyRsmGMEmm7DOF61xmzTnjuBcPjQYrba
|
||||
artifact: target/release/sterling.exe
|
||||
on:
|
||||
branch: master
|
||||
RUST_VERSION: stable
|
||||
appveyor_repo_tag: true
|
||||
provider: GitHub
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.cargo\registry
|
||||
- target
|
||||
|
||||
branches:
|
||||
only:
|
||||
# Release tags
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
# - master
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
on_build_success: false
|
||||
|
||||
# Building is done in the test phase, so we disable Appveyor's build phase.
|
||||
build: false
|
27
benches/bench.rs
Normal file
27
benches/bench.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
#[macro_use]
|
||||
extern crate criterion;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate sterling_ops;
|
||||
|
||||
use criterion::Criterion;
|
||||
use sterling_ops::currency::Currency;
|
||||
use sterling_ops::*;
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENCIES: Vec<Currency> = vec![
|
||||
Currency::new("penny", 1, "p", Some("pence".to_owned()), None),
|
||||
Currency::new("shilling", 100, "s", Some("sterling".to_owned()), None),
|
||||
Currency::new("guilder", 10_000, "g", None, None),
|
||||
Currency::new("note", 1_000_000, "N", None, Some(true)),
|
||||
];
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("default operation", |b| {
|
||||
b.iter(|| default_operation("3p 5s 7s 132c", &CURRENCIES, true))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
22
ci/before_deploy.ps1
Normal file
22
ci/before_deploy.ps1
Normal file
|
@ -0,0 +1,22 @@
|
|||
# This script takes care of packaging the build artifacts that will go in the
|
||||
# release zipfile
|
||||
|
||||
$SRC_DIR = $PWD.Path
|
||||
$STAGE = [System.Guid]::NewGuid().ToString()
|
||||
|
||||
Set-Location $ENV:Temp
|
||||
New-Item -Type Directory -Name $STAGE
|
||||
Set-Location $STAGE
|
||||
|
||||
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"
|
||||
|
||||
Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\sterling.exe" '.\'
|
||||
|
||||
7z a "$ZIP" *
|
||||
|
||||
Push-AppveyorArtifact "$ZIP"
|
||||
|
||||
Remove-Item *.* -Force
|
||||
Set-Location ..
|
||||
Remove-Item $STAGE
|
||||
Set-Location $SRC_DIR
|
32
ci/before_deploy.sh
Normal file
32
ci/before_deploy.sh
Normal file
|
@ -0,0 +1,32 @@
|
|||
# This script takes care of building your crate and packaging it for release
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local src=$(pwd) \
|
||||
stage=
|
||||
|
||||
case $TRAVIS_OS_NAME in
|
||||
linux)
|
||||
stage=$(mktemp -d)
|
||||
;;
|
||||
osx)
|
||||
stage=$(mktemp -d -t tmp)
|
||||
;;
|
||||
esac
|
||||
|
||||
test -f Cargo.lock || cargo generate-lockfile
|
||||
|
||||
cargo build --release --target $TARGET
|
||||
|
||||
# TODO Update this to package the right artifacts
|
||||
cp target/$TARGET/release/sterling $stage/
|
||||
|
||||
cd $stage
|
||||
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
|
||||
cd $src
|
||||
|
||||
rm -rf $stage
|
||||
}
|
||||
|
||||
main
|
14
ci/install.sh
Normal file
14
ci/install.sh
Normal file
|
@ -0,0 +1,14 @@
|
|||
set -ex
|
||||
|
||||
main() {
|
||||
local target=
|
||||
if [ $TRAVIS_OS_NAME = linux ]; then
|
||||
target=x86_64-unknown-linux-gnu
|
||||
sort=sort
|
||||
else
|
||||
target=x86_64-apple-darwin
|
||||
sort=gsort # for `sort --sort-version`, from brew's coreutils.
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
18
ci/script.sh
Normal file
18
ci/script.sh
Normal file
|
@ -0,0 +1,18 @@
|
|||
# This script takes care of testing your crate
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
cross build --target $TARGET --release
|
||||
|
||||
if [ ! -z $DISABLE_TESTS ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
cross test --target $TARGET
|
||||
}
|
||||
|
||||
# we don't run the "test phase" when doing deploys
|
||||
if [ -z $TRAVIS_TAG ]; then
|
||||
main
|
||||
fi
|
|
@ -10,16 +10,47 @@ use currency::Currency;
|
|||
|
||||
pub fn load_config(filename: &str) -> Result<Vec<Currency>, ConfigError> {
|
||||
let config_file = File::open(filename)?;
|
||||
let mut configs: Vec<Currency> = serde_yaml::from_reader(BufReader::new(config_file))?;
|
||||
configs.sort_by(|a, b| b.cmp(a));
|
||||
let config: Vec<Currency> = serde_yaml::from_reader(BufReader::new(config_file))?;
|
||||
|
||||
Ok(configs)
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn default_config() -> Vec<Currency> {
|
||||
pub fn parse_currency_config(
|
||||
config_result: Result<Vec<Currency>, ConfigError>,
|
||||
config_file_path: Option<&str>,
|
||||
) -> Result<Vec<Currency>, String> {
|
||||
match config_result {
|
||||
Ok(values) => Ok(values),
|
||||
Err(error) => match error.kind {
|
||||
ErrorKind::NotFound => {
|
||||
if let Some(file_path) = config_file_path {
|
||||
Err(format!(
|
||||
"Sterling Error: Can't find configuration file: \"{}\"",
|
||||
&file_path
|
||||
))
|
||||
} else {
|
||||
Ok(silver_standard_config())
|
||||
}
|
||||
}
|
||||
_ => Err(format!("Sterling Error: {}", error)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn phb_config() -> Vec<Currency> {
|
||||
vec![
|
||||
Currency::new("platinum", 1000000, "p", None, None),
|
||||
Currency::new("gold", 10000, "g", None, None),
|
||||
Currency::new("platinum", 1000, "p", None, None),
|
||||
Currency::new("gold", 100, "g", None, None),
|
||||
Currency::new("electrum", 50, "e", None, Some(true)),
|
||||
Currency::new("silver", 10, "s", None, None),
|
||||
Currency::new("copper", 1, "c", None, None),
|
||||
]
|
||||
}
|
||||
|
||||
fn silver_standard_config() -> Vec<Currency> {
|
||||
vec![
|
||||
Currency::new("platinum", 1_000_000, "p", None, None),
|
||||
Currency::new("gold", 10_000, "g", None, None),
|
||||
Currency::new("silver", 100, "s", None, None),
|
||||
Currency::new("copper", 1, "c", None, None),
|
||||
]
|
||||
|
|
208
src/convert.rs
208
src/convert.rs
|
@ -1,155 +1,109 @@
|
|||
use currency::Currency;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use regex::Regex;
|
||||
use separator::Separatable;
|
||||
|
||||
pub fn convert_to_copper(amount: usize, coin_denomination: &str) -> usize {
|
||||
match coin_denomination {
|
||||
"p" => amount * 1000,
|
||||
"g" => amount * 100,
|
||||
"e" => amount * 50,
|
||||
"s" => amount * 10,
|
||||
"c" => amount,
|
||||
_ => unreachable!("Invalid coin type; must be a valid coin found in the PHB."),
|
||||
}
|
||||
use currency::Currency;
|
||||
use lazysort::Sorted;
|
||||
|
||||
pub fn calculate_total_copper_value(
|
||||
values: &str,
|
||||
currency_regex: &Regex,
|
||||
rates: HashMap<String, usize>,
|
||||
) -> usize {
|
||||
currency_regex
|
||||
.captures_iter(&values)
|
||||
.fold(0, |sum, capture| {
|
||||
let value: usize = str::replace(&capture[1], ",", "").parse().unwrap();
|
||||
let rate: &usize = rates.get(&capture[2]).unwrap();
|
||||
let product = value * rate;
|
||||
|
||||
sum + product
|
||||
})
|
||||
}
|
||||
|
||||
pub fn calculate_total_copper_value(coins: Vec<&str>) -> Result<usize, &'static str> {
|
||||
let regex: Regex = Regex::new(r"(\d+)([cegps])").unwrap();
|
||||
for coin in coins.iter() {
|
||||
if let None = regex.captures(coin) {
|
||||
return Err(
|
||||
"Sterling Error: Invalid coin value. Make sure all coins are denoted properly.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let converted_values = coins.iter().map(|coin| {
|
||||
let captures = regex.captures(coin).unwrap();
|
||||
let amount: usize = captures[1].parse().unwrap();
|
||||
let denomination = captures[2].to_owned();
|
||||
convert_to_copper(amount, &denomination)
|
||||
});
|
||||
|
||||
Ok(converted_values.fold(0 as usize, |total, value| total + value))
|
||||
}
|
||||
|
||||
pub fn convert_currencies(copper_value: usize, currencies: Vec<Currency>) -> Vec<Currency> {
|
||||
exchange(copper_value, currencies)
|
||||
.iter()
|
||||
.filter(|c| (*c).value.unwrap_or(0) > 0)
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn exchange(copper: usize, mut currencies: Vec<Currency>) -> Vec<Currency> {
|
||||
let mut val = copper;
|
||||
pub fn exchange_currencies(
|
||||
copper_value: usize,
|
||||
currencies: &[Currency],
|
||||
print_full_name: bool,
|
||||
) -> Vec<String> {
|
||||
let mut val = copper_value;
|
||||
currencies
|
||||
.iter_mut()
|
||||
.iter()
|
||||
.sorted()
|
||||
.filter(|currency| currency.optional.unwrap_or(true))
|
||||
.map(|currency| {
|
||||
let value = val / currency.rate;
|
||||
val = val % currency.rate;
|
||||
|
||||
currency.with_value(value)
|
||||
(
|
||||
value,
|
||||
if print_full_name {
|
||||
if value > 1 {
|
||||
match (¤cy).plural {
|
||||
Some(ref plural) => String::from_str(plural).unwrap(),
|
||||
None => format!("{}s", ¤cy.name),
|
||||
}
|
||||
} else {
|
||||
String::from_str(¤cy.name).unwrap()
|
||||
}
|
||||
} else {
|
||||
String::from_str(¤cy.alias).unwrap()
|
||||
},
|
||||
)
|
||||
})
|
||||
.filter(|tuple| tuple.0 > 0)
|
||||
.map(|tuple| {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
tuple.0.separated_string(),
|
||||
if tuple.1.len() > 1 { " " } else { "" },
|
||||
tuple.1
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use convert::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::{calculate_total_copper_value, exchange_currencies};
|
||||
use currency::Currency;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref STANDARD_CURRENCIES: [Currency; 4] = [
|
||||
Currency::new("platinum", 1000000, "p", None, None),
|
||||
Currency::new("gold", 10000, "g", None, None),
|
||||
Currency::new("silver", 100, "s", None, None),
|
||||
Currency::new("copper", 1, "c", None, None),
|
||||
static ref STANDARD_CURRENCIES: Vec<Currency> = vec![
|
||||
Currency::new("guilder", 10_000, "g", None, None),
|
||||
Currency::new("shilling", 100, "s", Some("sterling".to_owned()), None),
|
||||
Currency::new("penny", 1, "p", Some("pence".to_owned()), None),
|
||||
];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_copper_to_copper() {
|
||||
assert_eq!(1, convert_to_copper(1, "c"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_silver_to_copper() {
|
||||
assert_eq!(10, convert_to_copper(1, "s"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_electrum_to_copper() {
|
||||
assert_eq!(50, convert_to_copper(1, "e"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_gold_to_copper() {
|
||||
assert_eq!(100, convert_to_copper(1, "g"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_platinum_to_copper() {
|
||||
assert_eq!(1000, convert_to_copper(1, "p"));
|
||||
static ref CURRENCY_REGEX: Regex = Regex::new(r"(\d+)([Ngsp])").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_total_copper_value() {
|
||||
let values = vec!["1p", "1g", "1e", "1s", "1c"];
|
||||
assert_eq!(1161, calculate_total_copper_value(values).unwrap());
|
||||
let rates: HashMap<String, usize> = vec![
|
||||
("g".to_owned(), 10_000usize),
|
||||
("s".to_owned(), 100usize),
|
||||
("p".to_owned(), 1usize),
|
||||
].into_iter()
|
||||
.collect();
|
||||
|
||||
let result = 10101usize;
|
||||
assert_eq!(
|
||||
result,
|
||||
calculate_total_copper_value("1g 1s 1p", &CURRENCY_REGEX, rates)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_calculate_total_copper_value_bad_inputs() {
|
||||
let values = vec!["1p", "1g", "1f", "1s", "1c"];
|
||||
assert_eq!(1161, calculate_total_copper_value(values).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_to_copper() {
|
||||
let currencies = vec![
|
||||
Currency::new("platinum", 1000000, "p", None, None).with_value(0),
|
||||
Currency::new("gold", 10000, "g", None, None).with_value(0),
|
||||
Currency::new("silver", 100, "s", None, None).with_value(0),
|
||||
Currency::new("copper", 1, "c", None, None).with_value(1),
|
||||
];
|
||||
|
||||
assert_eq!(currencies, exchange(1, STANDARD_CURRENCIES.to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_to_silver() {
|
||||
let currencies = vec![
|
||||
Currency::new("platinum", 1000000, "p", None, None).with_value(0),
|
||||
Currency::new("gold", 10000, "g", None, None).with_value(0),
|
||||
Currency::new("silver", 100, "s", None, None).with_value(1),
|
||||
Currency::new("copper", 1, "c", None, None).with_value(0),
|
||||
];
|
||||
|
||||
assert_eq!(currencies, exchange(100, STANDARD_CURRENCIES.to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_to_gold() {
|
||||
let currencies = vec![
|
||||
Currency::new("platinum", 1000000, "p", None, None).with_value(0),
|
||||
Currency::new("gold", 10000, "g", None, None).with_value(1),
|
||||
Currency::new("silver", 100, "s", None, None).with_value(0),
|
||||
Currency::new("copper", 1, "c", None, None).with_value(0),
|
||||
];
|
||||
|
||||
assert_eq!(currencies, exchange(10000, STANDARD_CURRENCIES.to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exchange_to_platinum() {
|
||||
let currencies = vec![
|
||||
Currency::new("platinum", 1000000, "p", None, None).with_value(1),
|
||||
Currency::new("gold", 10000, "g", None, None).with_value(0),
|
||||
Currency::new("silver", 100, "s", None, None).with_value(0),
|
||||
Currency::new("copper", 1, "c", None, None).with_value(0),
|
||||
];
|
||||
|
||||
assert_eq!(currencies, exchange(1000000, STANDARD_CURRENCIES.to_vec()));
|
||||
fn test_exchange_currencies() {
|
||||
let result = vec!["1g".to_owned(), "1s".to_owned(), "1p".to_owned()];
|
||||
assert_eq!(
|
||||
result,
|
||||
exchange_currencies(10101, &STANDARD_CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ use std::cmp::Ordering;
|
|||
pub struct Currency {
|
||||
pub name: String,
|
||||
pub rate: usize,
|
||||
pub value: Option<usize>,
|
||||
pub alias: String,
|
||||
pub plural: Option<String>,
|
||||
pub optional: Option<bool>,
|
||||
|
@ -21,54 +20,23 @@ impl Currency {
|
|||
Currency {
|
||||
name: name.to_owned(),
|
||||
rate,
|
||||
value: None,
|
||||
alias: alias.to_owned(),
|
||||
plural,
|
||||
optional,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_value(&mut self, value: usize) -> Currency {
|
||||
Currency {
|
||||
name: self.name.clone(),
|
||||
rate: self.rate,
|
||||
value: Some(value),
|
||||
alias: self.alias.clone(),
|
||||
plural: self.plural.clone(),
|
||||
optional: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_optional(&self) -> bool {
|
||||
match self.optional {
|
||||
Some(optional) => optional,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alias_display(&self) -> String {
|
||||
self.value.unwrap_or(0).to_string() + &self.alias
|
||||
}
|
||||
|
||||
pub fn full_display(&self) -> String {
|
||||
let mut display = self.value.unwrap_or(0).to_string() + " ";
|
||||
|
||||
if self.value.unwrap_or(0) > 1 {
|
||||
match &self.plural {
|
||||
&Some(ref plural) => display = display + &plural,
|
||||
&None => display = display + &self.name,
|
||||
}
|
||||
} else {
|
||||
display = display + &self.name;
|
||||
}
|
||||
|
||||
display
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Currency {
|
||||
fn cmp(&self, other: &Currency) -> Ordering {
|
||||
self.value.cmp(&other.value)
|
||||
other.rate.cmp(&self.rate)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +48,6 @@ impl PartialOrd for Currency {
|
|||
|
||||
impl PartialEq for Currency {
|
||||
fn eq(&self, other: &Currency) -> bool {
|
||||
self.value == other.value
|
||||
self.rate == other.rate
|
||||
}
|
||||
}
|
||||
|
|
244
src/lib.rs
Normal file
244
src/lib.rs
Normal file
|
@ -0,0 +1,244 @@
|
|||
extern crate lazysort;
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate regex;
|
||||
extern crate separator;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_yaml;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub mod config;
|
||||
mod convert;
|
||||
pub mod currency;
|
||||
|
||||
use currency::Currency;
|
||||
use regex::Regex;
|
||||
use separator::Separatable;
|
||||
|
||||
pub fn add_operation(
|
||||
augend: &str,
|
||||
addend: &str,
|
||||
custom_currency_regex: &Regex,
|
||||
currencies: &[Currency],
|
||||
print_full: bool,
|
||||
) -> String {
|
||||
let lhs =
|
||||
convert::calculate_total_copper_value(augend, custom_currency_regex, get_rates(currencies));
|
||||
|
||||
let rhs = convert::calculate_total_copper_value(
|
||||
addend,
|
||||
&custom_currency_regex,
|
||||
get_rates(currencies.as_ref()),
|
||||
);
|
||||
|
||||
convert::exchange_currencies(lhs + rhs, currencies, print_full).join(", ")
|
||||
}
|
||||
|
||||
pub fn sub_operation(
|
||||
minuend: &str,
|
||||
subtrahend: &str,
|
||||
custom_currency_regex: &Regex,
|
||||
currencies: &[Currency],
|
||||
print_full: bool,
|
||||
) -> String {
|
||||
let lhs = convert::calculate_total_copper_value(
|
||||
minuend,
|
||||
custom_currency_regex,
|
||||
get_rates(currencies),
|
||||
);
|
||||
|
||||
let rhs = convert::calculate_total_copper_value(
|
||||
subtrahend,
|
||||
custom_currency_regex,
|
||||
get_rates(currencies),
|
||||
);
|
||||
|
||||
let difference = if lhs > rhs { lhs - rhs } else { rhs - lhs };
|
||||
convert::exchange_currencies(difference, currencies, print_full).join(", ")
|
||||
}
|
||||
|
||||
pub fn mul_operation(
|
||||
multiplicand: &str,
|
||||
multiplier: usize,
|
||||
custom_currency_regex: &Regex,
|
||||
currencies: &[Currency],
|
||||
print_full: bool,
|
||||
) -> String {
|
||||
let lhs = convert::calculate_total_copper_value(
|
||||
multiplicand,
|
||||
custom_currency_regex,
|
||||
get_rates(currencies),
|
||||
);
|
||||
|
||||
convert::exchange_currencies(lhs * multiplier, currencies, print_full).join(", ")
|
||||
}
|
||||
|
||||
pub fn div_operation(
|
||||
dividend: &str,
|
||||
divisor: usize,
|
||||
custom_currency_regex: &Regex,
|
||||
currencies: &[Currency],
|
||||
print_full: bool,
|
||||
) -> String {
|
||||
let lhs = convert::calculate_total_copper_value(
|
||||
dividend,
|
||||
custom_currency_regex,
|
||||
get_rates(currencies),
|
||||
);
|
||||
|
||||
convert::exchange_currencies(lhs / divisor, currencies, print_full).join(", ")
|
||||
}
|
||||
|
||||
pub fn copper_operation(
|
||||
values: &str,
|
||||
custom_currency_regex: &Regex,
|
||||
currencies: &[Currency],
|
||||
) -> String {
|
||||
let copper_value =
|
||||
convert::calculate_total_copper_value(values, custom_currency_regex, get_rates(currencies));
|
||||
|
||||
format!("{}c", copper_value.separated_string())
|
||||
}
|
||||
|
||||
pub fn default_operation(values: &str, currencies: &[Currency], print_full: bool) -> String {
|
||||
let copper_value = convert::calculate_total_copper_value(
|
||||
values,
|
||||
&Regex::new(r"(0|(?:[1-9](?:\d+|\d{0,2}(?:,\d{3})*)))+([cegps])").unwrap(),
|
||||
get_rates(config::phb_config().as_ref()),
|
||||
);
|
||||
|
||||
let exchanged_currencies = convert::exchange_currencies(copper_value, currencies, print_full);
|
||||
|
||||
exchanged_currencies.join(", ")
|
||||
}
|
||||
|
||||
fn get_rates(currencies: &[Currency]) -> HashMap<String, usize> {
|
||||
currencies
|
||||
.iter()
|
||||
.map(|c| c.alias.clone())
|
||||
.zip(currencies.iter().map(|c| c.rate))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use currency::Currency;
|
||||
use regex::Regex;
|
||||
|
||||
use super::{
|
||||
add_operation, copper_operation, default_operation, div_operation, get_rates,
|
||||
mul_operation, sub_operation,
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENCIES: Vec<Currency> = vec![
|
||||
Currency::new("penny", 1, "p", Some("pence".to_owned()), None),
|
||||
Currency::new("shilling", 100, "s", Some("sterling".to_owned()), None),
|
||||
];
|
||||
static ref CUSTOM_CURRENCY_REGEX: Regex = Regex::new(&format!(
|
||||
"(\\d+)([{}])",
|
||||
CURRENCIES
|
||||
.iter()
|
||||
.map(|c| c.alias.clone())
|
||||
.fold(String::new(), |group, a| group + &a)
|
||||
)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_rates() {
|
||||
let rates: HashMap<_, _> = vec![("p".to_owned(), 1usize), ("s".to_owned(), 100usize)]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(rates, get_rates(&CURRENCIES));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_operation_same_currencies() {
|
||||
let result = "3p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
add_operation("1p", "2p", &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_operation_diff_currencies() {
|
||||
let result = "1s, 1p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
add_operation("1s", "1p", &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_operation_smaller_subtrahend() {
|
||||
let result = "1p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
sub_operation("2p", "1p", &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_operation_larger_subtrahend() {
|
||||
let result = "1p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
sub_operation("1p", "2p", &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mul_operation() {
|
||||
let result = "6p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
mul_operation("3p", 2, &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_div_operation_even_dividend() {
|
||||
let result = "2p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
div_operation("4p", 2, &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_div_operation_odd_dividend() {
|
||||
let result = "1p".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
div_operation("3p", 2, &CUSTOM_CURRENCY_REGEX, &CURRENCIES, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copper_operation() {
|
||||
let result = "103c".to_owned();
|
||||
assert_eq!(
|
||||
result,
|
||||
copper_operation("1s 3p", &CUSTOM_CURRENCY_REGEX, &CURRENCIES)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_operation() {
|
||||
let result = "1 shilling";
|
||||
assert_eq!(result, default_operation("1g", &CURRENCIES, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_operation_plural_output() {
|
||||
let result = "2 sterling";
|
||||
assert_eq!(result, default_operation("2g", &CURRENCIES, true));
|
||||
}
|
||||
}
|
241
src/main.rs
241
src/main.rs
|
@ -1,45 +1,46 @@
|
|||
#[macro_use]
|
||||
extern crate clap;
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate regex;
|
||||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_yaml;
|
||||
extern crate sterling_ops;
|
||||
|
||||
mod config;
|
||||
mod convert;
|
||||
mod currency;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
use std::process;
|
||||
|
||||
use config::ConfigError;
|
||||
use currency::Currency;
|
||||
use regex::Regex;
|
||||
use sterling_ops::config;
|
||||
use sterling_ops::currency::Currency;
|
||||
use sterling_ops::*;
|
||||
|
||||
fn main() {
|
||||
let app = clap_app!(sterling =>
|
||||
(version: env!("CARGO_PKG_VERSION"))
|
||||
(about: "Converts a given D&D 5e currency value to the Silver Standard.")
|
||||
(@arg CONFIG: -c --config +takes_value "Specify location of config file; defaults to './sterling-conf.yml'.")
|
||||
(@arg PRINT_FULL: -f --full "Print currencies with full name, rather than with alias.")
|
||||
(@arg OPTIONAL: -o --optional "Include currencies marked as optional when converting.")
|
||||
(@arg PRINT_FULL: -f --full "Print currencies with their full name, rather than with their alias")
|
||||
(@arg OPTIONAL: -o --optional "Include currencies marked as optional when converting")
|
||||
(@arg VALUE: ... "The value to be converted; should be suffixed with the coin's short-hand abbreviation, i.e. p, g, e, s, or c.")
|
||||
(@subcommand add =>
|
||||
(about: "Add two currency amounts together; uses the currencies defined in your config file")
|
||||
(@arg AUGEND: +required "The augend of the addition function; i.e. the left side")
|
||||
(@arg ADDEND: +required "The addend of the addition function; i.e. the right side")
|
||||
(@arg PRINT_FULL: -f --full "Print currencies with full name, rather than with alias.")
|
||||
(@arg AUGEND: +required "The augend of the addition function")
|
||||
(@arg ADDEND: +required "The addend of the addition function")
|
||||
)
|
||||
(@subcommand sub =>
|
||||
(about: "Subtract two currency amounts from one another; uses the currencies defined in your config file")
|
||||
(@arg MINUEND: +required "The minuend of the subtraction function; i.e. the left side")
|
||||
(@arg SUBTRAHEND: +required "The subtrahend of the subtraction function; i.e. the right side")
|
||||
(@arg PRINT_FULL: -f --full "Print currencies with full name, rather than with alias.")
|
||||
(@arg MINUEND: +required "The minuend of the subtraction function")
|
||||
(@arg SUBTRAHEND: +required "The subtrahend of the subtraction function")
|
||||
)
|
||||
(@subcommand mul =>
|
||||
(about: "Multiply a scalar multiplicand by a currency amount; uses the currencies defined in your config file")
|
||||
(@arg MULTIPLIER: +required "The scalar multiplier of the multiplication function")
|
||||
(@arg MULTIPLICAND: +required ... "The currency values to be multiplied")
|
||||
)
|
||||
(@subcommand div =>
|
||||
(about: "Divide a currency amount by some scalar divisor; uses the currencies defined in your config file")
|
||||
(@arg DIVISOR: +required "The scalar divisor of the division function")
|
||||
(@arg DIVIDEND: +required ... "The currency values to be divided")
|
||||
)
|
||||
(@subcommand copper =>
|
||||
(about: "Calculate the copper value of a custom currency")
|
||||
(@arg VALUE: +required ... "The custom currency value")
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -50,24 +51,22 @@ fn main() {
|
|||
});
|
||||
|
||||
let currencies: Vec<Currency> =
|
||||
match parse_currency_config(config_result, matches.value_of("CONFIG")) {
|
||||
match config::parse_currency_config(config_result, matches.value_of("CONFIG")) {
|
||||
Ok(currencies) => currencies
|
||||
.iter()
|
||||
.into_iter()
|
||||
.filter(|c| {
|
||||
let has_add_subcommand = match matches.subcommand_matches("add") {
|
||||
let is_sub_command = match matches.subcommand_name() {
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
};
|
||||
|
||||
if has_add_subcommand {
|
||||
if is_sub_command {
|
||||
true
|
||||
} else if !matches.is_present("OPTIONAL") {
|
||||
!c.is_optional()
|
||||
} else {
|
||||
true
|
||||
(!matches.is_present("OPTIONAL") && !c.is_optional())
|
||||
|| matches.is_present("OPTIONAL")
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect(),
|
||||
Err(error) => {
|
||||
eprintln!("{}", error);
|
||||
|
@ -75,117 +74,81 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("add") {
|
||||
let (lhs, rhs) = get_copper_value(
|
||||
let custom_currency_regex: Regex = Regex::new(&format!(
|
||||
"(\\d+)([{}])",
|
||||
currencies
|
||||
.iter()
|
||||
.map(|c| c.alias.clone())
|
||||
.fold(String::new(), |group, a| group + &a)
|
||||
)).unwrap();
|
||||
|
||||
let operation_result = match matches.subcommand() {
|
||||
("add", Some(command)) => add_operation(
|
||||
command.value_of("AUGEND").unwrap(),
|
||||
command.value_of("ADDEND").unwrap(),
|
||||
&custom_currency_regex,
|
||||
¤cies,
|
||||
matches.value_of("AUGEND").unwrap(),
|
||||
matches.value_of("ADDEND").unwrap(),
|
||||
);
|
||||
|
||||
let converted_currencies = convert::convert_currencies(lhs + rhs, currencies);
|
||||
let display_strings: Vec<String> =
|
||||
create_display_strings(converted_currencies, matches.is_present("PRINT_FULL"));
|
||||
|
||||
println!("{}", (&display_strings).join(", "));
|
||||
} else if let Some(matches) = matches.subcommand_matches("sub") {
|
||||
let (lhs, rhs) = get_copper_value(
|
||||
matches.is_present("PRINT_FULL"),
|
||||
),
|
||||
("sub", Some(command)) => sub_operation(
|
||||
command.value_of("MINUEND").unwrap(),
|
||||
command.value_of("SUBTRAHEND").unwrap(),
|
||||
&custom_currency_regex,
|
||||
¤cies,
|
||||
matches.value_of("MINUEND").unwrap(),
|
||||
matches.value_of("SUBTRAHEND").unwrap(),
|
||||
);
|
||||
|
||||
let difference = if lhs > rhs { lhs - rhs } else { rhs - lhs };
|
||||
|
||||
let converted_currencies = convert::convert_currencies(difference, currencies);
|
||||
let display_strings: Vec<String> =
|
||||
create_display_strings(converted_currencies, matches.is_present("PRINT_FULL"));
|
||||
|
||||
println!("{}", (&display_strings).join(", "));
|
||||
} else if let Some(values) = matches.values_of("VALUE") {
|
||||
let coins: Vec<&str> = values.collect();
|
||||
let total_copper_value = match convert::calculate_total_copper_value(coins) {
|
||||
Ok(total_copper_value) => total_copper_value,
|
||||
Err(err) => {
|
||||
eprintln!("{}", err);
|
||||
matches.is_present("PRINT_FULL"),
|
||||
),
|
||||
("mul", Some(command)) => mul_operation(
|
||||
&command
|
||||
.values_of("MULTIPLICAND")
|
||||
.unwrap()
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" "),
|
||||
command
|
||||
.value_of("MULTIPLIER")
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap(),
|
||||
&custom_currency_regex,
|
||||
¤cies,
|
||||
matches.is_present("PRINT_FULL"),
|
||||
),
|
||||
("div", Some(command)) => div_operation(
|
||||
&command
|
||||
.values_of("DIVIDEND")
|
||||
.unwrap()
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" "),
|
||||
command
|
||||
.value_of("DIVISOR")
|
||||
.unwrap()
|
||||
.parse::<usize>()
|
||||
.unwrap(),
|
||||
&custom_currency_regex,
|
||||
¤cies,
|
||||
matches.is_present("PRINT_FULL"),
|
||||
),
|
||||
("copper", Some(command)) => copper_operation(
|
||||
&command
|
||||
.values_of("VALUE")
|
||||
.unwrap()
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" "),
|
||||
&custom_currency_regex,
|
||||
¤cies,
|
||||
),
|
||||
_ => {
|
||||
if let Some(values) = matches.values_of("VALUE") {
|
||||
default_operation(
|
||||
&values.collect::<Vec<&str>>().join(" "),
|
||||
¤cies,
|
||||
matches.is_present("PRINT_FULL"),
|
||||
)
|
||||
} else {
|
||||
eprintln!("Sterling Error: please enter at least one value; should be suffixed with the coin's short-hand abbreviation, i.e. p, g, e, s, or c.");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let converted_currencies = convert::convert_currencies(total_copper_value, currencies);
|
||||
let display_strings: Vec<String> =
|
||||
create_display_strings(converted_currencies, matches.is_present("PRINT_FULL"));
|
||||
|
||||
println!("{}", (&display_strings).join(", "));
|
||||
} else {
|
||||
eprintln!("Please enter at least one value; should be suffixed with the coin's short-hand abbreviation, i.e. p, g, e, s, or c.");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_currency_config(
|
||||
config_result: Result<Vec<Currency>, ConfigError>,
|
||||
config_file_path: Option<&str>,
|
||||
) -> Result<Vec<Currency>, String> {
|
||||
match config_result {
|
||||
Ok(values) => Ok(values),
|
||||
Err(error) => match error.kind {
|
||||
ErrorKind::NotFound => {
|
||||
if let Some(file_path) = config_file_path {
|
||||
Err(format!(
|
||||
"Sterling Error: Can't find configuration file: \"{}\"",
|
||||
&file_path
|
||||
))
|
||||
} else {
|
||||
Ok(config::default_config())
|
||||
}
|
||||
}
|
||||
_ => Err(format!("Sterling Error: {}", error)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn create_display_strings(converted_currencies: Vec<Currency>, is_print_full: bool) -> Vec<String> {
|
||||
converted_currencies
|
||||
.iter()
|
||||
.map(|c| {
|
||||
if is_print_full {
|
||||
c.full_display()
|
||||
} else {
|
||||
c.alias_display()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_copper_value(currencies: &[Currency], lhs: &str, rhs: &str) -> (usize, usize) {
|
||||
let mut rates: HashMap<String, usize> = HashMap::with_capacity(currencies.len());
|
||||
for currency in currencies {
|
||||
rates.insert(currency.alias.clone(), currency.rate);
|
||||
}
|
||||
|
||||
let aliases = currencies
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|c| c.alias)
|
||||
.fold(String::new(), |group, a| group + &a);
|
||||
|
||||
let regex: Regex = Regex::new(&format!("(\\d+)([{}])", aliases)).unwrap();
|
||||
|
||||
let left_hand_side: usize = regex.captures_iter(lhs).fold(0, |sum, cap| {
|
||||
let value: usize = cap[1].parse().unwrap();
|
||||
let rate: usize = *rates.get(&cap[2]).unwrap();
|
||||
let product = value * rate;
|
||||
|
||||
sum + product
|
||||
});
|
||||
|
||||
let right_hand_side: usize = regex.captures_iter(rhs).fold(0, |sum, cap| {
|
||||
let value: usize = cap[1].parse().unwrap();
|
||||
let rate: usize = *rates.get(&cap[2]).unwrap();
|
||||
let product = value * rate;
|
||||
|
||||
sum + product
|
||||
});
|
||||
|
||||
(left_hand_side, right_hand_side)
|
||||
println!("{}", operation_result);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue