use crate::{ScriptDate, ScriptDateError};
use std::{
error::Error,
fmt::{Display, Formatter, Result as FmtResult},
num::ParseIntError,
str::FromStr,
};
use super::ScriptCode;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Column {
Alias,
Code,
Date,
NameEnglish,
NameFrench,
Number,
UnicodeVersion,
}
#[derive(Clone, Copy, Debug)]
pub enum VersionPart {
Major,
Minor,
}
#[derive(Clone, Debug)]
pub enum ParseError {
ColumnMissing(Column),
Integer {
column: Column,
source: ParseIntError,
value: String,
},
InvalidDate {
source: ScriptDateError,
value: String,
},
MissingVersionPart {
part: VersionPart,
value: String,
},
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.write_str(self.description())
}
}
impl Error for ParseError {
fn description(&self) -> &str {
use self::ParseError::*;
match self {
ColumnMissing(_) => "A column was missing from the row",
Integer { .. } => "An integer couldn't be parsed",
InvalidDate { .. } => "A date in the data was invalid",
MissingVersionPart { .. } => "Only a partial amount of the unicode version is present",
}
}
}
pub fn parse_lines<'a>(
input: &'a str,
) -> impl Iterator<Item = Result<ScriptCode<'a>, ParseError>> + 'a {
input.lines().filter_map(|line| {
if line.starts_with('#') || line.trim().is_empty() {
return None;
}
Some(parse_line(line))
})
}
pub fn parse_line(line: &str) -> Result<ScriptCode<'_>, ParseError> {
let mut columns = line.split(';');
let code = columns.next().ok_or(ParseError::ColumnMissing(Column::Code))?;
let num = columns.next().ok_or(ParseError::ColumnMissing(Column::Number))?;
let name_en = columns.next().ok_or(ParseError::ColumnMissing(Column::NameEnglish))?;
let name_fr = columns.next().ok_or(ParseError::ColumnMissing(Column::NameFrench))?;
let alias = match columns.next() {
Some("") => None,
Some(v) => Some(v),
None => return Err(ParseError::ColumnMissing(Column::Alias)),
};
let version = match columns.next() {
Some("") => None,
Some(version) => {
let mut parts = version.split('.');
let major_str = parts.next().ok_or_else(|| ParseError::MissingVersionPart {
part: VersionPart::Major,
value: version.to_owned(),
})?;
let major = parse_version_part(major_str)?;
let minor_str = parts.next().ok_or_else(|| ParseError::MissingVersionPart {
part: VersionPart::Minor,
value: version.to_owned(),
})?;
let minor = parse_version_part(minor_str)?;
Some((major, minor))
},
None => return Err(ParseError::ColumnMissing(Column::UnicodeVersion)),
};
let date = columns.next().ok_or(ParseError::ColumnMissing(Column::Date))?;
let date = ScriptDate::from_str(&date).map_err(|source| ParseError::InvalidDate {
source,
value: date.to_owned(),
})?;
Ok(ScriptCode {
alias: alias.map(Into::into),
code: code.into(),
date,
name: name_en.into(),
name_french: name_fr.into(),
num: num.into(),
unicode_version: version,
})
}
fn parse_version_part(
part: &str,
) -> Result<u8, ParseError> {
part.parse().map_err(|source| ParseError::Integer {
column: Column::UnicodeVersion,
source,
value: part.to_owned(),
})
}