use crate::{
consts,
metadata::{Database, Format, Metadata, DATABASE},
phone_number::PhoneNumber,
};
use std::{borrow::Cow, fmt};
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Mode {
E164,
International,
National,
Rfc3966,
}
#[derive(Copy, Clone, Debug)]
pub struct Formatter<'n, 'd, 'f> {
number: &'n PhoneNumber,
database: Option<&'d Database>,
mode: Mode,
format: Option<&'f Format>,
}
impl<'n, 'd, 'f> Formatter<'n, 'd, 'f> {
pub fn database<'a>(self, database: &'a Database) -> Formatter<'n, 'a, 'f> {
Formatter {
number: self.number,
database: Some(database),
mode: self.mode,
format: self.format,
}
}
pub fn mode(mut self, mode: Mode) -> Formatter<'n, 'd, 'f> {
self.mode = mode;
self
}
pub fn with<'a>(self, format: &'a Format) -> Formatter<'n, 'd, 'a> {
Formatter {
number: self.number,
database: self.database,
mode: self.mode,
format: Some(format),
}
}
}
pub fn format(number: &PhoneNumber) -> Formatter<'_, 'static, 'static> {
Formatter {
number,
database: None,
mode: Mode::E164,
format: None,
}
}
pub fn format_with<'d, 'n>(
database: &'d Database,
number: &'n PhoneNumber,
) -> Formatter<'n, 'd, 'static> {
Formatter {
number,
database: Some(database),
mode: Mode::E164,
format: None,
}
}
impl<'n, 'd, 'f> fmt::Display for Formatter<'n, 'd, 'f> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let db = self.database.unwrap_or(&DATABASE);
let meta = db
.by_code(&self.number.country().code())
.and_then(|m| m.into_iter().next())
.ok_or(fmt::Error)?;
let national = self.number.national().to_string();
let formatter = self.format.or_else(|| {
formatter(
&national,
if meta.international_formats().is_empty() || self.mode == Mode::National {
meta.formats()
} else {
meta.international_formats()
},
)
});
match self.mode {
Mode::E164 => {
write!(f, "+{}{}", self.number.country().code(), national)?;
}
Mode::International => {
write!(f, "+{} ", self.number.country().code())?;
if let Some(formatter) = formatter {
write!(f, "{}", replace(&national, meta, formatter, None, None))?;
} else {
write!(f, "{}", national)?;
}
if let Some(ext) = self.number.extension() {
write!(
f,
"{}{}",
meta.preferred_extension_prefix().unwrap_or(" ext. "),
ext
)?;
}
}
Mode::National => {
if let Some(formatter) = formatter {
let carrier = self
.number
.carrier()
.and_then(|c| formatter.domestic_carrier().map(|f| (c, f)));
if let Some((carrier, format)) = carrier {
write!(
f,
"{}",
replace(&national, meta, formatter, Some(format), Some(carrier))
)?;
} else if let Some(prefix) = formatter.national_prefix() {
write!(
f,
"{}",
replace(&national, meta, formatter, Some(prefix), None)
)?;
} else {
write!(f, "{}", replace(&national, meta, formatter, None, None))?;
}
} else {
write!(f, "{}", national)?;
}
if let Some(ext) = self.number.extension() {
write!(
f,
"{}{}",
meta.preferred_extension_prefix().unwrap_or(" ext. "),
ext
)?;
}
}
Mode::Rfc3966 => {
write!(f, "tel:+{}-", self.number.country().code())?;
if let Some(formatter) = formatter {
write!(
f,
"{}",
consts::SEPARATOR_PATTERN
.replace_all(&replace(&national, meta, formatter, None, None), "-")
)?;
} else {
write!(f, "{}", national)?;
}
if let Some(ext) = self.number.extension() {
write!(f, ";ext={}", ext)?;
}
}
}
Ok(())
}
}
fn formatter<'a>(number: &str, formats: &'a [Format]) -> Option<&'a Format> {
for format in formats {
let leading = format.leading_digits();
if leading.is_empty()
|| leading
.last()
.unwrap()
.find(number)
.map(|m| m.start() == 0)
.unwrap_or(false)
&& format
.pattern()
.find(number)
.map(|m| m.start() == 0 && m.end() == number.len())
.unwrap_or(false)
{
return Some(format);
}
}
None
}
fn replace(
national: &str,
meta: &Metadata,
formatter: &Format,
transform: Option<&str>,
carrier: Option<&str>,
) -> String {
formatter
.pattern()
.replace(
national,
&*if let Some(transform) = transform {
let first = consts::FIRST_GROUP
.captures(formatter.format())
.unwrap()
.get(1)
.unwrap()
.as_str();
let format = transform.replace(*consts::NP, meta.national_prefix().unwrap_or(""));
let format = format.replace(*consts::FG, &format!("${}", first));
let format = format.replace(*consts::CC, carrier.unwrap_or(""));
consts::FIRST_GROUP.replace(formatter.format(), &*format)
} else {
Cow::Borrowed(formatter.format())
},
)
.into()
}
#[cfg(test)]
mod test {
use crate::country;
use crate::formatter::Mode;
use crate::parser;
#[test]
fn us() {
assert_eq!(
"(650) 253-0000",
parser::parse(Some(country::US), "+1 6502530000")
.unwrap()
.format()
.mode(Mode::National)
.to_string()
);
assert_eq!(
"+1 650-253-0000",
parser::parse(Some(country::US), "+1 6502530000")
.unwrap()
.format()
.mode(Mode::International)
.to_string()
);
assert_eq!(
"(800) 253-0000",
parser::parse(Some(country::US), "+1 8002530000")
.unwrap()
.format()
.mode(Mode::National)
.to_string()
);
assert_eq!(
"+1 800-253-0000",
parser::parse(Some(country::US), "+1 8002530000")
.unwrap()
.format()
.mode(Mode::International)
.to_string()
);
assert_eq!(
"(900) 253-0000",
parser::parse(Some(country::US), "+1 9002530000")
.unwrap()
.format()
.mode(Mode::National)
.to_string()
);
assert_eq!(
"+1 900-253-0000",
parser::parse(Some(country::US), "+1 9002530000")
.unwrap()
.format()
.mode(Mode::International)
.to_string()
);
assert_eq!(
"tel:+1-900-253-0000",
parser::parse(Some(country::US), "+1 9002530000")
.unwrap()
.format()
.mode(Mode::Rfc3966)
.to_string()
);
}
#[test]
fn gb() {
assert_eq!(
"020 7031 3000",
parser::parse(Some(country::GB), "+44 2070313000")
.unwrap()
.format()
.mode(Mode::National)
.to_string()
);
assert_eq!(
"+44 20 7031 3000",
parser::parse(Some(country::GB), "+44 2070313000")
.unwrap()
.format()
.mode(Mode::International)
.to_string()
);
assert_eq!(
"020 7031 3000",
parser::parse(Some(country::GB), "+44 2070313000")
.unwrap()
.format()
.mode(Mode::National)
.to_string()
);
assert_eq!(
"07912 345678",
parser::parse(Some(country::GB), "+44 7912345678")
.unwrap()
.format()
.mode(Mode::National)
.to_string()
);
assert_eq!(
"+44 7912 345678",
parser::parse(Some(country::GB), "+44 7912345678")
.unwrap()
.format()
.mode(Mode::International)
.to_string()
);
}
}