Release/0.3.0

This commit is contained in:
Zachary Dziura 2018-05-16 20:58:35 +00:00
parent 26aecf26ec
commit abfb69e82d
12 changed files with 410 additions and 191 deletions

2
.gitignore vendored
View file

@ -21,3 +21,5 @@ Cargo.lock
.history .history
# End of https://www.gitignore.io/api/rust # End of https://www.gitignore.io/api/rust
sterling-conf.yml

24
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,24 @@
image: "rust:latest"
stages:
- test
- deploy
cargo:test:
stage: test
only:
- master
script:
- cargo test --verbose
cargo:build:
stage: deploy
only:
- master
script:
- cargo build --release
cache:
paths:
- target/
artifacts:
paths:
- target/release/sterling

27
.travis.yml Normal file
View file

@ -0,0 +1,27 @@
language: rust
os:
- osx
branches:
only:
- master
rust:
- stable
script:
- cargo test --verbose
- cargo build --release
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"
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
on:
repo: zcdziura/sterling
branch: master
addons:
artifacts: true

View file

@ -1,6 +1,6 @@
[package] [package]
name = "sterling" name = "sterling"
version = "0.2.0" version = "0.3.0"
description = "Converts a given D&D 5e currency value to the Silver Standard." description = "Converts a given D&D 5e currency value to the Silver Standard."
authors = ["Zachary Dziura <zcdziura@gmail.com>"] authors = ["Zachary Dziura <zcdziura@gmail.com>"]
readme = "README.md" readme = "README.md"
@ -9,9 +9,9 @@ repository = "https://gitlab.com/zcdziura/sterling"
keywords = ["dnd", "coins", "converter", "currency", "5e"] keywords = ["dnd", "coins", "converter", "currency", "5e"]
[dependencies] [dependencies]
clap = "2.31.1" clap = "2.31"
lazy_static = "1.0.0" lazy_static = "1.0"
regex = "0.2" regex = "1.0"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_yaml = "0.7" serde_yaml = "0.7"

View file

