Git Product home page Git Product logo

num2words's Introduction

num2words

Convert numbers like 42 to forty-two

Usage

This crate can be either used as a library or a binary.

Library

Example usage:

use num2words::Num2Words;
assert_eq!(Num2Words::new(42).to_words(), Ok(String::from("forty-two")));

The builder Num2Words can take any of these methods: lang, cardinal, ordinal, ordinal_num, year, and currency.

use num2words::*;
assert_eq!(
    Num2Words::new(42).lang(Lang::French).to_words(),
    Ok(String::from("quarante-deux"))
);
assert_eq!(
    Num2Words::new(42).ordinal().to_words(),
    Ok(String::from("forty-second"))
);
assert_eq!(
    Num2Words::new(42.01).currency(Currency::DOLLAR).to_words(),
    Ok(String::from("forty-two dollars and one cent"))
);

These arguments can be chained.

For more information about the available languages, outputs types and currencies, see Information.

Binary

This crate provides a command-line interface to run requests on num2words.

Example:

$ num2words 42
forty-two
$ num2words 10 --to UAH --lang uk
десять гривень

You can download the app via the following command:

$ cargo install num2words

You can also change the language via the CLI argument --lang [locale] and provide a specific output type or a currency with the argument --to [cardinal|ordinal|ordinal_num|year|ISO 4217].

For more information about the usage of num2words please refer to the docs or via the following command:

$ num2words --help

Information

Supported languages

Here is a list of all of the supported languages:

Flag Code Locale Language 42
🇺🇸🇬🇧 Lang::English en English forty-two
🇫🇷🇨🇦 Lang::French fr French quarante-deux
🇧🇪🇨🇩 Lang::French_BE fr_BE French (BE) quarante-deux
🇨🇭 Lang::French_CH fr_CH French (CH) quarante-deux
🇺🇦 Lang::Ukrainian uk Ukrainian сорок два

This list can be expanded! Contributions are welcomed.

Supported output types

Here is a list of all of the supported outputs types (with the associated command-line interface code):

Library method CLI argument Example output
.cardinal() cardinal forty-two (42)
.ordinal() ordinal forty-second (42)
.ordinal_num() ordinal_num 42nd (42)
.year() year nineteen oh-one (1901)
.currency(cur) ISO 4217 code forty-two dollars and one cent (42.01)

Supported currencies

Three-letter enum variants corresponds to the currency's ISO 4217 code, but there are exceptions to accomodate generic terminologies: DINAR, DOLLAR, PESO and RIYAL.

A summary of all of the supported currencies are available in the documentation of Currency.

About

This library is widely inspired by Savoir-faire Linux's Python lib.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

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  avatar  avatar

Watchers

 avatar

num2words's Issues

French support

Includes the following:

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

Nepali support

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

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.

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 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.

`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).

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>
    |```

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)

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

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.

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.

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.

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()

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

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.

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.