From abfb69e82dc017a36af44cb4b2c23bacdd0db2a7 Mon Sep 17 00:00:00 2001 From: Zachary Dziura Date: Wed, 16 May 2018 20:58:35 +0000 Subject: [PATCH] Release/0.3.0 --- .gitignore | 2 + .gitlab-ci.yml | 24 +++ .travis.yml | 27 +++ Cargo.toml | 8 +- MIT.md => LICENSE-MIT.md | 0 UNLICENSE.md => LICENSE-UNLICENSE.md | 0 README.md | 70 +++++--- appveyor.yml | 29 +++ src/config.rs | 10 +- src/convert.rs | 155 ++++++++++++++++ src/currency.rs | 22 ++- src/main.rs | 254 +++++++++++---------------- 12 files changed, 410 insertions(+), 191 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 .travis.yml rename MIT.md => LICENSE-MIT.md (100%) rename UNLICENSE.md => LICENSE-UNLICENSE.md (100%) create mode 100644 appveyor.yml create mode 100644 src/convert.rs diff --git a/.gitignore b/.gitignore index ded6399..290a69d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ Cargo.lock .history # End of https://www.gitignore.io/api/rust + +sterling-conf.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..dc4765a --- /dev/null +++ b/.gitlab-ci.yml @@ -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 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8043cbb --- /dev/null +++ b/.travis.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 0323977..69ef54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sterling" -version = "0.2.0" +version = "0.3.0" description = "Converts a given D&D 5e currency value to the Silver Standard." authors = ["Zachary Dziura "] readme = "README.md" @@ -9,9 +9,9 @@ repository = "https://gitlab.com/zcdziura/sterling" keywords = ["dnd", "coins", "converter", "currency", "5e"] [dependencies] -clap = "2.31.1" -lazy_static = "1.0.0" -regex = "0.2" +clap = "2.31" +lazy_static = "1.0" +regex = "1.0" serde = "1.0" serde_derive = "1.0" serde_yaml = "0.7" diff --git a/MIT.md b/LICENSE-MIT.md similarity index 100% rename from MIT.md rename to LICENSE-MIT.md diff --git a/UNLICENSE.md b/LICENSE-UNLICENSE.md similarity index 100% rename from UNLICENSE.md rename to LICENSE-UNLICENSE.md diff --git a/README.md b/README.md index 9bc37a3..47c2af4 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,28 @@ and [I make Silver Standard for 5th Edition (Spreadsheets.)](https://www.reddit. ``` USAGE: - sterling.exe [FLAGS] [OPTIONS] [VALUE]... + sterling [FLAGS] [OPTIONS] [VALUE]... [SUBCOMMAND] FLAGS: - -f, --full Print currencies with full name, rather than with alias. - -h, --help Prints help information - -V, --version Prints version information + -o, --optional Include currencies marked as optional when converting. + -f, --full Print currencies with full name, rather than with alias. + -h, --help Prints help information + -V, --version Prints version information OPTIONS: -c, --config Specify location of config file; defaults to './sterling-conf.yml'. ARGS: - ... The value to be converted; should be suffixed with the coin's short-hand - abbreviation, i.e. p, g, e, s, or c. + ... The value to be converted; should be suffixed with the coin's short-hand abbreviation, i.e. p, g, + 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: @@ -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 // sixty-nine copper coins, printing the full names of the coins, using the custom config file -// detailed below. -sterling --full -c "~/Documents/D&D/sterling-conf.yml" 1p 36g 12e 82s 469c // 27 sterling, 9 farthing +// detailed below, including optional currencies +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 `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 -in whatever location as supplied by the `-c` flag. Below is an example `sterling-conf.yml` file, -showing the actual currencies that I use within my own campaign! +in whatever location as supplied by the `-c` flag. You can also specify that a currency be optional, +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" rate: 8640 alias: "F" + optional: true - name: "sterling" rate: 240 @@ -94,15 +121,16 @@ while a suit of heavy plate armor equals fifteen gold coins, rather than fifteen ## Installation -Make sure that you first have `rust` and `cargo` installed onto your computer before downloading -`sterling`. Just follow the simple -[Installation Guide](https://doc.rust-lang.org/cargo/getting-started/installation.html) on the -official Rust language website to install both programs. +The easiest way to install `sterling` is to do so with `cargo`, the build tool that's installed +along with the `rust` compiler. If you already have `rust` and `cargo` installed onto your computer, +simply run the following command from a command prompt: -Once `rust` and `cargo` are installed onto your computer, run the following command: +``` +$ cargo install sterling +``` -`cargo install sterling` - -This will install `sterling` into the `.cargo/bin` directory within your User directory -(`/home/YOUR_USER_NAME` on Linux and macOS, `C:\Users\YOUR_USER_NAME` on Windows). Be sure to add -this directory to your PATH. \ No newline at end of file +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 +README. Simply download the correct binary for your computer's operating system, extract it +somewhere into your file system (such as a "bin" folder within your user directory), and add that +location to your system's PATH. \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..d994f41 --- /dev/null +++ b/appveyor.yml @@ -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 \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index c25ace7..162164f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ +use std::convert::From; use std::error::Error; use std::fmt::{self, Display, Formatter}; -use std::convert::From; use std::fs::File; use std::io::{self, BufReader, ErrorKind}; @@ -18,10 +18,10 @@ pub fn load_config(filename: &str) -> Result, ConfigError> { pub fn default_config() -> Vec { vec![ - Currency::new("platinum", 1000000, "p", None), - Currency::new("gold", 10000, "g", None), - Currency::new("silver", 100, "s", None), - Currency::new("copper", 1, "c", None), + 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), ] } diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 0000000..3cad37d --- /dev/null +++ b/src/convert.rs @@ -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 { + 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) -> Vec { + exchange(copper_value, currencies) + .iter() + .filter(|c| (*c).value.unwrap_or(0) > 0) + .cloned() + .collect() +} + +pub fn exchange(copper: usize, mut currencies: Vec) -> Vec { + 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())); + } +} diff --git a/src/currency.rs b/src/currency.rs index 93b131e..508db70 100644 --- a/src/currency.rs +++ b/src/currency.rs @@ -7,16 +7,24 @@ pub struct Currency { pub value: Option, pub alias: String, pub plural: Option, + pub optional: Option, } impl Currency { - pub fn new(name: &str, rate: usize, alias: &str, plural: Option) -> Currency { + pub fn new( + name: &str, + rate: usize, + alias: &str, + plural: Option, + optional: Option, + ) -> Currency { Currency { name: name.to_owned(), rate, value: None, alias: alias.to_owned(), plural, + optional, } } @@ -27,6 +35,14 @@ impl Currency { 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, } } @@ -50,10 +66,6 @@ impl Currency { } } -// impl Display for Currency { -// fn -// } - impl Ord for Currency { fn cmp(&self, other: &Currency) -> Ordering { self.value.cmp(&other.value) diff --git a/src/main.rs b/src/main.rs index e1492ad..9bcaf21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,10 @@ extern crate serde_derive; extern crate serde_yaml; mod config; +mod convert; mod currency; +use std::collections::HashMap; use std::io::ErrorKind; use std::process; @@ -21,11 +23,24 @@ use regex::Regex; fn main() { 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.") (@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 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(); @@ -34,17 +49,61 @@ fn main() { None => "./sterling-conf.yml", }); - let currencies = match parse_currency_config(config_result, matches.value_of("CONFIG")) { - Ok(currencies) => currencies, - Err(error) => { - eprintln!("{}", error); - process::exit(1); - } - }; + let currencies: Vec = + match parse_currency_config(config_result, matches.value_of("CONFIG")) { + Ok(currencies) => currencies + .iter() + .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( + ¤cies, + matches.value_of("AUGEND").unwrap(), + matches.value_of("ADDEND").unwrap(), + ); + + let converted_currencies = convert::convert_currencies(lhs + rhs, currencies); + let display_strings: Vec = + 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( + ¤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 = + 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 calculate_total_copper_value(coins) { + let total_copper_value = match convert::calculate_total_copper_value(coins) { Ok(total_copper_value) => total_copper_value, Err(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 = create_display_strings(converted_currencies, matches.is_present("PRINT_FULL")); @@ -72,68 +131,19 @@ fn parse_currency_config( 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)) + Err(format!( + "Sterling Error: Can't find configuration file: \"{}\"", + &file_path + )) } else { Ok(config::default_config()) } - }, + } _ => 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 { - 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) -> Vec { - 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) -> Vec { - exchange(copper_value, currencies) - .iter() - .filter(|c| (*c).value.unwrap_or(0) > 0) - .cloned() - .collect() -} - fn create_display_strings(converted_currencies: Vec, is_print_full: bool) -> Vec { converted_currencies .iter() @@ -147,103 +157,35 @@ fn create_display_strings(converted_currencies: Vec, is_print_full: bo .collect() } -#[cfg(test)] -mod tests { - use super::*; - use currency::Currency; - - 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), - ]; +fn get_copper_value(currencies: &[Currency], lhs: &str, rhs: &str) -> (usize, usize) { + let mut rates: HashMap = HashMap::with_capacity(currencies.len()); + for currency in currencies { + rates.insert(currency.alias.clone(), currency.rate); } - #[test] - fn test_convert_copper_to_copper() { - assert_eq!(1, convert_to_copper(1, "c")); - } + let aliases = currencies + .iter() + .cloned() + .map(|c| c.alias) + .fold(String::new(), |group, a| group + &a); - #[test] - fn test_convert_silver_to_copper() { - assert_eq!(10, convert_to_copper(1, "s")); - } + let regex: Regex = Regex::new(&format!("(\\d+)([{}])", aliases)).unwrap(); - #[test] - fn test_convert_electrum_to_copper() { - assert_eq!(50, convert_to_copper(1, "e")); - } + 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; - #[test] - fn test_convert_gold_to_copper() { - assert_eq!(100, convert_to_copper(1, "g")); - } + sum + product + }); - #[test] - fn test_convert_platinum_to_copper() { - assert_eq!(1000, convert_to_copper(1, "p")); - } + 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; - #[test] - fn test_calculate_total_copper_value() { - let values = vec!["1p", "1g", "1e", "1s", "1c"]; - assert_eq!(1161, calculate_total_copper_value(values).unwrap()); - } + sum + product + }); - #[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).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())); - } + (left_hand_side, right_hand_side) }