Functional Weave
Code in Python

text.number-to-words@1.0.0

impl/rust.rs

1,962 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::money_amount::{money_from_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune
use super::text_integer_to_words::integer_to_words;  ← from text.integer-to-words ^1.0.0 · built alongside by fune

/// English unit names: (code, major one, major many, minor one, minor many).
/// All three currencies have two decimal places, so the split below is by 100.
/// A currency is not added here without its real unit names: they cannot be
/// derived from a code.
const UNITS: [(&str, &str, &str, &str, &str); 3] = [
    ("GBP", "pound", "pounds", "penny", "pence"),
    ("EUR", "euro", "euros", "cent", "cents"),
    ("USD", "dollar", "dollars", "cent", "cents"),
];

/// Write a money amount in words, cheque style: 123456 GBP minor units is
/// "one thousand two hundred and thirty-four pounds and fifty-six pence".
///
/// # Panics
/// Panics on a currency without listed unit names, or an out-of-range amount.
pub fn amount_in_words(amount: &Money) -> String {
    let units = match UNITS.iter().find(|u| u.0 == amount.currency) {
        Some(found) => found,
        None => panic!("no words for currency \"{}\"", amount.currency),
    };

    // Validate the whole amount first, so an out-of-range value fails with the
    // same message it would as a plain number.
    integer_to_words(amount.minor);

    let magnitude = amount.minor.abs();
    let major = magnitude / 100;
    let minor = magnitude % 100;

    let mut parts: Vec<String> = Vec::new();
    if major > 0 || minor == 0 {
        let name = if major == 1 { units.1 } else { units.2 };
        parts.push(format!("{} {}", integer_to_words(major), name));
    }
    if minor > 0 {
        let name = if minor == 1 { units.3 } else { units.4 };
        parts.push(format!("{} {}", integer_to_words(minor), name));
    }

    let body = parts.join(" and ");
    if amount.minor < 0 {
        format!("minus {}", body)
    } else {
        body
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    Value::Str(amount_in_words(&money_from_value(&args[0])))
}