#![deny(missing_docs)]
#![warn(clippy::pedantic, clippy::cargo)]
#![allow(clippy::fn_params_excessive_bools, clippy::struct_excessive_bools)]
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AbjadError {
#[error("Unrecognized character: {0}")]
UnrecognizedCharacter(String),
}
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct AbjadPrefs {
pub count_shaddah: bool,
pub double_alif_maddah: bool,
pub ignore_lone_hamzah: bool,
pub maghribi_order: bool,
}
pub trait Abjad {
fn abjad(self, prefs: AbjadPrefs) -> u32;
fn abjad_collect_errors(self, prefs: AbjadPrefs) -> (u32, Vec<String>);
fn abjad_strict(self, prefs: AbjadPrefs) -> Result<u32, AbjadError>;
}
impl Abjad for &str {
fn abjad(self, prefs: AbjadPrefs) -> u32 {
let mut abjad_total: u32 = 0;
let mut last_value: u32 = 0;
for character in self.chars() {
if let Ok(new_value) = get_letter_value(
character,
last_value,
prefs.count_shaddah,
prefs.double_alif_maddah,
prefs.ignore_lone_hamzah,
prefs.maghribi_order,
) {
abjad_total += new_value;
last_value = new_value;
} else {
last_value = 0;
}
}
abjad_total
}
fn abjad_collect_errors(self, prefs: AbjadPrefs) -> (u32, Vec<String>) {
let mut abjad_total: u32 = 0;
let mut errors: Vec<String> = Vec::new();
let mut last_value: u32 = 0;
for character in self.chars() {
if let Ok(new_value) = get_letter_value(
character,
last_value,
prefs.count_shaddah,
prefs.double_alif_maddah,
prefs.ignore_lone_hamzah,
prefs.maghribi_order,
) {
abjad_total += new_value;
last_value = new_value;
} else {
errors.push(character.escape_unicode().collect());
last_value = 0;
}
}
(abjad_total, errors)
}
fn abjad_strict(self, prefs: AbjadPrefs) -> Result<u32, AbjadError> {
let mut abjad_total: u32 = 0;
let mut last_value: u32 = 0;
for character in self.chars() {
let new_value = get_letter_value(
character,
last_value,
prefs.count_shaddah,
prefs.double_alif_maddah,
prefs.ignore_lone_hamzah,
prefs.maghribi_order,
)?;
abjad_total += new_value;
last_value = new_value;
}
Ok(abjad_total)
}
}
fn get_letter_value(
character: char,
last_value: u32,
count_shaddah: bool,
double_alif_maddah: bool,
ignore_lone_hamzah: bool,
maghribi_order: bool,
) -> Result<u32, AbjadError> {
let mut letter_value: u32 = 0;
match character {
'ا' | 'أ' | 'إ' | 'ٱ' => letter_value = 1,
'آ' => {
if double_alif_maddah {
letter_value = 2;
} else {
letter_value = 1;
}
}
'ء' => {
if !ignore_lone_hamzah {
letter_value = 1;
}
}
'ب' | 'پ' => letter_value = 2,
'ج' | 'چ' => letter_value = 3,
'د' => letter_value = 4,
'ه' | 'ة' | 'ۀ' => letter_value = 5,
'و' | 'ؤ' => letter_value = 6,
'ز' | 'ژ' => letter_value = 7,
'ح' => letter_value = 8,
'ط' => letter_value = 9,
'ي' | 'ى' | 'ئ' | 'ی' => letter_value = 10,
'ك' | 'ک' | 'گ' => letter_value = 20,
'ل' => letter_value = 30,
'م' => letter_value = 40,
'ن' => letter_value = 50,
'س' => {
if maghribi_order {
letter_value = 300;
} else {
letter_value = 60;
}
}
'ع' => letter_value = 70,
'ف' => letter_value = 80,
'ص' => {
if maghribi_order {
letter_value = 60;
} else {
letter_value = 90;
}
}
'ق' => letter_value = 100,
'ر' => letter_value = 200,
'ش' => {
if maghribi_order {
letter_value = 1000;
} else {
letter_value = 300;
}
}
'ت' => letter_value = 400,
'ث' => letter_value = 500,
'خ' => letter_value = 600,
'ذ' => letter_value = 700,
'ض' => {
if maghribi_order {
letter_value = 90;
} else {
letter_value = 800;
}
}
'ظ' => {
if maghribi_order {
letter_value = 800;
} else {
letter_value = 900;
}
}
'غ' => {
if maghribi_order {
letter_value = 900;
} else {
letter_value = 1000;
}
}
'\u{0651}' => {
if count_shaddah {
letter_value = last_value;
}
}
' ' | '\u{200C}' => {}
_ => {
let escaped: String = character.escape_unicode().collect();
return Err(AbjadError::UnrecognizedCharacter(escaped));
}
}
Ok(letter_value)
}