use crate::base::{Kwargs, KwVal, Lang, N2WError, Result};
use crate::currency::{CurrencyForms, CurrencyValue};
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, python_decimal_str, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero};
use std::collections::HashMap;
const OBJ: &str = "__OBJECT__";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mut {
Soft,
Aspirate,
}
type Entry = (&'static str, Option<Mut>);
type Word<'a> = (&'a str, Option<Mut>);
use Mut::{Aspirate as AM, Soft as SM};
const CARDINAL_WORDS: [&[Entry]; 20] = [
&[("dim", None), (OBJ, None)],
&[("un", None), (OBJ, None)],
&[("dau", Some(SM)), (OBJ, None)],
&[("tri", Some(AM)), (OBJ, None)],
&[("pedwar", None), (OBJ, None)],
&[("pump", None), (OBJ, None)],
&[("chwech", Some(AM)), (OBJ, None)],
&[("saith", None), (OBJ, None)],
&[("wyth", None), (OBJ, None)],
&[("naw", None), (OBJ, None)],
&[("deg", None), (OBJ, None)],
&[("un", None), (OBJ, None), ("ar ddeg", None)],
&[("deuddeg", None), (OBJ, None)],
&[("tri", Some(AM)), (OBJ, None), ("ar ddeg", None)],
&[("pedwar", None), (OBJ, None), ("ar ddeg", None)],
&[("pymtheg", None), (OBJ, None)],
&[("un", None), (OBJ, None), ("ar bymtheg", None)],
&[("dau", Some(SM)), (OBJ, None), ("ar bymtheg", None)],
&[("deunaw", None), (OBJ, None)],
&[("pedwar", None), ("ar bymtheg", None)], ];
const CARDINAL_WORDS_FEM: [&[Entry]; 20] = [
&[("dim", None), (OBJ, None)],
&[("un", None), (OBJ, None)],
&[("dwy", Some(SM)), (OBJ, None)],
&[("tair", None), (OBJ, None)],
&[("pedair", None), (OBJ, None)],
&[("pump", None), (OBJ, None)],
&[("chwech", Some(AM)), (OBJ, None)],
&[("saith", None), (OBJ, None)],
&[("wyth", None), (OBJ, None)],
&[("naw", None), (OBJ, None)],
&[("deg", None), (OBJ, None)],
&[("un", None), (OBJ, None), ("ar ddeg", None)],
&[("deuddeg", None), (OBJ, None)],
&[("tair", None), (OBJ, None), ("ar ddeg", None)],
&[("pedair", None), (OBJ, None), ("ar ddeg", None)],
&[("pymtheg", None), (OBJ, None)],
&[("un", None), (OBJ, None), ("ar bymtheg", None)],
&[("dwy", Some(SM)), (OBJ, None), ("ar bymtheg", None)],
&[("deunaw", None), (OBJ, None)],
&[("pedair", None), ("ar bymtheg", None)], ];
const ORDINAL_WORDS: [&[Entry]; 20] = [
&[("dimfed", None), (OBJ, None)],
&[(OBJ, None), ("cyntaf", None)],
&[("ail", Some(SM)), (OBJ, None)],
&[("trydydd", None), (OBJ, None)],
&[("pedwerydd", None), (OBJ, None)],
&[("pumed", None), (OBJ, None)],
&[("chweched", None), (OBJ, None)],
&[("saithfed", None), (OBJ, None)],
&[("wythfed", None), (OBJ, None)],
&[("nawfed", None), (OBJ, None)],
&[("degfed", None), (OBJ, None)],
&[("unfed", Some(SM)), (OBJ, None), ("ar ddeg", None)],
&[("deuddegfed", None), (OBJ, None)],
&[("trydydd", None), (OBJ, None), ("ar ddeg", None)],
&[("pedwerydd", None), (OBJ, None), ("ar ddeg", None)],
&[("pymthegfed", None), (OBJ, None)],
&[("unfed", None), (OBJ, None), ("ar bymtheg", None)],
&[("ail", Some(SM)), (OBJ, None), ("ar bymtheg", None)],
&[("deunawfed", None), (OBJ, None)],
&[("pedwerydd", None), (OBJ, None), ("ar bymtheg", None)],
];
#[allow(dead_code)]
const ORDINAL_WORDS_FEM: [&[Entry]; 20] = [
&[("dimfed", None), (OBJ, None)],
&[(OBJ, None), ("gyntaf", None)],
&[("ail", Some(SM)), (OBJ, None)],
&[("trydedd", Some(SM)), (OBJ, None)],
&[("pedwaredd", Some(SM)), (OBJ, None)],
&[("pumed", None), (OBJ, None)],
&[("chweched", None), (OBJ, None)],
&[("saithfed", None), (OBJ, None)],
&[("wythfed", None), (OBJ, None)],
&[("nawfed", None), (OBJ, None)],
&[("degfed", None), (OBJ, None)],
&[("unfed", Some(SM)), (OBJ, None), ("ar ddeg", None)],
&[("deuddegfed", None), (OBJ, None)],
&[("trydedd", Some(SM)), (OBJ, None), ("ar ddeg", None)],
&[("pedwaredd", Some(SM)), (OBJ, None), ("ar ddeg", None)],
&[("pymthegfed", None), (OBJ, None)],
&[("unfed", None), (OBJ, None), ("ar bymtheg", None)],
&[("ail", Some(SM)), (OBJ, None), ("ar bymtheg", None)],
&[("deunawfed", None), (OBJ, None)],
&[("pedwaredd", None), (OBJ, None), ("ar bymtheg", None)],
];
fn million_word(exponent: u32) -> Entry {
match exponent {
3 => ("mil", None),
6 => ("miliwn", None),
9 => ("biliwn", None),
12 => ("triliwn", None),
15 => ("cwadriliwn", None),
18 => ("cwintiliwn", None),
21 => ("secsttiliwn", None),
24 => ("septiliwn", None),
27 => ("octiliwn", None),
30 => ("noniliwn", None),
33 => ("dengiliwn", None),
_ => unreachable!("million_word({}) — outside the 999*10**33 ceiling", exponent),
}
}
const STR_TENS_1: &[Entry] = &[("ugain", None), (OBJ, None)];
const STR_TENS_2: &[Entry] = &[("deugain", None), (OBJ, None)];
const STR_TENS_3: &[Entry] = &[("trigain", None), (OBJ, None)];
const STR_TENS_4: &[Entry] = &[("pedwar ugain", None), (OBJ, None)];
const ORD_STR_TENS_1: &[Entry] = &[("ugainfed", None), (OBJ, None)];
const ORD_STR_TENS_2: &[Entry] = &[("deugainfed", None), (OBJ, None)];
const ORD_STR_TENS_3: &[Entry] = &[("trigainfed", None), (OBJ, None)];
const ORD_STR_TENS_4: &[Entry] = &[("pedwar ugainfed", None), (OBJ, None)];
fn str_tens(tens: i64) -> Option<&'static [Entry]> {
match tens {
1 => Some(STR_TENS_1),
2 => Some(STR_TENS_2),
3 => Some(STR_TENS_3),
4 => Some(STR_TENS_4),
_ => None,
}
}
fn ord_str_tens(tens: i64) -> Option<&'static [Entry]> {
match tens {
1 => Some(ORD_STR_TENS_1),
2 => Some(ORD_STR_TENS_2),
3 => Some(ORD_STR_TENS_3),
4 => Some(ORD_STR_TENS_4),
_ => None,
}
}
const AC_VALUES: [i64; 16] = [1, 8, 11, 16, 20, 21, 31, 36, 41, 48, 61, 68, 71, 81, 88, 91];
fn mutate(word: &str, mutation: Mut) -> String {
match mutation {
Mut::Soft => softmutation(word),
Mut::Aspirate => aspiratedmutation(word),
}
}
fn softmutation(word: &str) -> String {
let mut it = word.chars();
let c0 = it.next();
let c1 = it.next();
let rest: String = word.chars().skip(1).collect();
if c0 == Some('p') && c1 != Some('h') {
format!("b{}", rest)
} else if c0 == Some('t') && c1 != Some('h') {
format!("d{}", rest)
} else if c0 == Some('c') && c1 != Some('h') {
format!("g{}", rest)
} else if c0 == Some('b') || c0 == Some('m') {
format!("f{}", rest)
} else if c0 == Some('d') && c1 != Some('d') {
format!("d{}", word)
} else if word.starts_with("ll") {
rest
} else if word.starts_with("rh") {
format!("r{}", word.chars().skip(2).collect::<String>())
} else if word == "ugain" {
"hugain".to_string()
} else {
word.to_string()
}
}
fn aspiratedmutation(word: &str) -> String {
let mut it = word.chars();
let c0 = it.next();
let c1 = it.next();
let rest: String = word.chars().skip(1).collect();
if c0 == Some('p') && c1 != Some('h') {
format!("ph{}", rest)
} else if c0 == Some('t') && c1 != Some('h') {
format!("th{}", rest)
} else if c0 == Some('c') && c1 != Some('h') {
format!("ch{}", rest)
} else {
word.to_string()
}
}
fn makestring<'a>(result: &[Word<'a>], counted: Option<&str>) -> String {
let mut out: Vec<String> = Vec::new();
let mut lastmut: Option<Mut> = None;
let mut counted = counted;
for &(w, mut_) in result.iter() {
let word: &str = if w == OBJ {
match counted.take() {
None => continue,
Some(c) => c,
}
} else {
w
};
match lastmut {
Some(m) => out.push(mutate(word, m)),
None => out.push(word.to_string()),
}
lastmut = mut_;
}
out.join(" ")
}
fn py_float_str(value: f64, precision: u32) -> String {
let a = value.abs();
let mag = if a != 0.0 && (a >= 1e16 || a < 1e-4) {
let s = format!("{:e}", a);
let (mant, exp) = s.split_once('e').expect("LowerExp always emits an e");
let exp: i32 = exp.parse().expect("exponent is a small int");
format!(
"{}e{}{:02}",
mant,
if exp < 0 { '-' } else { '+' },
exp.abs()
)
} else {
format!("{:.*}", precision as usize, a)
};
if value.is_sign_negative() {
format!("-{}", mag)
} else {
mag
}
}
fn py_repr_wordlist(words: &[Entry]) -> String {
let items: Vec<String> = words
.iter()
.map(|(w, m)| {
let m_repr = match m {
Some(Mut::Soft) => "'SM'",
Some(Mut::Aspirate) => "'AM'",
None => "None",
};
format!("('{}', {})", w, m_repr)
})
.collect();
format!("[{}]", items.join(", "))
}
fn kw_truthy(v: &KwVal) -> bool {
match v {
KwVal::Bool(b) => *b,
KwVal::Int(i) => *i != 0,
KwVal::Str(s) => !s.is_empty(),
KwVal::List(l) => !l.is_empty(),
KwVal::None => false,
}
}
fn float_key_repr(value: &FloatValue) -> String {
match value {
FloatValue::Float { value, precision } => py_float_str(*value, *precision),
FloatValue::Decimal { value, .. } => {
format!("Decimal('{}')", python_decimal_str(value))
}
}
}
fn hundred_group(number: i64, _informal: bool, gender: &str, ordinal: bool) -> Vec<Entry> {
let hundreds = number / 100;
let until100 = number % 100; let mut result: Vec<Entry> = Vec::new();
let cw: &[&[Entry]; 20] = if gender == "fem" {
&CARDINAL_WORDS_FEM
} else if ordinal {
&ORDINAL_WORDS
} else {
&CARDINAL_WORDS
};
if hundreds > 0 {
if hundreds > 1 {
result.extend_from_slice(CARDINAL_WORDS[hundreds as usize]);
}
result.extend_from_slice(&[("cant", None), (OBJ, None)]);
if until100 != 0 {
if AC_VALUES.contains(&until100) {
result.push(("ac", None));
} else {
result.push(("a", Some(AM)));
}
}
}
if until100 != 0 {
if !ordinal && (50..=59).contains(&until100) {
let units = number % 10;
if hundreds > 0 {
if units == 0 {
result.push(("hanner", None));
} else if units == 1 {
result.extend_from_slice(&[("hanner ac un", None), (OBJ, None)]);
} else {
result.push(("hanner a", Some(AM)));
result.extend_from_slice(cw[units as usize]);
}
} else if units == 0 {
result.extend_from_slice(&[("hanner cant", None), (OBJ, None)]);
} else if units == 1 {
result.extend_from_slice(&[("hanner cant ac un", None), (OBJ, None)]);
} else {
result.push(("hanner cant a", Some(AM)));
result.extend_from_slice(cw[units as usize]);
}
} else if (number < 20 && number > 0) || (number == 0 && hundreds == 0) {
if gender == "fem" {
result.extend_from_slice(CARDINAL_WORDS_FEM[number as usize]);
} else {
result.extend_from_slice(CARDINAL_WORDS[number as usize]);
}
} else {
let tens = until100 / 20;
let units = number % 20;
let degau = if ordinal && units == 0 {
ord_str_tens(tens)
} else {
str_tens(tens)
};
if units != 0 {
if tens > 1 {
result.extend_from_slice(cw[units as usize]);
if let Some(d) = degau {
result.push(("a", Some(AM)));
result.extend_from_slice(d);
}
} else {
result.extend_from_slice(cw[units as usize]);
if let Some(d) = degau {
result.push(("ar", Some(SM)));
result.extend_from_slice(d);
}
}
} else if let Some(d) = degau {
result.extend_from_slice(d);
}
}
}
result
}
fn maxval_cy() -> BigInt {
BigInt::from(999) * BigInt::from(10u32).pow(33)
}
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const GENERIC_DOLLARS: [&str; 2] = ["dolar", "dolarau"];
const GENERIC_CENTS: [&str; 2] = ["ceiniog", "ceiniogau"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert("EUR", CurrencyForms::new(&["euro", "euros"], &GENERIC_CENTS));
m.insert("USD", CurrencyForms::new(&GENERIC_DOLLARS, &GENERIC_CENTS));
m.insert(
"GBP",
CurrencyForms::new(&["punt", "punnoedd"], &["ceiniog", "ceiniogau"]),
);
m.insert("CNY", CurrencyForms::new(&["yuan", "yuans"], &["ffen", "ffens"]));
m
}
fn build_currency_adjectives() -> HashMap<&'static str, &'static str> {
[
("AUD", "Australian"),
("BYN", "Belarusian"),
("CAD", "Canadian"),
("EEK", "Estonian"),
("USD", "US"),
("RUB", "Russian"),
("NOK", "Norwegian"),
("MXN", "Mexican"),
("RON", "Romanian"),
("INR", "Indian"),
("HUF", "Hungarian"),
("ISK", "íslenskar"),
("UZS", "Uzbekistan"),
("SAR", "Saudi"),
("JPY", "Japanese"),
("KRW", "Korean"),
]
.into_iter()
.collect()
}
pub struct LangCy {
currency_forms: HashMap<&'static str, CurrencyForms>,
currency_adjectives: HashMap<&'static str, &'static str>,
}
impl Default for LangCy {
fn default() -> Self {
Self::new()
}
}
impl LangCy {
pub fn new() -> Self {
LangCy {
currency_forms: build_currency_forms(),
currency_adjectives: build_currency_adjectives(),
}
}
fn to_cardinal_raw(
&self,
number: &BigInt,
informal: bool,
gender: &str,
ordinal: bool,
) -> Result<Vec<Entry>> {
let mut negative = false;
let mut number = number.clone();
if number.is_negative() {
negative = true;
number = -number;
}
if number.is_zero() {
return Ok(CARDINAL_WORDS[0].to_vec());
}
#[allow(clippy::nonminimal_bool)]
if !(number < maxval_cy()) {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
let mut groups: Vec<(i64, u32)> = Vec::new();
let mut lowestgroup: Option<i64> = None;
for pot in [3u32, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36] {
let hi = BigInt::from(10u32).pow(pot);
let lo = BigInt::from(10u32).pow(pot - 3);
let gr = number.mod_floor(&hi).div_floor(&lo);
let gr = gr.to_i64().expect("group of three digits fits i64");
groups.push((gr, pot));
if gr != 0 && lowestgroup.is_none() {
lowestgroup = Some(gr);
}
}
let mut result: Vec<Entry> = Vec::new();
if negative {
result.push(("meinws", None));
}
for &(gr, pot) in groups.iter().rev() {
if gr == 0 {
continue;
}
let g = if pot == 6 {
"fem" } else if pot == 3 {
gender } else {
"masc" };
if gr > 1 || pot == 3 {
let words = hundred_group(gr, informal, g, ordinal && lowestgroup == Some(gr));
result.extend(words);
}
if pot > 3 {
result.push(million_word(pot - 3));
}
}
Ok(result)
}
fn to_cardinal_full(
&self,
number: &BigInt,
informal: bool,
gender: &str,
ordinal: bool,
) -> Result<String> {
let words = self.to_cardinal_raw(number, informal, gender, ordinal)?;
Ok(makestring(&words, None))
}
fn float_to_words(&self, value: f64, precision: u32) -> Result<String> {
let is_negative = value < 0.0;
let abs_float = value.abs();
let pre = BigInt::from_f64(abs_float.trunc()).expect("finite float truncates exactly");
let prefix = self.to_cardinal_full(&pre, false, "masc", false)?;
let s = py_float_str(abs_float, precision);
let float_part = match s.split_once('.') {
Some((_, frac)) => frac.to_string(),
None => return Err(N2WError::Index("list index out of range".into())),
};
let mut parts: Vec<String> = Vec::new();
for c in float_part.chars() {
let d = c.to_digit(10).ok_or_else(|| {
N2WError::Value(format!("invalid literal for int() with base 10: '{}'", c))
})?;
parts.push(self.to_cardinal_full(&BigInt::from(d), false, "masc", false)?);
}
let postfix = parts.join(" ");
let result = format!("{} pwynt {}", prefix, postfix);
if is_negative {
Ok(format!("meinws {}", result))
} else {
Ok(result)
}
}
fn decimal_to_cardinal(&self, value: &BigDecimal, ordinal: bool) -> Result<String> {
let negative = value.is_negative();
let absval = value.abs();
if absval.is_zero() {
return Ok(makestring(CARDINAL_WORDS[0], None));
}
#[allow(clippy::nonminimal_bool)]
if !(absval < BigDecimal::from(maxval_cy())) {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
let pre = absval.with_scale(0).as_bigint_and_exponent().0;
let mut groups: Vec<(i64, u32)> = Vec::new();
let mut lowestgroup: Option<i64> = None;
for pot in [3u32, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36] {
let hi = BigInt::from(10u32).pow(pot);
let lo = BigInt::from(10u32).pow(pot - 3);
let gr = pre.mod_floor(&hi).div_floor(&lo);
let gr = gr.to_i64().expect("group of three digits fits i64");
groups.push((gr, pot));
if gr != 0 && lowestgroup.is_none() {
lowestgroup = Some(gr);
}
}
let mut result: Vec<Entry> = Vec::new();
if negative {
result.push(("meinws", None));
}
for &(gr, pot) in groups.iter().rev() {
if gr == 0 {
continue;
}
let g = if pot == 6 { "fem" } else { "masc" };
if gr > 1 || pot == 3 {
let words = hundred_group(gr, false, g, ordinal && lowestgroup == Some(gr));
result.extend(words);
}
if pot > 3 {
result.push(million_word(pot - 3));
}
}
Ok(makestring(&result, None))
}
fn to_ordinal_impl(&self, value: &BigInt, gender: &str) -> Result<String> {
if value < &BigInt::from(20) {
if value.is_negative() {
return Err(N2WError::Key(format!("{}", value)));
}
let idx = value.to_usize().expect("0..=19 fits usize");
return Ok(makestring(ORDINAL_WORDS[idx], None));
}
if value == &BigInt::from(100) {
return Ok("canfed".to_string());
} else if value > &BigInt::from(100) {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
self.to_cardinal_full(value, false, gender, true)
}
}
impl Lang for LangCy {
fn default_currency(&self) -> &str {
"GBP"
}
fn default_separator(&self) -> &str {
","
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.to_cardinal_full(value, false, "masc", false)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.to_ordinal_impl(value, "masc")
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
match value {
FloatValue::Float { value, precision } => self.float_to_words(*value, *precision),
FloatValue::Decimal { value, .. } => self.decimal_to_cardinal(value, false),
}
}
fn cardinal_float_entry(
&self,
value: &FloatValue,
precision_override: Option<u32>,
) -> Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
let lt20 = match value {
FloatValue::Float { value, .. } => *value < 20.0,
FloatValue::Decimal { value, .. } => *value < BigDecimal::from(20),
};
if lt20 {
if let Some(i) = value.as_whole_int() {
if !i.is_negative() {
let idx = i.to_usize().expect("0..=19 fits usize");
return Ok(makestring(ORDINAL_WORDS[idx], None));
}
}
return Err(N2WError::Key(float_key_repr(value)));
}
let (eq100, gt100) = match value {
FloatValue::Float { value, .. } => (*value == 100.0, *value > 100.0),
FloatValue::Decimal { value, .. } => {
let hundred = BigDecimal::from(100);
(*value == hundred, *value > hundred)
}
};
if eq100 {
return Ok("canfed".to_string());
}
if gt100 {
return Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
));
}
match value {
FloatValue::Float { value, precision } => self.float_to_words(*value, *precision),
FloatValue::Decimal { value, .. } => self.decimal_to_cardinal(value, true),
}
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
python_decimal_parse(s)
}
fn inf_result(&self, negative: bool, to: &str) -> Result<String> {
match to {
"ordinal_num" => Ok(format!(
"{}Infinity",
if negative { "-" } else { "" }
)),
_ => Err(N2WError::NotImplemented(
"The given number is too large.".to_string(),
)),
}
}
fn nan_result(&self, to: &str) -> Result<String> {
match to {
"ordinal_num" => Ok("NaN".to_string()),
_ => Err(N2WError::Custom {
module: "decimal",
class: "InvalidOperation",
msg: "[<class 'decimal.InvalidOperation'>]".to_string(),
}),
}
}
fn neg_zero_decimal(&self, to: &str) -> Option<Result<String>> {
match to {
"cardinal" | "year" => Some(Ok("dim".to_string())),
_ => None,
}
}
fn to_fraction(&self, numerator: &BigInt, denominator: &BigInt) -> Result<String> {
if denominator.is_zero() {
return Err(N2WError::ZeroDivision(
"denominator must not be zero".into(),
));
}
if denominator.is_one() || numerator.is_zero() {
return self.to_cardinal(numerator);
}
let is_negative = numerator.is_negative() ^ denominator.is_negative();
if is_negative {
return Err(N2WError::Attribute(
"'Num2Word_CY' object has no attribute 'negword'".into(),
));
}
let abs_n = numerator.abs();
let abs_d = denominator.abs();
let num_word = self.to_cardinal(&abs_n)?;
let mut den_word = self.to_ordinal(&abs_d)?;
if !abs_n.is_one() {
den_word.push('s');
}
Ok(format!("{} {}", num_word, den_word))
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if kw.is_empty() {
return self.to_cardinal(value);
}
if !kw.only(&["informal", "gender", "counted", "raw", "ordinal"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
if let Some(v) = kw.get("ordinal") {
if kw_truthy(v) {
let rest: Vec<(String, KwVal)> = kw
.0
.iter()
.filter(|(k, _)| k != "ordinal")
.cloned()
.collect();
return self.to_ordinal_kw(value, &Kwargs(rest));
}
}
let raw = kw.get("raw").map(kw_truthy).unwrap_or(false);
let counted: Option<&str> = match kw.get("counted") {
None | Some(KwVal::None) => None,
Some(KwVal::Str(s)) => Some(s.as_str()),
Some(_) => return Err(N2WError::Fallback("kwargs".into())),
};
let gender = if kw.str("gender") == Some("fem") {
"fem"
} else {
"masc"
};
let words = self.to_cardinal_raw(value, false, gender, false)?;
if raw {
return Ok(py_repr_wordlist(&words));
}
if value.is_zero() {
return Ok(makestring(&words, None));
}
if value.abs() < BigInt::from(100) {
let w: Vec<Word<'_>> = words.iter().map(|&(a, b)| (a, b)).collect();
Ok(makestring(&w, counted))
} else {
let mut w: Vec<Word<'_>> = words.iter().map(|&(a, b)| (a, b)).collect();
if let Some(c) = counted {
w.push(("o", Some(SM)));
w.push((c, None));
}
Ok(makestring(&w, None))
}
}
fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["informal", "gender"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let gender = if kw.str("gender") == Some("fem") {
"fem"
} else {
"masc"
};
self.to_ordinal_impl(value, gender)
}
fn to_year_kw(&self, value: &BigInt, _kw: &Kwargs) -> Result<String> {
self.to_cardinal(value)
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if kw.is_empty() {
return self.cardinal_float_entry(value, precision_override);
}
if !kw.only(&["informal", "gender", "counted", "raw"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
match value {
FloatValue::Float { value, precision } => self.float_to_words(*value, *precision),
FloatValue::Decimal { .. } => Err(N2WError::Fallback("kwargs".into())),
}
}
fn lang_name(&self) -> &str {
"Num2Word_CY"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn currency_adjective(&self, code: &str) -> Option<&str> {
self.currency_adjectives.get(code).copied()
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
let form = if n.is_one() { 0 } else { 1 };
forms
.get(form)
.cloned()
.ok_or_else(|| N2WError::Index("tuple index out of range".into()))
}
fn money_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
let forms = self
.currency_forms
.get(currency)
.ok_or_else(|| N2WError::Key(format!("'{}'", currency)))?;
let raw = self.to_cardinal_raw(number, false, "fem", false)?;
let mut m: Vec<Word<'_>> = raw.iter().map(|&(w, mu)| (w, mu)).collect();
if number > &BigInt::from(100) {
let c = &forms.unit[1];
m.push(("o", Some(SM)));
m.push((c.as_str(), None));
Ok(makestring(&m, None))
} else {
let c = &forms.unit[0];
Ok(makestring(&m, Some(c.as_str())))
}
}
fn cents_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
if number.is_zero() {
return Ok(String::new());
}
let forms = self
.currency_forms
.get(currency)
.ok_or_else(|| N2WError::Key(format!("'{}'", currency)))?;
let m: Vec<Word<'_>> = if number > &BigInt::one() {
let raw = self.to_cardinal_raw(number, false, "masc", false)?;
raw.iter().map(|&(w, mu)| (w, mu)).collect()
} else {
vec![(OBJ, None)]
};
let c = &forms.subunit[0];
Ok(makestring(&m, Some(c.as_str())))
}
fn to_currency(
&self,
val: &CurrencyValue,
currency: &str,
cents: bool,
separator: Option<&str>,
adjective: bool,
) -> Result<String> {
let separator = separator.unwrap_or(self.default_separator());
if let CurrencyValue::Int(v) = val {
let minus_str = if v.is_negative() { "minws " } else { "" };
let abs_val = v.abs();
let money_str = self.to_cardinal(&abs_val)?;
let currency_str = if abs_val.is_one() { "bunt" } else { "bunnoedd" };
return Ok(format!("{}{} {}", minus_str, money_str, currency_str));
}
if !self.currency_forms.contains_key(currency) {
return Err(N2WError::NotImplemented(format!(
"Currency code \"{}\" not implemented for \"{}\"",
currency,
self.lang_name()
)));
}
if val.is_negative() {
return Err(N2WError::Attribute(
"'Num2Word_CY' object has no attribute 'negword'".into(),
));
}
crate::currency::default_to_currency(self, val, currency, cents, separator, adjective)
}
}