use std::cmp::Ordering;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::LazyLock;
use icu_collator::options::CollatorOptions;
use icu_collator::preferences::CollationNumericOrdering;
use icu_collator::{Collator, CollatorBorrowed, CollatorPreferences};
use thiserror::Error;
use super::{Entry, Meta};
static COLLATOR: LazyLock<CollatorBorrowed<'static>> = LazyLock::new(|| {
let mut prefs = CollatorPreferences::default();
prefs.numeric_ordering = Some(CollationNumericOrdering::True);
let options = CollatorOptions::default();
Collator::try_new(prefs, options).expect("failed to construct collator for sorting the index")
});
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub enum SortField {
Date,
#[default]
Title,
}
#[derive(Error, Debug, Clone)]
#[error("invalid sort field: {0}")]
pub struct SortError(String);
impl FromStr for SortField {
type Err = SortError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"date" => Ok(Self::Date),
"title" => Ok(Self::Title),
"default" => Ok(Self::default()),
other => Err(SortError(other.into())),
}
}
}
impl SortField {
pub fn default_direction(&self) -> SortDirection {
use SortDirection::*;
use SortField::*;
match self {
Date => Descending,
Title => Ascending,
}
}
pub fn compare<M: Meta>(&self, e1: &Entry<M>, e2: &Entry<M>) -> Ordering {
match self {
SortField::Date => e1.date.cmp(&e2.date),
SortField::Title => COLLATOR.compare(&e1.title, &e2.title),
}
}
}
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
pub enum SortDirection {
Ascending,
Descending,
}
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
pub struct Sort {
pub field: SortField,
pub direction: SortDirection,
}
impl FromStr for Sort {
type Err = SortError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (explicit_dir, key) = if let Some(key) = s.strip_prefix('+') {
(Some(SortDirection::Ascending), key)
} else if let Some(key) = s.strip_prefix('-') {
(Some(SortDirection::Descending), key)
} else {
(None, s)
};
let field: SortField = key.parse()?;
let direction = explicit_dir.unwrap_or_else(|| field.default_direction());
Ok(Self { direction, field })
}
}
impl Default for Sort {
fn default() -> Self {
let field = SortField::default();
Self {
field,
direction: field.default_direction(),
}
}
}
#[derive(Debug, Clone)]
pub struct Index {
pub sort: Sort,
pub directories: Vec<PathBuf>,
pub syndicate: Option<Syndicate>,
pub paginate: Option<u32>,
pub max: Option<u32>,
pub compact: bool,
}
#[derive(Debug, Clone)]
pub struct Syndicate {
pub max: Option<u32>,
}