use std::{
collections::BTreeMap,
fmt,
fs::File,
io::{self, BufRead},
path::{Path, PathBuf},
str::FromStr,
};
use crate::error::{Error, ErrorKind};
pub fn parse<P, D>(ucd_dir: P) -> Result<Vec<D>, Error>
where
P: AsRef<Path>,
D: UcdFile,
{
let mut xs = vec![];
for result in D::from_dir(ucd_dir)? {
let x = result?;
xs.push(x);
}
Ok(xs)
}
pub fn parse_by_codepoint<P, D>(
ucd_dir: P,
) -> Result<BTreeMap<Codepoint, D>, Error>
where
P: AsRef<Path>,
D: UcdFileByCodepoint,
{
let mut map = BTreeMap::new();
for result in D::from_dir(ucd_dir)? {
let x = result?;
for cp in x.codepoints() {
map.insert(cp, x.clone());
}
}
Ok(map)
}
pub fn parse_many_by_codepoint<P, D>(
ucd_dir: P,
) -> Result<BTreeMap<Codepoint, Vec<D>>, Error>
where
P: AsRef<Path>,
D: UcdFileByCodepoint,
{
let mut map = BTreeMap::new();
for result in D::from_dir(ucd_dir)? {
let x = result?;
for cp in x.codepoints() {
map.entry(cp).or_insert(vec![]).push(x.clone());
}
}
Ok(map)
}
pub fn ucd_directory_version<D: ?Sized + AsRef<Path>>(
ucd_dir: &D,
) -> Result<(u64, u64, u64), Error> {
fn ucd_directory_version_inner(
ucd_dir: &Path,
) -> Result<(u64, u64, u64), Error> {
let re_version_rx = regex!(r"-([0-9]+).([0-9]+).([0-9]+).txt");
let proplist = ucd_dir.join("PropList.txt");
let contents = first_line(&proplist)?;
let caps = match re_version_rx.captures(&contents) {
Some(c) => c,
None => {
return err!("Failed to find version in line {:?}", contents)
}
};
let capture_to_num = |n| {
caps.get(n).unwrap().as_str().parse::<u64>().map_err(|e| Error {
kind: ErrorKind::Parse(format!(
"Failed to parse version from {:?} in PropList.txt: {}",
contents, e
)),
line: Some(0),
path: Some(proplist.clone()),
})
};
let major = capture_to_num(1)?;
let minor = capture_to_num(2)?;
let patch = capture_to_num(3)?;
Ok((major, minor, patch))
}
ucd_directory_version_inner(ucd_dir.as_ref())
}
fn first_line(path: &Path) -> Result<String, Error> {
let file = std::fs::File::open(path).map_err(|e| Error {
kind: ErrorKind::Io(e),
line: None,
path: Some(path.into()),
})?;
let mut reader = std::io::BufReader::new(file);
let mut line_contents = String::new();
reader.read_line(&mut line_contents).map_err(|e| Error {
kind: ErrorKind::Io(e),
line: None,
path: Some(path.into()),
})?;
Ok(line_contents)
}
pub fn parse_codepoint_association<'a>(
line: &'a str,
) -> Result<(Codepoints, &'a str), Error> {
let re_parts = regex!(
r"(?x)
^
\s*(?P<codepoints>[^\s;]+)\s*;
\s*(?P<property>[^;\x23]+)\s*
",
);
let caps = match re_parts.captures(line.trim()) {
Some(caps) => caps,
None => return err!("invalid PropList line: '{}'", line),
};
let property = match caps.name("property") {
Some(property) => property.as_str().trim(),
None => {
return err!(
"could not find property name in PropList line: '{}'",
line
)
}
};
Ok((caps["codepoints"].parse()?, property))
}
pub fn parse_codepoint_sequence(s: &str) -> Result<Vec<Codepoint>, Error> {
let mut cps = vec![];
for cp in s.trim().split_whitespace() {
cps.push(cp.parse()?);
}
Ok(cps)
}
pub fn parse_break_test(line: &str) -> Result<(Vec<String>, String), Error> {
let re_parts = regex!(
r"(?x)
^
(?:÷|×)
(?P<groups>(?:\s[0-9A-Fa-f]{4,5}\s(?:÷|×))+)
\s+
\#(?P<comment>.+)
$
",
);
let re_group = regex!(
r"(?x)
(?P<codepoint>[0-9A-Fa-f]{4,5})\s(?P<kind>÷|×)
",
);
let caps = match re_parts.captures(line.trim()) {
Some(caps) => caps,
None => return err!("invalid break test line: '{}'", line),
};
let comment = caps["comment"].trim().to_string();
let mut groups = vec![];
let mut cur = String::new();
for cap in re_group.captures_iter(&caps["groups"]) {
let cp: Codepoint = cap["codepoint"].parse()?;
let ch = match cp.scalar() {
Some(ch) => ch,
None => {
return err!(
"invalid codepoint '{:X}' in line: '{}'",
cp.value(),
line
)
}
};
cur.push(ch);
if &cap["kind"] == "÷" {
groups.push(cur);
cur = String::new();
}
}
Ok((groups, comment))
}
pub trait UcdFile:
Clone + fmt::Debug + Default + Eq + FromStr<Err = Error> + PartialEq
{
fn relative_file_path() -> &'static Path;
fn file_path<P: AsRef<Path>>(ucd_dir: P) -> PathBuf {
ucd_dir.as_ref().join(Self::relative_file_path())
}
fn from_dir<P: AsRef<Path>>(
ucd_dir: P,
) -> Result<UcdLineParser<File, Self>, Error> {
UcdLineParser::from_path(Self::file_path(ucd_dir))
}
}
pub trait UcdFileByCodepoint: UcdFile {
fn codepoints(&self) -> CodepointIter;
}
#[derive(Debug)]
pub struct UcdLineParser<R, D> {
path: Option<PathBuf>,
rdr: io::BufReader<R>,
line: String,
line_number: u64,
_data: std::marker::PhantomData<D>,
}
impl<D> UcdLineParser<File, D> {
pub(crate) fn from_path<P: AsRef<Path>>(
path: P,
) -> Result<UcdLineParser<File, D>, Error> {
let path = path.as_ref();
let file = File::open(path).map_err(|e| Error {
kind: ErrorKind::Io(e),
line: None,
path: Some(path.to_path_buf()),
})?;
Ok(UcdLineParser::new(Some(path.to_path_buf()), file))
}
}
impl<R: io::Read, D> UcdLineParser<R, D> {
pub(crate) fn new(path: Option<PathBuf>, rdr: R) -> UcdLineParser<R, D> {
UcdLineParser {
path,
rdr: io::BufReader::new(rdr),
line: String::new(),
line_number: 0,
_data: std::marker::PhantomData,
}
}
}
impl<R: io::Read, D: FromStr<Err = Error>> Iterator for UcdLineParser<R, D> {
type Item = Result<D, Error>;
fn next(&mut self) -> Option<Result<D, Error>> {
loop {
self.line_number += 1;
self.line.clear();
let n = match self.rdr.read_line(&mut self.line) {
Err(err) => {
return Some(Err(Error {
kind: ErrorKind::Io(err),
line: None,
path: self.path.clone(),
}))
}
Ok(n) => n,
};
if n == 0 {
return None;
}
if !self.line.starts_with('#') && !self.line.trim().is_empty() {
break;
}
}
let line_number = self.line_number;
Some(self.line.parse().map_err(|mut err: Error| {
err.line = Some(line_number);
err
}))
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Codepoints {
Single(Codepoint),
Range(CodepointRange),
}
impl Default for Codepoints {
fn default() -> Codepoints {
Codepoints::Single(Codepoint::default())
}
}
impl IntoIterator for Codepoints {
type IntoIter = CodepointIter;
type Item = Codepoint;
fn into_iter(self) -> CodepointIter {
match self {
Codepoints::Single(x) => x.into_iter(),
Codepoints::Range(x) => x.into_iter(),
}
}
}
impl FromStr for Codepoints {
type Err = Error;
fn from_str(s: &str) -> Result<Codepoints, Error> {
if s.contains("..") {
CodepointRange::from_str(s).map(Codepoints::Range)
} else {
Codepoint::from_str(s).map(Codepoints::Single)
}
}
}
impl fmt::Display for Codepoints {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Codepoints::Single(ref x) => x.fmt(f),
Codepoints::Range(ref x) => x.fmt(f),
}
}
}
impl PartialEq<u32> for Codepoints {
fn eq(&self, other: &u32) -> bool {
match *self {
Codepoints::Single(ref x) => x == other,
Codepoints::Range(ref x) => x == &(*other, *other),
}
}
}
impl PartialEq<Codepoint> for Codepoints {
fn eq(&self, other: &Codepoint) -> bool {
match *self {
Codepoints::Single(ref x) => x == other,
Codepoints::Range(ref x) => x == &(*other, *other),
}
}
}
impl PartialEq<(u32, u32)> for Codepoints {
fn eq(&self, other: &(u32, u32)) -> bool {
match *self {
Codepoints::Single(ref x) => &(x.value(), x.value()) == other,
Codepoints::Range(ref x) => x == other,
}
}
}
impl PartialEq<(Codepoint, Codepoint)> for Codepoints {
fn eq(&self, other: &(Codepoint, Codepoint)) -> bool {
match *self {
Codepoints::Single(ref x) => &(*x, *x) == other,
Codepoints::Range(ref x) => x == other,
}
}
}
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord,
)]
pub struct CodepointRange {
pub start: Codepoint,
pub end: Codepoint,
}
impl IntoIterator for CodepointRange {
type IntoIter = CodepointIter;
type Item = Codepoint;
fn into_iter(self) -> CodepointIter {
CodepointIter { next: self.start.value(), range: self }
}
}
impl FromStr for CodepointRange {
type Err = Error;
fn from_str(s: &str) -> Result<CodepointRange, Error> {
let re_parts = regex!(r"^(?P<start>[A-Z0-9]+)\.\.(?P<end>[A-Z0-9]+)$");
let caps = match re_parts.captures(s) {
Some(caps) => caps,
None => return err!("invalid codepoint range: '{}'", s),
};
let start = caps["start"].parse().or_else(|err| {
err!("failed to parse '{}' as a codepoint range: {}", s, err)
})?;
let end = caps["end"].parse().or_else(|err| {
err!("failed to parse '{}' as a codepoint range: {}", s, err)
})?;
Ok(CodepointRange { start, end })
}
}
impl fmt::Display for CodepointRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
impl PartialEq<(u32, u32)> for CodepointRange {
fn eq(&self, other: &(u32, u32)) -> bool {
&(self.start.value(), self.end.value()) == other
}
}
impl PartialEq<(Codepoint, Codepoint)> for CodepointRange {
fn eq(&self, other: &(Codepoint, Codepoint)) -> bool {
&(self.start, self.end) == other
}
}
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord,
)]
pub struct Codepoint(u32);
impl Codepoint {
pub fn from_u32(n: u32) -> Result<Codepoint, Error> {
if n > 0x10FFFF {
err!("{:x} is not a valid Unicode codepoint", n)
} else {
Ok(Codepoint(n))
}
}
pub fn value(self) -> u32 {
self.0
}
pub fn scalar(self) -> Option<char> {
char::from_u32(self.0)
}
}
impl IntoIterator for Codepoint {
type IntoIter = CodepointIter;
type Item = Codepoint;
fn into_iter(self) -> CodepointIter {
let range = CodepointRange { start: self, end: self };
CodepointIter { next: self.value(), range }
}
}
impl FromStr for Codepoint {
type Err = Error;
fn from_str(s: &str) -> Result<Codepoint, Error> {
match u32::from_str_radix(s, 16) {
Ok(n) => Codepoint::from_u32(n),
Err(err) => {
return err!(
"failed to parse '{}' as a hexadecimal codepoint: {}",
s,
err
);
}
}
}
}
impl fmt::Display for Codepoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04X}", self.0)
}
}
impl PartialEq<u32> for Codepoint {
fn eq(&self, other: &u32) -> bool {
self.0 == *other
}
}
impl PartialEq<Codepoint> for u32 {
fn eq(&self, other: &Codepoint) -> bool {
*self == other.0
}
}
#[derive(Debug)]
pub struct CodepointIter {
next: u32,
range: CodepointRange,
}
impl Iterator for CodepointIter {
type Item = Codepoint;
fn next(&mut self) -> Option<Codepoint> {
if self.next > self.range.end.value() {
return None;
}
let current = self.next;
self.next += 1;
Some(Codepoint::from_u32(current).unwrap())
}
}