Skip to content

Commit

Permalink
Merge pull request #31 from YouKnow-sys/master
Browse files Browse the repository at this point in the history
refactored and updated the modules
  • Loading branch information
omid authored Nov 20, 2023
2 parents 4b69ee2 + 368644f commit e6f46e0
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 306 deletions.
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "persian-tools"
version = "0.1.0"
authors = ["rustland-fa developers"]
edition = "2018"
edition = "2021"
license = "MIT"
readme = "README.md"
repository = "https://github.com/rustland-fa/persian-tools-rs"
Expand All @@ -13,12 +13,12 @@ keywords = ["persian", "tool", "format", "tools", "iran"]
categories = ["algorithms", "date-and-time", "encoding", "internationalization", "localization"]
include = ["src/", "*.md"]

[features]
default = []
translate = ["dep:reqwest"]

[dependencies]
lazy_static = "1.4.0"
regex = "1.4.3"
phf = { version = "0.8", features = ["macros"] }
strum = { version = "0.20", features = ["derive"] }
num-traits = "0.2.14"
reqwest = { version = "0.11.2", features = ["blocking"] }
# persian-tools = { path = "." }
strum = { version = "0.25", features = ["derive"] }
num-traits = "0.2"
reqwest = { version = "0.11", features = ["blocking"], optional = true }

4 changes: 2 additions & 2 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
merge_imports = true
space_after_colon = true
# merge_imports = true
# space_after_colon = true
18 changes: 16 additions & 2 deletions src/banking/bank_codes_table.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::utils::{create_fixed_map, FixedMap};

#[derive(Debug)]
pub struct BankCode {
pub name: &'static str,
pub code: &'static str,
pub name: &'static str,
}

pub static BANK_CODE_TABLE: phf::Map<&'static str, BankCode> = phf::phf_map! {
pub static BANK_CODE_TABLE: FixedMap<&str, BankCode> = create_fixed_map! {
"636214" => BankCode {
name: "بانک آینده",
code: "636214",
Expand Down Expand Up @@ -186,3 +188,15 @@ pub static BANK_CODE_TABLE: phf::Map<&'static str, BankCode> = phf::phf_map! {
code: "628157",
},
};

impl FixedMap<&str, BankCode> {
pub fn get_bank_code_from_name(&self, name: &str) -> Option<&'static str> {
self.0
.iter()
.find_map(|(_, bc)| (bc.name == name).then_some(bc.code))
}

pub fn get_bank_name_from_code(&self, code: &str) -> Option<&'static str> {
self.get(&code).map(|v| v.name)
}
}
61 changes: 31 additions & 30 deletions src/banking/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::banking::bank_codes_table::BANK_CODE_TABLE;
use crate::impl_trait_for_string_types;
use crate::utils::impl_trait_for_string_types;