@ -8,22 +8,28 @@ and [I make Silver Standard for 5th Edition (Spreadsheets.)](https://www.reddit.
``` ```
USAGE: USAGE:
sterling.exe [FLAGS] [OPTIONS] [VALUE]... sterling [FLAGS] [OPTIONS] [VALUE]... [SUBCOMMAND]
FLAGS: FLAGS:
-f, --full Print currencies with full name, rather than with alias. -o, --optional Include currencies marked as optional when converting.
-h, --help Prints help information -f, --full Print currencies with full name, rather than with alias.
-V, --version Prints version information -h, --help Prints help information
-V, --version Prints version information
OPTIONS: OPTIONS:
-c, --config <CONFIG> Specify location of config file; defaults to './sterling-conf.yml'. -c, --config <CONFIG> Specify location of config file; defaults to './sterling-conf.yml'.
ARGS: ARGS:
<VALUE>... The value to be converted; should be suffixed with the coin's short-hand <VALUE>... The value to be converted; should be suffixed with the coin's short-hand abbreviation, i.e. p, g,
abbreviation, i.e. p, g, e, s, or c. e, s, or c.
SUBCOMMANDS:
add Add two currency amounts together; uses the currencies defined in your config file
help Prints this message or the help of the given subcommand(s)
sub Subtract two currency amounts from one another; uses the currencies defined in your config file
``` ```
## Examples ## Converting Currency Examples
``` ```
// Convert one hundred platinum coins: // Convert one hundred platinum coins:
@ -41,22 +47,43 @@ sterling --full 1p 36g 12e 82s 469c // 64 silver, 89 copper
// Convert one platinum, thirty-six gold, twelve electrum, eighty-two silver, and four hundred // Convert one platinum, thirty-six gold, twelve electrum, eighty-two silver, and four hundred
// sixty-nine copper coins, printing the full names of the coins, using the custom config file // sixty-nine copper coins, printing the full names of the coins, using the custom config file
// detailed below. // detailed below, including optional currencies
sterling --full -c "~/Documents/D&D/sterling-conf.yml" 1p 36g 12e 82s 469c // 27 sterling, 9 farthing sterling --full -o -c "~/Documents/D&D/sterling-conf.yml" 1p 36g 12e 82s 469c // 27 sterling, 9 farthing
``` ```
## Adding and Subtracting Currency Examples
```
// Add together ten and twenty pense, using the custom config file detailed below
sterling add "10p" "20p" // 1s, 10p
// Subtract two sterling and ten pence from one florin
sterling sub "1F" "2s 10p" --full // 33 sterling, 10 pence
// Subtract one florin from two sterling and ten pence. Note that, regardless of order, the smaller
// value is ALWAYS subtracted from the larger value.
sterling sub "2s 10p" "1F" --full // 33 sterling, 10 pence
```
Note that `sterling` doesn't allow for negative currencies. Therefore, when subtracting currencies,
the smaller currency value will always be subtracted from the larger currency value, regardless of
the order of the currencies in the `sub` command.
## Custom Currencies ## Custom Currencies
`sterling` allows for user-defined currencies, with their own names and conversion rates. By `sterling` allows for user-defined currencies, with their own names and conversion rates. By
default, `sterling` will look at a file within the current directory called `sterling-conf.yml`, or default, `sterling` will look at a file within the current directory called `sterling-conf.yml`, or
in whatever location as supplied by the `-c` flag. Below is an example `sterling-conf.yml` file, in whatever location as supplied by the `-c` flag. You can also specify that a currency be optional,
showing the actual currencies that I use within my own campaign! which will prevent that currency from being used when converting values, unless the `-o` flag is
passed. Below is an example `sterling-conf.yml` file, showing the actual currencies that I use
within my own campaign!
``` ```
- -
name: "florin" name: "florin"
rate: 8640 rate: 8640
alias: "F" alias: "F"
optional: true
- -
name: "sterling" name: "sterling"
rate: 240 rate: 240
@ -94,15 +121,16 @@ while a suit of heavy plate armor equals fifteen gold coins, rather than fifteen
## Installation ## Installation
Make sure that you first have `rust` and `cargo` installed onto your computer before downloading The easiest way to install `sterling` is to do so with `cargo`, the build tool that's installed
`sterling`. Just follow the simple along with the `rust` compiler. If you already have `rust` and `cargo` installed onto your computer,
[Installation Guide](https://doc.rust-lang.org/cargo/getting-started/installation.html) on the simply run the following command from a command prompt:
official Rust language website to install both programs.
Once `rust` and `cargo` are installed onto your computer, run the following command: ```
$ cargo install sterling
```
`cargo install sterling` If you do not have the `rust` compiler installed, you can also find pre-built binaries for 64-bit
Windows, macOS, and Linux computers in the "Tags" navigation link, which is displayed above this
This will install `sterling` into the `.cargo/bin` directory within your User directory README. Simply download the correct binary for your computer's operating system, extract it
(`/home/YOUR_USER_NAME` on Linux and macOS, `C:\Users\YOUR_USER_NAME` on Windows). Be sure to add somewhere into your file system (such as a "bin" folder within your user directory), and add that
this directory to your PATH. location to your system's PATH.

29
appveyor.yml Normal file
View file

@ -0,0 +1,29 @@
os: Visual Studio 2015
branches:
only:
- master
environment:
matrix:
- channel: stable
target: x86_64-pc-windows-gnu
artifacts:
- path: target/release/sterling.exe
name: sterling
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
test_script:
- cargo test --verbose
- cargo build --release
deploy:
provider: GitHub
description: ''
auth_token:
secure: bvA/4J1T0h65ur6tsg6k/wlZFjP3qr2QsyRsmGMEmm7DOF61xmzTnjuBcPjQYrba
artifact: target/release/sterling.exe
on:
branch: master

View file

@ -1,6 +1,6 @@
use std::convert::From;
use std::error::Error; use std::error::Error;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::convert::From;
use std::fs::File; use std::fs::File;
use std::io::{self, BufReader, ErrorKind}; use std::io::{self, BufReader, ErrorKind};
@ -18,10 +18,10 @@ pub fn load_config(filename: &str) -> Result<Vec<Currency>, ConfigError> {
pub fn default_config() -> Vec<Currency> { pub fn default_config() -> Vec<Currency> {
vec![ vec![
Currency::new("platinum", 1000000, "p", None), Currency::new("platinum", 1000000, "p", None, None),
Currency::new("gold", 10000, "g", None), Currency::new("gold", 10000, "g", None, None),
Currency::new("silver", 100, "s", None), Currency::new("silver", 100, "s", None, None),
Currency::new("copper", 1, "c", None), Currency::new("copper", 1, "c", None, None),
] ]
} }

155
src/convert.rs Normal file
View file

@ -0,0 +1,155 @@
use currency::Currency;
use regex::Regex;
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."),
}
}
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;
currencies
.iter_mut()
.map(|currency| {
let value = val / currency.rate;
val = val % currency.rate;
currency.with_value(value)
})
.collect()
}
#[cfg(test)]
mod tests {
use convert::*;
use currency::Currency;
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),
];
}
#[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"));
}
#[test]
fn test_calculate_total_copper_value() {
let values = vec!["1p", "1g", "1e", "1s", "1c"];
assert_eq!(1161, calculate_total_copper_value(values).unwrap());
}
#[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()));
}
}

