GithubHelp home page GithubHelp logo

ballasi / num2words Goto Github PK

View Code? Open in Web Editor NEW
15.0 1.0 8.0 106 KB

Convert numbers like 42 to forty-two

Home Page: https://crates.io/crates/num2words

License: Apache License 2.0

Rust 100.00%
numbers num2words numtowords rust rust-lang number-spelling number-converter

num2words's Introduction

software engineer. dev.
making music under free licenses.
trying to improve my C and Rust skills.
arch user btw.

num2words's People

Contributors

80ltrumpet avatar arpankapoor avatar ballasi avatar pavloslav avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

num2words's Issues

Spanish Support

Hello. I might try add spanish support. I haven't properly fully read how other lang's implementations are, but is there anything I should know? Or should wait till I submit pull request first?

Also, since it is spanish impl I was thinking about using spanish names for statics, and possibly even variables as well. as for methods I guess can leave them in english.

Also I might not be able to implement the Currency and date and others (Only cardinal if I didn't misinterpret it) since they feel a bit out of my league.... at least not at first!

  • Spanish Identifiers (type's impl method, constants and variables)

Add a .prefer() function

Something I would like to add for the English language before moving on to other languages is the ability to set preferences.

For instance, here is a sample code:

use num2words::*;

fn print_game_score(home_score: i64, away_score: i64) -> Result<(), Num2Err> {
    println!(
        "{} - {}",
        Num2Words::new(home_score).to_words()?,
        Num2Words::new(away_score).to_words()?
    );
    Ok(())
}

Calling print_game_score(3, 0).unwrap() would print three - zero, but in this case, we are more likely to be interested to see three - nil, same for "oh" (e.g., in versioning).

I am interested in implementing a call similar to the one below:

Num2Words::new(home_score).prefer(["nil"]).to_words()

Lots of Clippy Warnings

There's a lot of things that clippy isn't happy about. I was wondering if you plan to sort them out anytime. Also I got a file for rustfmt which uses nightly. In case you would like to use it. I personally like the option of grouping by modules

  --> src\lang\fr.rs:18:16
   |
18 | const UNITS: [&'static str; 9] = [
   |               -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
   = note: `#[warn(clippy::redundant_static_lifetimes)]` on by default

warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:22:15
   |
22 | const TENS: [&'static str; 9] = [
   |              -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes

warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:34:16
   |
34 | const TEENS: [&'static str; 10] = [
   |               -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes

warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:39:16
   |
39 | const MEGAS: [&'static str; 33] = [
   |               -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes

warning: useless conversion to the same type: `std::string::String`
   --> src\currency.rs:176:9
    |
176 | /         String::from(
177 | |             match self {
178 | |                 Currency::AED | Currency::KWD => "fils",
179 | |                 Currency::ARS | Currency::BRL | Currency::CLP | Currency::COP | Currency::MXN => {
...   |
192 | |             .replace("{}", if plural_form { "s" } else { "" }),
193 | |         )
    | |_________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
    = note: `#[warn(clippy::useless_conversion)]` on by default
help: consider removing `String::from()`
    |
176 ~         match self {
177 +                 Currency::AED | Currency::KWD => "fils",
178 +                 Currency::ARS | Currency::BRL | Currency::CLP | Currency::COP | Currency::MXN => {
179 +                     "centavo{}"
180 +                 }
181 ~                 Currency::CRC => "céntimo{}",
182 +                 Currency::IDR | Currency::MYR => "sen{}",
183 +                 Currency::KRW => "jeon{}",
184 +                 Currency::SAR => "halalat{}",
185 +                 Currency::THB => "satang{}",
186 +                 Currency::UAH => "kopiyok{}",
187 +                 Currency::UYU => "centesimo{}",
188 +                 Currency::VND => "xu{}",
189 +                 _ => cent,
190 +             }
191 +             .replace("{}", if plural_form { "s" } else { "" })
    |

warning: module has the same name as its containing module
 --> src\lang\mod.rs:2:1
  |
2 | mod lang;
  | ^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
  = note: `#[warn(clippy::module_inception)]` on by default

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:104:18
    |
104 |                   .find(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))
    |  __________________^
105 | |                 .is_some();
    | |__________________________^ help: use `any()` instead: `any(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
    = note: `#[warn(clippy::search_is_some)]` on by default

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:106:28
    |
106 |               let reformed = preferences
    |  ____________________________^
107 | |                 .iter()
108 | |                 .find(|v: &&String| {
109 | |                     ["reformed", "1990", "rectifié", "rectification"].contains(&v.as_str())
110 | |                 })
111 | |                 .is_some();
    | |__________________________^
    |
    = help: this is more succinctly expressed by calling `any()`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:118:18
    |
118 |                   .find(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))
    |  __________________^
119 | |                 .is_some();
    | |__________________________^ help: use `any()` instead: `any(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:120:28
    |
120 |               let reformed = preferences
    |  ____________________________^
121 | |                 .iter()
122 | |                 .find(|v: &&String| {
123 | |                     ["reformed", "1990", "rectifié", "rectification"].contains(&v.as_str())
124 | |                 })
125 | |                 .is_some();
    | |__________________________^
    |
    = help: this is more succinctly expressed by calling `any()`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:132:18
    |
132 |                   .find(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))
    |  __________________^
133 | |                 .is_some();
    | |__________________________^ help: use `any()` instead: `any(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:134:28
    |
134 |               let reformed = preferences
    |  ____________________________^
135 | |                 .iter()
136 | |                 .find(|v: &&String| {
137 | |                     ["reformed", "1990", "rectifié", "rectification"].contains(&v.as_str())
138 | |                 })
139 | |                 .is_some();
    | |__________________________^
    |
    = help: this is more succinctly expressed by calling `any()`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: stripping a suffix manually
   --> src\lang\fr.rs:318:25
    |
318 |                         &w[..w.len() - 1]
    |                         ^^^^^^^^^^^^^^^^^
    |
note: the suffix was tested here
   --> src\lang\fr.rs:317:21
    |
warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:18:16
   |
18 | const UNITS: [&'static str; 9] = [
   |               -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
   = note: `#[warn(clippy::redundant_static_lifetimes)]` on by default

warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:22:15
   |
22 | const TENS: [&'static str; 9] = [
   |              -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes

warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:34:16
   |
34 | const TEENS: [&'static str; 10] = [
   |               -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes

warning: constants have by default a `'static` lifetime
  --> src\lang\fr.rs:39:16
   |
39 | const MEGAS: [&'static str; 33] = [
   |               -^^^^^^^---- help: consider removing `'static`: `&str`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes

warning: useless conversion to the same type: `std::string::String`
   --> src\currency.rs:176:9
    |
176 | /         String::from(
177 | |             match self {
178 | |                 Currency::AED | Currency::KWD => "fils",
179 | |                 Currency::ARS | Currency::BRL | Currency::CLP | Currency::COP | Currency::MXN => {
...   |
192 | |             .replace("{}", if plural_form { "s" } else { "" }),
193 | |         )
    | |_________^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion
    = note: `#[warn(clippy::useless_conversion)]` on by default
help: consider removing `String::from()`
    |
176 ~         match self {
177 +                 Currency::AED | Currency::KWD => "fils",
178 +                 Currency::ARS | Currency::BRL | Currency::CLP | Currency::COP | Currency::MXN => {
179 +                     "centavo{}"
180 +                 }
181 ~                 Currency::CRC => "céntimo{}",
182 +                 Currency::IDR | Currency::MYR => "sen{}",
183 +                 Currency::KRW => "jeon{}",
184 +                 Currency::SAR => "halalat{}",
185 +                 Currency::THB => "satang{}",
186 +                 Currency::UAH => "kopiyok{}",
187 +                 Currency::UYU => "centesimo{}",
188 +                 Currency::VND => "xu{}",
189 +                 _ => cent,
190 +             }
191 +             .replace("{}", if plural_form { "s" } else { "" })
    |

warning: module has the same name as its containing module
 --> src\lang\mod.rs:2:1
  |
2 | mod lang;
  | ^^^^^^^^^
  |
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
  = note: `#[warn(clippy::module_inception)]` on by default

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:104:18
    |
104 |                   .find(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))
    |  __________________^
105 | |                 .is_some();
    | |__________________________^ help: use `any()` instead: `any(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
    = note: `#[warn(clippy::search_is_some)]` on by default

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:106:28
    |
106 |               let reformed = preferences
    |  ____________________________^
107 | |                 .iter()
108 | |                 .find(|v: &&String| {
109 | |                     ["reformed", "1990", "rectifié", "rectification"].contains(&v.as_str())
110 | |                 })
111 | |                 .is_some();
    | |__________________________^
    |
    = help: this is more succinctly expressed by calling `any()`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:118:18
    |
118 |                   .find(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))
    |  __________________^
119 | |                 .is_some();
    | |__________________________^ help: use `any()` instead: `any(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:120:28
    |
120 |               let reformed = preferences
    |  ____________________________^
121 | |                 .iter()
122 | |                 .find(|v: &&String| {
123 | |                     ["reformed", "1990", "rectifié", "rectification"].contains(&v.as_str())
124 | |                 })
125 | |                 .is_some();
    | |__________________________^
    |
    = help: this is more succinctly expressed by calling `any()`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:132:18
    |
132 |                   .find(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))
    |  __________________^
133 | |                 .is_some();
    | |__________________________^ help: use `any()` instead: `any(|v| ["feminine", "feminin", "féminin", "f"].contains(&v.as_str()))`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: called `is_some()` after searching an `Iterator` with `find`
   --> src\lang\lang.rs:134:28
    |
134 |               let reformed = preferences
    |  ____________________________^
135 | |                 .iter()
136 | |                 .find(|v: &&String| {
137 | |                     ["reformed", "1990", "rectifié", "rectification"].contains(&v.as_str())
138 | |                 })
139 | |                 .is_some();
    | |__________________________^
    |
    = help: this is more succinctly expressed by calling `any()`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some

warning: stripping a suffix manually
   --> src\lang\fr.rs:318:25
    |
318 |                         &w[..w.len() - 1]
    |                         ^^^^^^^^^^^^^^^^^
    |
note: the suffix was tested here
   --> src\lang\fr.rs:317:21
    |
317 |                     if w.ends_with('e') {
    |                     ^^^^^^^^^^^^^^^^^^^^
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
    = note: `#[warn(clippy::manual_strip)]` on by default
help: try using the `strip_suffix` method
    |
317 ~                     if let Some(<stripped>) = w.strip_suffix('e') {
318 ~                         <stripped>
    |```

Some currencies have other than 100 fractional units; and fractional units all are named "cents" in English

According to Wiki, there are some currencies that are split in other number of fractions, like BHD (1000) or BND (10). Even Yuan is split into 10 Jiao, not 100. Sometimes currencies that have the same name have the different number of fractions, like Iraqi dinar (IQD) has 1000 Fils, and Serbian dinar (RSD) has 100 Para. There should be an additional argument in to_currency function.

Also, fractional units have their respective names.

This is a critical bug.

Historically, there were currencies with several fractional units, like the old British Pound sterling had 12 shillings, each shilling split into 20 pence before 1971. Thank God we should not support this.

Split thousands Returns several 0

The !num.is_zero() doesn't work properly for float. The division isn't accurate so the exponent reduces on each division and eventually reaches 0. I think there is no need to use float. The input string can be split by . and both be represented as u128 which gives precision of 38 digits which I think is equal to the Bigfloat.

Into<Number> for any number type

For now, Into<Number> only allows i64 and f64.

This has been chosen as this is the largest common number type that features the greatest range of numbers.

However, people may work with other types and are fine using smaller number ranges.

This forces the user to cast it to the 64 bits version of the number.

This issue is a bit connected to #5 as if the int/float type is removed altogether with another it may need to add Into case for each common number types.

`Num2Words::parse` with input that matches "0[1-9]" produces "duodecillion"

Minimal Reproducer

use num2words::Num2Words;

fn main() {
    let zero_one = Num2Words::parse("01").unwrap().to_words().unwrap();
    println!("{zero_one}");
}

Expected Output

one

Note: zero one is a valid alternative, but 010 yields ten, not zero ten.

Actual Output

one duodecillion

Commentary

This behavior holds for all inputs matching the pattern: 0[1-9] (exactly two digits where the first digit is zero and the second digit is not zero).

to_words panics on NAN

As in the issue title the following code will panic:

if let Some(word) = Num2Words::parse(&word).and_then(|x| x.to_words().ok()) {
    println!("Word: {}", word);
}
thread 'main' panicked at /home/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/num2words-1.0.0/src/lang/en.rs:78:53:
called `Option::unwrap()` on a `None` value
stack backtrace:
   0: rust_begin_unwind
             at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/std/src/panicking.rs:595:5
   1: core::panicking::panic_fmt
             at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/panicking.rs:67:14
   2: core::panicking::panic
             at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/panicking.rs:117:5
   3: core::option::Option<T>::unwrap
             at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/option.rs:935:21
   4: num2words::lang::en::English::split_thousands
             at /home/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/num2words-1.0.0/src/lang/en.rs:78:28
   5: num2words::lang::en::English::int_to_cardinal
             at /home/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/num2words-1.0.0/src/lang/en.rs:106:29
   6: num2words::lang::en::English::float_to_cardinal
             at /home/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/num2words-1.0.0/src/lang/en.rs:159:33
   7: <num2words::lang::en::English as num2words::lang::lang::Language>::to_cardinal
             at /home/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/num2words-1.0.0/src/lang/en.rs:188:13
   8: num2words::num2words::Num2Words::to_words
             at /home/daniel/.cargo/registry/src/index.crates.io-6f17d22bba15001f/num2words-1.0.0/src/num2words.rs:274:33

German support

As I have studied German a long time ago, I could probably make this happen.

If anyone here knows German and would like to contribute, do shoot a message here! You are more than welcome! I probably won't start working on it as of today so contribution on that end would be greatly appreciated.

Would it be alright to add benchmarks?

I was doing some benchmarks while writing stuffs up, but because I wasn't as expertised on this aspect, I deleted everything. However, it does work by accessing the library as a Lib User, so to test inner logic, you would have to either copy and paste it out of the private modules, or expose methods to test.

I have used Criterion, but it seems there's probably more appealing options like Bencher, which seems to support CI benching (Whatever that is) https://nnethercote.github.io/perf-book/benchmarking.html

Nepali support

I am planning to open PR for Nepali language. Is it ok?

French support

Includes the following:

  • French (France & Canada)
  • French (Switzerland)
  • French (Belgium)

A number of clippy lints all over the code

Nothing serious, really, but some places can really be better with fixing those lints, like

warning: `to_string` applied to a type that implements `Display` in `eprintln!` args
   --> src/bin/bin.rs:120:51
    |
120 |             Err(err) => eprintln!("Error: {}", err.to_string()),
    |                                                   ^^^^^^^^^^^^ help: remove this

if it still is there, will get to it after adding Ukrainian

Add default currency name

For use in the future, languages should be able to batch some "default string" for each currencies. If we need to add a new currency, we may not know its translation and use in various languages, this helps having a first implementation that is likely to work well in the first place and might be patched in the future if needed to. In a way, the currencies() function may work in a "override currency name" way.

Add non_exhaustive attribute to Currency

It's not finished yet, and in future it will change. Making changes to all languages will be slow. So it's better to mark it as non_exhaustive and add default branches in all matches.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.