gazetta_core/model/
index.rs

1//  Copyright (C) 2015 Steven Allen
2//
3//  This file is part of gazetta.
4//
5//  This program is free software: you can redistribute it and/or modify it under the terms of the
6//  GNU General Public License as published by the Free Software Foundation version 3 of the
7//  License.
8//
9//  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10//  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
11//  the GNU General Public License for more details.
12//
13//  You should have received a copy of the GNU General Public License along with this program.  If
14//  not, see <http://www.gnu.org/licenses/>.
15//
16
17use 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}