View file

@ -7,16 +7,24 @@ pub struct Currency {
pub value: Option<usize>, pub value: Option<usize>,
pub alias: String, pub alias: String,
pub plural: Option<String>, pub plural: Option<String>,
pub optional: Option<bool>,
} }
impl Currency { impl Currency {
pub fn new(name: &str, rate: usize, alias: &str, plural: Option<String>) -> Currency { pub fn new(
name: &str,
rate: usize,
alias: &str,
plural: Option<String>,
optional: Option<bool>,
) -> Currency {
Currency { Currency {
name: name.to_owned(), name: name.to_owned(),
rate, rate,
value: None, value: None,
alias: alias.to_owned(), alias: alias.to_owned(),
plural, plural,
optional,
} }
} }
@ -27,6 +35,14 @@ impl Currency {
value: Some(value), value: Some(value),
alias: self.alias.clone(), alias: self.alias.clone(),
plural: self.plural.clone(), plural: self.plural.clone(),
optional: None,
}
}
pub fn is_optional(&self) -> bool {
match self.optional {
Some(optional) => optional,
None => false,
} }
} }
@ -50,10 +66,6 @@ impl Currency {
} }
} }
// impl Display for Currency {
// fn
// }
impl Ord for Currency { impl Ord for Currency {
fn cmp(&self, other: &Currency) -> Ordering { fn cmp(&self, other: &Currency) -> Ordering {
self.value.cmp(&other.value) self.value.cmp(&other.value)

View file

@ -10,8 +10,10 @@ extern crate serde_derive;
extern crate serde_yaml; extern crate serde_yaml;
mod config; mod config;
mod convert;
mod currency; mod currency;
use std::collections::HashMap;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::process; use std::process;
@ -21,11 +23,24 @@ use regex::Regex;
fn main() { fn main() {
let app = clap_app!(sterling => let app = clap_app!(sterling =>
(version: "0.2.0") (version: env!("CARGO_PKG_VERSION"))
(about: "Converts a given D&D 5e currency value to the Silver Standard.") (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 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 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 VALUE: ... "The value to be converted; should be suffixed with the coin's short-hand abbreviation, i.e. p, g, e, s, or c.") (@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.")
)
(@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.")
)
); );
let matches = app.get_matches(); let matches = app.get_matches();
@ -34,17 +49,61 @@ fn main() {
None => "./sterling-conf.yml", None => "./sterling-conf.yml",
}); });
let currencies = match parse_currency_config(config_result, matches.value_of("CONFIG")) { let currencies: Vec<Currency> =
Ok(currencies) => currencies, match parse_currency_config(config_result, matches.value_of("CONFIG")) {
Err(error) => { Ok(currencies) => currencies
eprintln!("{}", error); .iter()
process::exit(1); .filter(|c| {
} let has_add_subcommand = match matches.subcommand_matches("add") {
}; Some(_) => true,
None => false,
};
if let Some(values) = matches.values_of("VALUE") { if has_add_subcommand {
true
} else if !matches.is_present("OPTIONAL") {
!c.is_optional()
} else {
true
}
})
.cloned()
.collect(),
Err(error) => {
eprintln!("{}", error);
process::exit(1);
}
};
if let Some(matches) = matches.subcommand_matches("add") {
let (lhs, rhs) = get_copper_value(
&currencies,
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(
&currencies,
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 coins: Vec<&str> = values.collect();
let total_copper_value = match calculate_total_copper_value(coins) { let total_copper_value = match convert::calculate_total_copper_value(coins) {
Ok(total_copper_value) => total_copper_value, Ok(total_copper_value) => total_copper_value,
Err(err) => { Err(err) => {
eprintln!("{}", err); eprintln!("{}", err);
@ -52,7 +111,7 @@ fn main() {
} }
}; };
let converted_currencies = convert_currencies(total_copper_value, currencies); let converted_currencies = convert::convert_currencies(total_copper_value, currencies);
let display_strings: Vec<String> = let display_strings: Vec<String> =
create_display_strings(converted_currencies, matches.is_present("PRINT_FULL")); create_display_strings(converted_currencies, matches.is_present("PRINT_FULL"));
@ -72,68 +131,19 @@ fn parse_currency_config(
Err(error) => match error.kind { Err(error) => match error.kind {
ErrorKind::NotFound => { ErrorKind::NotFound => {
if let Some(file_path) = config_file_path { if let Some(file_path) = config_file_path {
Err(format!("Sterling Error: Can't find configuration file: \"{}\"", &file_path)) Err(format!(
"Sterling Error: Can't find configuration file: \"{}\"",
&file_path
))
} else { } else {
Ok(config::default_config()) Ok(config::default_config())
} }
}, }
_ => Err(format!("Sterling Error: {}", error)), _ => Err(format!("Sterling Error: {}", error)),
}, },
} }
} }
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."),
}
}
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))
}
fn exchange(copper: usize, mut currencies: Vec<Currency>) -> Vec<Currency> {
let mut val = copper;
currencies
.iter_mut()
.map(|currency| {
let value = val / currency.rate;
val = val % currency.rate;
currency.with_value(value)
})
.collect()
}
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()
}
fn create_display_strings(converted_currencies: Vec<Currency>, is_print_full: bool) -> Vec<String> { fn create_display_strings(converted_currencies: Vec<Currency>, is_print_full: bool) -> Vec<String> {
converted_currencies converted_currencies
.iter() .iter()
@ -147,103 +157,35 @@ fn create_display_strings(converted_currencies: Vec<Currency>, is_print_full: bo
.collect() .collect()
} }
#[cfg(test)] fn get_copper_value(currencies: &[Currency], lhs: &str, rhs: &str) -> (usize, usize) {
mod tests { let mut rates: HashMap<String, usize> = HashMap::with_capacity(currencies.len());
use super::*; for currency in currencies {
use currency::Currency; rates.insert(currency.alias.clone(), currency.rate);
lazy_static! {
static ref STANDARD_CURRENCIES: [Currency; 4] = [
Currency::new("platinum", 1000000, "p", None),
Currency::new("gold", 10000, "g", None),
Currency::new("silver", 100, "s", None),
Currency::new("copper", 1, "c", None),
];
} }
#[test] let aliases = currencies
fn test_convert_copper_to_copper() { .iter()
assert_eq!(1, convert_to_copper(1, "c")); .cloned()
} .map(|c| c.alias)
.fold(String::new(), |group, a| group + &a);
#[test] let regex: Regex = Regex::new(&format!("(\\d+)([{}])", aliases)).unwrap();
fn test_convert_silver_to_copper() {
assert_eq!(10, convert_to_copper(1, "s"));
}
#[test] let left_hand_side: usize = regex.captures_iter(lhs).fold(0, |sum, cap| {
fn test_convert_electrum_to_copper() { let value: usize = cap[1].parse().unwrap();
assert_eq!(50, convert_to_copper(1, "e")); let rate: usize = *rates.get(&cap[2]).unwrap();
} let product = value * rate;
#[test] sum + product
fn test_convert_gold_to_copper() { });
assert_eq!(100, convert_to_copper(1, "g"));
}
#[test] let right_hand_side: usize = regex.captures_iter(rhs).fold(0, |sum, cap| {
fn test_convert_platinum_to_copper() { let value: usize = cap[1].parse().unwrap();
assert_eq!(1000, convert_to_copper(1, "p")); let rate: usize = *rates.get(&cap[2]).unwrap();
} let product = value * rate;
#[test] sum + product
fn test_calculate_total_copper_value() { });
let values = vec!["1p", "1g", "1e", "1s", "1c"];
assert_eq!(1161, calculate_total_copper_value(values).unwrap());
}
#[test] (left_hand_side, right_hand_side)
#[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).with_value(0),
Currency::new("gold", 10000, "g", None).with_value(0),
Currency::new("silver", 100, "s", None).with_value(0),
Currency::new("copper", 1, "c", 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).with_value(0),
Currency::new("gold", 10000, "g", None).with_value(0),
Currency::new("silver", 100, "s", None).with_value(1),
Currency::new("copper", 1, "c", 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).with_value(0),
Currency::new("gold", 10000, "g", None).with_value(1),
Currency::new("silver", 100, "s", None).with_value(0),
Currency::new("copper", 1, "c", 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).with_value(1),
Currency::new("gold", 10000, "g", None).with_value(0),
Currency::new("silver", 100, "s", None).with_value(0),
Currency::new("copper", 1, "c", None).with_value(0),
];
assert_eq!(currencies, exchange(1000000, STANDARD_CURRENCIES.to_vec()));
}
} }