gazetta_core/model/
index.rs1use std::cmp::Ordering;
18use std::path::PathBuf;
19use std::str::FromStr;
20use std::sync::LazyLock;
21
22use icu_collator::options::CollatorOptions;
23use icu_collator::preferences::CollationNumericOrdering;
24use icu_collator::{Collator, CollatorBorrowed, CollatorPreferences};
25use thiserror::Error;
26
27use super::{Entry, Meta};
28
29static COLLATOR: LazyLock<CollatorBorrowed<'static>> = LazyLock::new(|| {
30 let mut prefs = CollatorPreferences::default();
31 prefs.numeric_ordering = Some(CollationNumericOrdering::True);
32 let options = CollatorOptions::default();
33 Collator::try_new(prefs, options).expect("failed to construct collator for sorting the index")
34});
35
36#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
37pub enum SortField {
38 Date,
39 #[default]
40 Title,
41}
42
43#[derive(Error, Debug, Clone)]
44#[error("invalid sort field: {0}")]
45pub struct SortError(String);
46
47impl FromStr for SortField {
48 type Err = SortError;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 match s {
52 "date" => Ok(Self::Date),
53 "title" => Ok(Self::Title),
54 "default" => Ok(Self::default()),
55 other => Err(SortError(other.into())),
56 }
57 }
58}
59
60impl SortField {
61 pub fn default_direction(&self) -> SortDirection {
62 use SortDirection::*;
63 use SortField::*;
64 match self {
65 Date => Descending,
66 Title => Ascending,
67 }
68 }
69
70 pub fn compare<M: Meta>(&self, e1: &Entry<M>, e2: &Entry<M>) -> Ordering {
71 match self {
72 SortField::Date => e1.date.cmp(&e2.date),
73 SortField::Title => COLLATOR.compare(&e1.title, &e2.title),
74 }
75 }
76}
77
78#[derive(Clone, Debug, Copy, Eq, PartialEq)]
79pub enum SortDirection {
80 Ascending,
81 Descending,
82}
83
84#[derive(Clone, Debug, Copy, Eq, PartialEq)]
85pub struct Sort {
86 pub field: SortField,
87 pub direction: SortDirection,
88}
89
90impl FromStr for Sort {
91 type Err = SortError;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 let (explicit_dir, key) = if let Some(key) = s.strip_prefix('+') {
95 (Some(SortDirection::Ascending), key)
96 } else if let Some(key) = s.strip_prefix('-') {
97 (Some(SortDirection::Descending), key)
98 } else {
99 (None, s)
100 };
101 let field: SortField = key.parse()?;
102 let direction = explicit_dir.unwrap_or_else(|| field.default_direction());
103 Ok(Self { direction, field })
104 }
105}
106
107impl Default for Sort {
108 fn default() -> Self {
109 let field = SortField::default();
110 Self {
111 field,
112 direction: field.default_direction(),
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
118pub struct Index {
119 pub sort: Sort,
120 pub directories: Vec<PathBuf>,
121 pub syndicate: Option<Syndicate>,
122 pub paginate: Option<u32>,
123 pub max: Option<u32>,
124 pub compact: bool,
125}
126
127#[derive(Debug, Clone)]
128pub struct Syndicate {
129 pub max: Option<u32>,
130}