pub mod bank_codes_table;
pub mod sheba;
Expand All @@ -9,38 +9,39 @@ pub mod sheba_table;
pub trait Banking: AsRef<str> {
/// Checks if the bank card number is valid or not.
fn is_valid_bank_card_number(&self) -> bool {
let number = self.as_ref();
let len = number.len();
if len < 16
|| number.parse::<u64>().is_err()
|| number[1..10].parse::<u32>().unwrap() == 0
|| number[10..16].parse::<u32>().unwrap() == 0
{
let text = self.as_ref();
if text.len() != 16 {
return false;
}

let mut sum = 0;
let mut even;
let mut sub_digit;
for i in 0..16 {
even = if i % 2 == 0 { 2 } else { 1 };
sub_digit = number[i..i + 1].parse::<i32>().unwrap() * even;
sum += if sub_digit > 9 {
sub_digit - 9
} else {
sub_digit
};
let digits: Vec<u32> = text.chars().map_while(|c| c.to_digit(10)).collect();
if digits.len() != 16 || digits.iter().sum::<u32>() == 0 {
return false;
}

let sum = digits
.into_iter()
.enumerate()
.map(|(idx, x)| {
let mut sub_digit = x * if idx % 2 == 0 { 2 } else { 1 };
if sub_digit > 9 {
sub_digit -= 9
}
sub_digit
})
.sum::<u32>();

sum % 10 == 0
}

/// Get the bank name from card number.
fn get_bank_name_from_card_number(&self) -> String {
fn get_bank_name_from_card_number(&self) -> Option<&str> {
let number = self.as_ref();
if number.is_valid_bank_card_number() {
return BANK_CODE_TABLE[&number[0..6]].name.to_string();
}
number.to_string()
number.is_valid_bank_card_number().then(|| {
BANK_CODE_TABLE
.get_bank_name_from_code(&number[0..6])
.unwrap()
})
}
}

Expand All @@ -67,23 +68,23 @@ mod test {
#[test]
fn get_bank_name_from_card_number_test() {
assert_eq!(
"بانک قوامین",
Some("بانک قوامین"),
"6395991167965615".get_bank_name_from_card_number()
);
assert_eq!(
"بانک کشاورزی",
Some("بانک کشاورزی"),
"6037701689095443".get_bank_name_from_card_number()
);
assert_eq!(
"بانک سامان",
Some("بانک سامان"),
"6219861034529007".get_bank_name_from_card_number()
);
assert_eq!("", "".get_bank_name_from_card_number());
assert_eq!(None, "".get_bank_name_from_card_number());

let string = "6395991167965615".to_string();
assert_eq!("بانک قوامین", string.get_bank_name_from_card_number());
assert_eq!(Some("بانک قوامین"), string.get_bank_name_from_card_number());
assert_eq!(
"بانک قوامین",
Some("بانک قوامین"),
Banking::get_bank_name_from_card_number(&string)
);
}
Expand Down
38 changes: 14 additions & 24 deletions src/banking/sheba.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
use crate::{banking::sheba_table::ShebaInfo, impl_trait_for_string_types};
use lazy_static::lazy_static;
use regex::Regex;
use crate::{banking::sheba_table::ShebaInfo, utils::impl_trait_for_string_types};

use crate::banking::sheba_table::SHEBA_CODE_TABLE;

lazy_static! {
static ref SHEBA_CODE_PATTERN: Regex = Regex::new(r#"^IR[0-9]{24}$"#).unwrap();
}

pub trait ShebaNumber: AsRef<str> {
fn is_valid_sheba_code(&self) -> bool {
match self.iso_7064_mod_97_10() {
Ok(i) => i == 1,
Err(_) => false,
}
self.iso_7064_mod_97_10().is_ok_and(|i| i == 1)
}

fn sheba_code_info(&self) -> Option<&ShebaInfo> {
if !self.is_valid_sheba_code() {
return None;
}
SHEBA_CODE_TABLE.get(&self.as_ref()[4..7])
SHEBA_CODE_TABLE.get(&&self.as_ref()[4..7])
}

fn iso_7064_mod_97_10(&self) -> crate::Result<i32> {
let sheba_code = self.as_ref();
if !SHEBA_CODE_PATTERN.is_match(sheba_code) {
// check if sheba is in valid format (^IR[0-9]{24}$)
if !(sheba_code.len() == 26
&& sheba_code.starts_with("IR")
&& sheba_code.chars().skip(2).all(|c| c.is_ascii_digit()))
{
return Err("invalid sheba code".into());
}

let d1 = sheba_code.as_bytes()[0] - 65 + 10;
let d2 = sheba_code.as_bytes()[1] - 65 + 10;
let mut remainder = format!("{}{}{}{}", &sheba_code[4..], d1, d2, &sheba_code[2..4]);
Expand All @@ -53,20 +49,14 @@ mod sheba_code {

#[test]
fn sheba_code_validate() {
assert_eq!(true, "IR210180000000009190404878".is_valid_sheba_code());
assert_eq!(false, "123332132131432498654433".is_valid_sheba_code());
assert_eq!(
false,
"IR1233321321314324986544323222".is_valid_sheba_code()
);
assert_eq!(false, "IR1233321222".is_valid_sheba_code());
assert!("IR210180000000009190404878".is_valid_sheba_code());
assert!(!"123332132131432498654433".is_valid_sheba_code());
assert!(!"IR1233321321314324986544323222".is_valid_sheba_code());
assert!(!"IR1233321222".is_valid_sheba_code());
}

#[test]
fn sheba_code_info() {
assert_eq!(
true,
"IR210180000000009190404878".sheba_code_info().is_some()
)
assert!("IR210180000000009190404878".sheba_code_info().is_some())
}
}
14 changes: 8 additions & 6 deletions src/banking/sheba_table.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use crate::utils::{create_fixed_map, FixedMap};

#[derive(Debug)]
pub struct ShebaInfo {
nickname: &'static str,
name: &'static str,
persian_name: &'static str,
code: &'static str,
pub code: &'static str,
pub nickname: &'static str,
pub name: &'static str,
pub persian_name: &'static str,
}

pub static SHEBA_CODE_TABLE: phf::Map<&'static str, ShebaInfo> = phf::phf_map! {
pub static SHEBA_CODE_TABLE: FixedMap<&str, ShebaInfo> = create_fixed_map! {
"010" => ShebaInfo{
nickname: "central-bank",
name: "Central Bank of Iran",
Expand Down Expand Up @@ -145,7 +147,7 @@ pub static SHEBA_CODE_TABLE: phf::Map<&'static str, ShebaInfo> = phf::phf_map! {
persian_name: "بانک مهر ایران",
code: "060",
},
"" => ShebaInfo{
"061" => ShebaInfo{
nickname: "shahr",
name: "City Bank",
persian_name: "بانک شهر",
Expand Down
20 changes: 9 additions & 11 deletions src/digit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::impl_trait_for_string_types;
use crate::utils::*;
use std::convert::TryFrom;

/// Supported language variants.
Expand Down Expand Up @@ -60,7 +60,7 @@ pub trait Digit: AsRef<str> {
impl_trait_for_string_types!(Digit);

/// The multipliers of the persian number system, up to a billion.
pub static MULTIPLIERS: phf::Map<&'static str, u32> = phf::phf_map! {
pub static MULTIPLIERS: FixedMap<&str, u32> = create_fixed_map! {
"هزار" => 1_000,
"میلیون" => 1_000_000,
"میلیارد" => 1_000_000_000,
Expand All @@ -72,7 +72,7 @@ pub static MULTIPLIERS: phf::Map<&'static str, u32> = phf::phf_map! {
/// Includes [1-20], [30, 40, ..., 100], and [100, 200, ..., 900]
// TODO: probably move to another file, too much bloat here.
// TOOD: Is it 'nohsad' or 'noh sad'? 'haftsad or 'haft sad'?
pub static FACE_VALUE: phf::Map<&'static str, u16> = phf::phf_map! {
pub static FACE_VALUE: FixedMap<&str, u16> = create_fixed_map! {
"صفر" => 0,
"یک" => 1,
"دو" => 2,
Expand Down Expand Up @@ -122,9 +122,9 @@ impl TryFrom<&str> for TokenType {
type Error = &'static str;

fn try_from(token: &str) -> Result<Self, Self::Error> {
if let Some(v) = FACE_VALUE.get(token) {
if let Some(v) = FACE_VALUE.get(&token) {
Ok(TokenType::FaceValue(*v))
} else if let Some(m) = MULTIPLIERS.get(token) {
} else if let Some(m) = MULTIPLIERS.get(&token) {
Ok(TokenType::Multiplier(*m))
} else {
Err("Unsupported token")
Expand Down Expand Up @@ -158,25 +158,23 @@ pub trait WordsToNumber: AsRef<str> {
+ Copy,
>(
&self,
) -> Result<N, &'static str> {
) -> crate::Result<N> {
// TODO: ^^ maybe make a module-level Result alias.
use std::convert::TryInto;
const CANT_CONVERT: &str = "Given number does not fit in the provided`N`";

let parsed = self
.as_ref()
.split(' ')
.into_iter()
.filter(|t| *t != "و")
.map(TokenType::try_from)
.collect::<Result<Vec<_>, _>>()?;

let mut final_value: N = num_traits::Zero::zero();
let mut intermediary_value: N = num_traits::Zero::zero();

let check_add = |this: N, that: N| this.checked_add(&that).ok_or(CANT_CONVERT);
let check_add = |lhs: N, rhs: N| lhs.checked_add(&rhs).ok_or(CANT_CONVERT);

let checked_mul = |this: N, that: N| this.checked_mul(&that).ok_or(CANT_CONVERT);
let checked_mul = |lhs: N, rhs: N| lhs.checked_mul(&rhs).ok_or(CANT_CONVERT);
let mut last: Option<TokenType> = None;

for t in parsed {
Expand All @@ -189,7 +187,7 @@ pub trait WordsToNumber: AsRef<str> {
}
TokenType::Multiplier(m) => {
if last.map_or(false, |last| matches!(last, TokenType::Multiplier(_))) {
return Err("Incorrect format: two multipliers in a row");
return Err("Incorrect format: two multipliers in a row".into());
}

// a bit of helper: if this is the first iteration, you can omit a 'یک' and we
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod number_suffix;
pub mod persian_content;
pub mod phone_number;
pub mod province;
#[cfg(feature = "translate")]
pub mod translate;
pub(crate) mod utils;

Expand Down
Loading

0 comments on commit e6f46e0

Please sign in to comment.