#![warn(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::cargo)]
#![cfg_attr(test, allow(clippy::non_ascii_literal))]
#![cfg_attr(test, allow(clippy::shadow_unrelated))]
#![allow(unknown_lints)]
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused_qualifications)]
#![warn(variant_size_differences)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_alias))]
#![no_std]
#![doc(html_root_url = "https://docs.rs/focaccia/2.0.0")]
#[cfg(any(test, doctest))]
extern crate std;
use core::cmp::Ordering;
use core::convert::TryFrom;
use core::fmt;
#[cfg(test)]
mod exhaustive;
mod folding;
#[doc = include_str!("../LICENSE-UNICODE")]
#[cfg(doc)]
#[cfg_attr(docsrs, doc(cfg(doc)))]
pub mod unicode_terms {}
pub use folding::{
ascii_case_eq, ascii_casecmp, unicode_full_case_eq, unicode_full_casecmp,
unicode_full_turkic_case_eq, unicode_full_turkic_casecmp,
};
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CaseFold {
Full,
Ascii,
Turkic,
Lithuanian,
}
impl Default for CaseFold {
#[inline]
fn default() -> Self {
Self::Full
}
}
impl CaseFold {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self::Full
}
#[inline]
#[must_use]
pub fn casecmp(self, left: &str, right: &str) -> Ordering {
match self {
Self::Full | Self::Lithuanian => unicode_full_casecmp(left, right),
Self::Ascii => ascii_casecmp(left.as_bytes(), right.as_bytes()),
Self::Turkic => unicode_full_turkic_casecmp(left, right),
}
}
#[inline]
#[must_use]
pub fn case_eq(self, left: &str, right: &str) -> bool {
match self {
Self::Full | Self::Lithuanian => unicode_full_case_eq(left, right),
Self::Ascii => ascii_case_eq(left.as_bytes(), right.as_bytes()),
Self::Turkic => unicode_full_turkic_case_eq(left, right),
}
}
}
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct NoSuchCaseFoldingScheme {
_private: (),
}
impl NoSuchCaseFoldingScheme {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self { _private: () }
}
}
impl fmt::Display for NoSuchCaseFoldingScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("No such Unicode case folding scheme exists")
}
}
impl core::error::Error for NoSuchCaseFoldingScheme {}
impl TryFrom<Option<&str>> for CaseFold {
type Error = NoSuchCaseFoldingScheme;
#[inline]
fn try_from(value: Option<&str>) -> Result<Self, Self::Error> {
Self::try_from(value.map(str::as_bytes))
}
}
impl TryFrom<Option<&[u8]>> for CaseFold {
type Error = NoSuchCaseFoldingScheme;
#[inline]
fn try_from(value: Option<&[u8]>) -> Result<Self, Self::Error> {
match value {
None => Ok(Self::Full),
Some(scheme) if scheme == b"ascii" => Ok(Self::Ascii),
Some(scheme) if scheme == b"turkic" => Ok(Self::Turkic),
Some(scheme) if scheme == b"lithuanian" => Ok(Self::Lithuanian),
Some(_) => Err(NoSuchCaseFoldingScheme::new()),
}
}
}
#[cfg(test)]
mod tests {
use core::cmp::Ordering;
use crate::{CaseFold, NoSuchCaseFoldingScheme};
#[test]
fn case_folding_ascii_case_eq() {
let input = "CAFE";
let output = "cafe";
assert!(CaseFold::Full.case_eq(input, output));
assert!(CaseFold::Full.case_eq(output, input));
assert!(CaseFold::Ascii.case_eq(input, output));
assert!(CaseFold::Ascii.case_eq(output, input));
assert!(CaseFold::Turkic.case_eq(input, output));
assert!(CaseFold::Turkic.case_eq(output, input));
assert!(CaseFold::Lithuanian.case_eq(input, output));
assert!(CaseFold::Lithuanian.case_eq(output, input));
}
#[test]
fn case_folding_ascii_casecmp() {
let input = "CAFE";
let output = "cafe";
assert_eq!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
assert_eq!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
assert_eq!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
assert_eq!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
}
#[test]
fn case_folding_8bit_case_eq() {
let input = "ß";
let output = "ss";
assert!(CaseFold::Full.case_eq(input, output));
assert!(CaseFold::Full.case_eq(output, input));
assert!(!CaseFold::Ascii.case_eq(input, output));
assert!(!CaseFold::Ascii.case_eq(output, input));
assert!(CaseFold::Turkic.case_eq(input, output));
assert!(CaseFold::Turkic.case_eq(output, input));
assert!(CaseFold::Lithuanian.case_eq(input, output));
assert!(CaseFold::Lithuanian.case_eq(output, input));
}
#[test]
fn case_folding_8bit_casecmp() {
let input = "ß";
let output = "ss";
assert_eq!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
assert_eq!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
assert_eq!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
}
#[test]
fn case_folding_turkic_capital_i_with_dot() {
let input = "İ";
let output = "i";
assert!(CaseFold::Turkic.case_eq(input, output));
assert!(CaseFold::Turkic.case_eq(output, input));
assert_eq!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
assert_eq!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
}
#[test]
fn case_folding_multibyte_case_eq() {
let input = "Ńͺ";
let output = "ń ι";
assert!(!CaseFold::Full.case_eq(input, output));
assert!(!CaseFold::Full.case_eq(output, input));
assert!(!CaseFold::Ascii.case_eq(input, output));
assert!(!CaseFold::Ascii.case_eq(output, input));
assert!(!CaseFold::Turkic.case_eq(input, output));
assert!(!CaseFold::Turkic.case_eq(output, input));
assert!(!CaseFold::Lithuanian.case_eq(input, output));
assert!(!CaseFold::Lithuanian.case_eq(output, input));
}
#[test]
fn case_folding_multibyte_casecmp() {
let input = "Ńͺ";
let output = "ń ι";
assert_ne!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
}
#[test]
fn case_folding_4_6_case_eq() {
let input = "℡㏆𝞻";
let output = "telc∕kgσ";
assert!(!CaseFold::Full.case_eq(input, output));
assert!(!CaseFold::Full.case_eq(output, input));
assert!(!CaseFold::Ascii.case_eq(input, output));
assert!(!CaseFold::Ascii.case_eq(output, input));
assert!(!CaseFold::Turkic.case_eq(input, output));
assert!(!CaseFold::Turkic.case_eq(output, input));
assert!(!CaseFold::Lithuanian.case_eq(input, output));
assert!(!CaseFold::Lithuanian.case_eq(output, input));
}
#[test]
fn case_folding_4_6_casecmp() {
let input = "℡㏆𝞻";
let output = "telc∕kgσ";
assert_ne!(CaseFold::Full.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Full.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Ascii.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Ascii.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Turkic.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Turkic.casecmp(output, input), Ordering::Equal);
assert_ne!(CaseFold::Lithuanian.casecmp(input, output), Ordering::Equal);
assert_ne!(CaseFold::Lithuanian.casecmp(output, input), Ordering::Equal);
}
#[test]
fn error_display_is_not_empty() {
use core::fmt::Write as _;
use std::string::String;
let tc = NoSuchCaseFoldingScheme::new();
let mut buf = String::new();
write!(&mut buf, "{tc}").unwrap();
assert!(!buf.is_empty());
}
}
#[cfg(doctest)]
macro_rules! readme {
($x:expr) => {
#[doc = $x]
mod readme {}
};
() => {
readme!(include_str!("../README.md"));
};
}
#[cfg(doctest)]
readme!();