Skip to content

Commit

Permalink
Add icu-locale macros
Browse files Browse the repository at this point in the history
  • Loading branch information
zbraniecki committed Sep 3, 2020
1 parent 3ebc97d commit f2eb526
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"components/icu4x",
"components/uniset",
"components/locale",
"components/locale/macros",
"components/num-util",
"components/pluralrules",
]
21 changes: 21 additions & 0 deletions components/locale/macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "icu-locale-macros"
description = "proc-macros for icu-locale"
version = "0.0.1"
authors = ["The ICU4X Project Developers"]
edition = "2018"
readme = "README.md"
repository = "https://github.com/unicode-org/icu4x"
license-file = "../../LICENSE"
categories = ["internationalization"]
include = [
"src/**/*",
"Cargo.toml",
"README.md"
]

[lib]
proc_macro = true

[dependencies]
icu-locale = { version = "0.0.1", path = "../" }
71 changes: 71 additions & 0 deletions components/locale/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
extern crate proc_macro;

use proc_macro::TokenStream;

fn get_value_from_token_stream(input: TokenStream) -> String {
let val = format!("{}", input);
if !val.starts_with('"') || !val.ends_with('"') {
panic!("Argument must be a string literal.");
}
let len = val.len();
(&val[1..len - 1]).to_string()
}

fn language_output(input: Option<u64>) -> String {
let val = if let Some(raw) = input {
format!("Some({})", raw)
} else {
"None".to_string()
};
format!("unsafe {{ icu_locale::subtags::Language::from_raw_unchecked({}) }}", val)
}

fn region_output(input: u32) -> String {
format!("unsafe {{ icu_locale::subtags::Region::from_raw_unchecked({}) }}", input)
}

#[proc_macro]
pub fn language(input: TokenStream) -> TokenStream {
let val = get_value_from_token_stream(input);

let parsed: icu_locale::subtags::Language = val.parse().expect("Malformed Language Subtag");

let lang = language_output(parsed.into_raw());

lang.parse().unwrap()
}

#[proc_macro]
pub fn region(input: TokenStream) -> TokenStream {
let val = get_value_from_token_stream(input);

let parsed: icu_locale::subtags::Region = val.parse().expect("Malformed Region Subtag");

let region = region_output(parsed.into_raw());

region.parse().unwrap()
}

#[proc_macro]
pub fn langid(input: TokenStream) -> TokenStream {
let val = get_value_from_token_stream(input);

let langid: icu_locale::LanguageIdentifier = val.parse().expect("Malformed Language Identifier");

let lang = language_output(langid.language.into_raw());
let region = if let Some(region) = langid.region {
format!("Some({})", region_output(region.into_raw()))
} else {
"None".to_string()
};

let output = format!(r#"
icu_locale::LanguageIdentifier {{
language: {},
region: {},
..Default::default()
}}
"#, lang, region);

output.parse().unwrap()
}
25 changes: 25 additions & 0 deletions components/locale/macros/tests/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use icu_locale_macros::*;

const LANG_PL: icu_locale::subtags::Language = language!("pL");

#[test]
fn language() {
let lang = language!("En");

assert_eq!(lang, "en");
assert_eq!(LANG_PL, "pl");
}

#[test]
fn region() {
let region = region!("us");

assert_eq!(region, "US");
}

#[test]
fn langid() {
let langid = langid!("en_uS");

assert_eq!(langid.to_string(), "en-US");
}
19 changes: 19 additions & 0 deletions components/locale/src/subtags/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ impl Language {
}
}

pub fn into_raw(self) -> Option<u64> {
self.0.map(|v| v.into())
}

/// Constructor which takes a raw value returned by
/// `into_raw`.
///
/// # Safety
///
/// This function accepts any u64 that is exected to be a valid
/// `TinyStr8` and a valid `Language` subtag.
pub const unsafe fn from_raw_unchecked(v: Option<u64>) -> Self {
if let Some(v) = v {
Self(Some(TinyStr8::new_unchecked(v)))
} else {
Self(None)
}
}

/// A helper function for displaying
/// a `Language` subtag as a `&str`.
///
Expand Down
12 changes: 12 additions & 0 deletions components/locale/src/subtags/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ impl Region {
}
}

pub fn into_raw(self) -> u32 {
self.0.into()
}

/// # Safety
///
/// This function accepts any u64 that is exected to be a valid
/// `TinyStr8` and a valid `Language` subtag.
pub const unsafe fn from_raw_unchecked(v: u32) -> Self {
Self(TinyStr4::new_unchecked(v))
}

/// A helper function for displaying
/// a `Region` subtag as a `&str`.
///
Expand Down
8 changes: 8 additions & 0 deletions components/locale/src/subtags/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ impl Script {
Ok(Self(s.to_ascii_titlecase()))
}

/// # Safety
///
/// This function accepts any u64 that is exected to be a valid
/// `TinyStr8` and a valid `Language` subtag.
pub const unsafe fn from_raw_unchecked(v: u32) -> Self {
Self(TinyStr4::new_unchecked(v))
}

/// A helper function for displaying
/// a `Script` subtag as a `&str`.
///
Expand Down

0 comments on commit f2eb526

Please sign in to comment.