iso15924 0.1.0

ISO 15924 data.
Documentation
//! The `ScriptCode` definition with provided parsing functionality.

pub mod list;
pub mod parser;

use crate::ScriptDate;
use std::borrow::Cow;

/// Representation of each script code with all of the information provided by
/// the standard.
///
/// # Serde
///
/// This struct derives serde's `Deserialize` and `Serialize` if you enable the
/// `serde` feature.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct ScriptCode<'a> {
    /// The `Property Value Alias` as defined by unicode.org.
    ///
    /// The definition is located here:
    ///
    /// <http://www.unicode.org/Public/UNIDATA/PropertyValueAliases.txt>
    pub alias: Option<Cow<'a, str>>,
    /// 4-character representation of the script code.
    pub code: Cow<'a, str>,
    /// The date of the introduction of the script to the standard.
    pub date: ScriptDate,
    /// The English name of the script code.
    pub name: Cow<'a, str>,
    /// The French name of the script code.
    pub name_french: Cow<'a, str>,
    /// Numeric 3-digit representation of the script code.
    pub num: Cow<'a, str>,
    /// The version of the Unicode specification that the script was added in.
    pub unicode_version: Option<(u8, u8)>,
}

impl ScriptCode<'_> {
    /// Returns all of the script codes in no guarenteed order.
    pub fn all() -> &'static [ScriptCode<'static>] {
        self::list::all()
    }

    /// Retrieve a `ScriptCode` via its `alias` (`Property Value Alias`) value if
    /// one exists.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use iso15924::ScriptCode;
    ///
    /// assert!(ScriptCode::by_alias("Ahom").is_some());
    /// ```
    pub fn by_alias(alias: impl AsRef<str>) -> Option<&'static ScriptCode<'static>> {
        Self::_by_alias(alias.as_ref())
    }

    fn _by_alias(alias: &str) -> Option<&'static ScriptCode<'static>> {
        Self::all().into_iter().find(|s| s.alias.as_ref().map(|a| a == alias).unwrap_or(false))
    }

    /// Retrieve a `ScriptCode` via its `code` value if one exists.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use iso15924::ScriptCode;
    ///
    /// assert!(ScriptCode::by_code("Blis").is_some());
    /// assert!(ScriptCode::by_code("Abza").is_none());
    /// ```
    pub fn by_code(code: impl AsRef<str>) -> Option<&'static ScriptCode<'static>> {
        Self::_by_code(code.as_ref())
    }

    fn _by_code(code: &str) -> Option<&'static ScriptCode<'static>> {
        Self::all().into_iter().find(|s| s.code == code)
    }

    /// Retrieve a `Vec` of `ScriptCode`s with `ScriptDate`s that are within the
    /// range of the `from` and `to` given. The `from` and `to` are both optional,
    /// and can either be `None` or `Some(ScriptDate)` for variations of the range
    /// wanted.
    ///
    /// # Examples
    ///
    /// Getting all `ScriptCode`s between `2005-01-01` and `2012-01-01`:
    ///
    /// ```rust
    /// use iso15924::{ScriptCode, ScriptDate};
    ///
    /// let date_from = ScriptDate::new(2005, 01, 01);
    /// let date_to = ScriptDate::new(2012, 01, 01);
    ///
    /// let scripts = ScriptCode::by_date_range(Some(date_from), Some(date_to));
    /// ```
    ///
    /// Retrieving all `ScriptCode`s after `2005-01-01`:
    ///
    /// ```rust
    /// use iso15924::{ScriptCode, ScriptDate};
    ///
    /// let date_from = ScriptDate::new(2005, 01, 01);
    ///
    /// let scripts = ScriptCode::by_date_range(Some(date_from), None);
    /// ```
    ///
    /// Retrieving all `ScriptCode`s before `2012-01-01`:
    ///
    /// ```rust
    /// use iso15924::{ScriptCode, ScriptDate};
    ///
    /// let date_to = ScriptDate::new(2012, 01, 01);
    ///
    /// let scripts = ScriptCode::by_date_range(None, Some(date_to));
    /// ```
    ///
    /// Consequentially, you can also retrieve no values:
    ///
    /// ```rust
    /// use iso15924::ScriptCode;
    ///
    /// assert_eq!(0, ScriptCode::by_date_range(None, None).len());
    /// ```
    pub fn by_date_range(
        from: Option<ScriptDate>,
        to: Option<ScriptDate>,
    ) -> Vec<&'static ScriptCode<'static>> {
        let from_do = from.is_some();
        let to_do = to.is_some();

        // If searching via neither the given `from` or `to`, then nothing will be
        // found, so just return an empty `Vec`.
        if !from_do && !to_do {
            return Vec::new();
        }

        let mut codes = Vec::new();

        for code in Self::all() {
            // If the date of the given `from` is less than that of the `code`, then
            // don't push it to the `Vec`.
            if let Some(from) = from {
                if code.date < from {
                    continue;
                }
            }

            // If the date of the given `to` is greater than that of the `code`,
            // then don't push it to the `Vec`.
            if let Some(to) = to {
                if code.date > to {
                    continue;
                }
            }

            codes.push(code);
        }

        codes
    }

    /// Retrieve a `ScriptCode` via its `name` if it exists:
    ///
    /// ```rust
    /// use iso15924::ScriptCode;
    ///
    /// let script1 = ScriptCode::by_name("Adlam");
    /// assert!(script1.is_some());
    ///
    /// let script2 = ScriptCode::by_name("Aaaaa");
    /// assert!(script2.is_none());
    /// ```
    pub fn by_name(name: impl AsRef<str>) -> Option<&'static ScriptCode<'static>> {
        Self::_by_name(name.as_ref())
    }

    fn _by_name(name: &str) -> Option<&'static ScriptCode<'static>> {
        Self::all().into_iter().find(|s| s.name == name)
    }

    /// Retrieve a `ScriptCode` via its `name_french` if it exists:
    ///
    /// ```rust
    /// use iso15924::ScriptCode;
    ///
    /// assert!(ScriptCode::by_name_french("arabe").is_some());
    ///
    /// assert!(ScriptCode::by_name_french("aaaaa").is_none());
    /// ```
    pub fn by_name_french(name: impl AsRef<str>) -> Option<&'static ScriptCode<'static>> {
        Self::_by_name_french(name.as_ref())
    }

    fn _by_name_french(name: &str) -> Option<&'static ScriptCode<'static>> {
        Self::all().into_iter().find(|s| s.name_french == name)
    }

    /// Retrieve a `ScriptCode` via its `num` if it exists:
    ///
    /// ```rust
    /// use iso15924::ScriptCode;
    ///
    /// assert!(ScriptCode::by_num("070").is_some());
    /// assert!(ScriptCode::by_num("000").is_none());
    /// ```
    pub fn by_num(num: impl AsRef<str>) -> Option<&'static ScriptCode<'static>> {
        Self::_by_num(num.as_ref())
    }

    fn _by_num(num: &str) -> Option<&'static ScriptCode<'static>> {
        Self::all().into_iter().find(|s| s.num == num)
    }
}

#[cfg(test)]
mod tests {
    use crate::ScriptDate;
    use std::{
        convert::TryFrom,
        error::Error,
    };
    use super::ScriptCode;

    #[test]
    fn test_all() {
        // Test the number of script codes for backwards-compat purposes.
        //
        // This number CAN increase without breaking backwards compatibility,
        // but decreasing the number is a BC break.
        assert_eq!(202, ScriptCode::all().len());
    }

    #[test]
    fn test_get_by_alias() {
        assert!(ScriptCode::by_alias("Ahom").is_some());
        assert!(ScriptCode::by_alias(String::from("Ahom")).is_some());
        assert!(ScriptCode::by_alias("aaaa").is_none());
    }

    #[test]
    fn test_get_by_code() {
        assert!(ScriptCode::by_code("Blis").is_some());
        assert!(ScriptCode::by_code(String::from("Blis")).is_some());
        assert!(ScriptCode::by_alias("abza").is_none());
    }

    #[test]
    fn test_get_by_date_range() -> Result<(), Box<dyn Error>> {
        let f = ScriptDate::try_from((2005, 1, 1))?;
        let t = ScriptDate::try_from((2012, 1, 1))?;

        let r1 = ScriptCode::by_date_range(Some(f), Some(t));
        assert!(r1.len() > 1);
        let r2 = ScriptCode::by_date_range(Some(f), None);
        assert!(r2.len() > 1);
        let r3 = ScriptCode::by_date_range(None, Some(t));
        assert!(r3.len() > 1);
        let r4 = ScriptCode::by_date_range(None, None);
        assert!(r4.is_empty());

        Ok(())
    }

    #[test]
    fn test_get_by_name() {
        assert!(ScriptCode::by_name("Adlam").is_some());
        assert!(ScriptCode::by_name(String::from("Adlam")).is_some());
        assert!(ScriptCode::by_name("Aaaaa").is_none());
    }

    #[test]
    fn test_get_by_name_french() {
        assert!(ScriptCode::by_name_french("arabe").is_some());
        assert!(ScriptCode::by_name_french(String::from("arabe")).is_some());
        assert!(ScriptCode::by_name_french("aaaaa").is_none());
    }

    #[test]
    fn test_get_by_num() {
        assert!(ScriptCode::by_num("070").is_some());
        assert!(ScriptCode::by_num(String::from("070")).is_some());
        assert!(ScriptCode::by_num("000").is_none());
    }
}