Release/0.3.0
This commit is contained in:
parent
26aecf26ec
commit
abfb69e82d
12 changed files with 410 additions and 191 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -21,3 +21,5 @@ Cargo.lock
|
|||
.history
|
||||
|
||||
# End of https://www.gitignore.io/api/rust
|
||||
|
||||
sterling-conf.yml
|
||||
|
|
24
.gitlab-ci.yml
Normal file
24
.gitlab-ci.yml
Normal 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
27
.travis.yml
Normal 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
|
|
@ -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 <zcdziura@gmail.com>"]
|
||||
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"
|
||||
|
|
70
README.md
70
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 <CONFIG> Specify location of config file; defaults to './sterling-conf.yml'.
|
||||
|
||||
ARGS:
|
||||
<VALUE>... The value to be converted; should be suffixed with the coin's short-hand
|
||||
abbreviation, i.e. p, g, e, s, or c.
|
||||
<VALUE>... 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.
|
||||
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.
|
29
appveyor.yml
Normal file
29
appveyor.yml
Normal 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
|
|
@ -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<Vec<Currency>, ConfigError> {
|
|||
|
||||
pub fn default_config() -> Vec<Currency> {
|
||||
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),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
155
src/convert.rs
Normal file
155
src/convert.rs
Normal 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()));
|
||||
}
|
||||
}
|
|
@ -7,16 +7,24 @@ pub struct Currency {
|
|||
pub value: Option<usize>,
|
||||
pub alias: String,
|
||||
pub plural: Option<String>,
|
||||
pub optional: Option<bool>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
|
|
254
src/main.rs
254
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<Currency> =
|
||||
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<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(
|
||||
¤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 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<String> =
|
||||
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<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> {
|
||||
converted_currencies
|
||||
.iter()
|
||||
|
@ -147,103 +157,35 @@ fn create_display_strings(converted_currencies: Vec<Currency>, 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<String, usize> = 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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue