cobalt/cobalt_model/
frontmatter.rs

1use std::fmt;
2
3use cobalt_config::DateTime;
4use cobalt_config::SourceFormat;
5use liquid;
6use serde::Serialize;
7
8use super::pagination;
9use crate::error::Result;
10
11#[derive(Debug, Eq, PartialEq, Default, Clone, Serialize)]
12#[serde(deny_unknown_fields, default)]
13pub struct Frontmatter {
14    pub permalink: cobalt_config::Permalink,
15    pub slug: liquid::model::KString,
16    pub title: liquid::model::KString,
17    pub description: Option<liquid::model::KString>,
18    pub excerpt: Option<liquid::model::KString>,
19    pub categories: Vec<liquid::model::KString>,
20    pub tags: Vec<liquid::model::KString>,
21    pub excerpt_separator: liquid::model::KString,
22    pub published_date: Option<DateTime>,
23    pub format: SourceFormat,
24    pub templated: bool,
25    pub layout: Option<liquid::model::KString>,
26    pub is_draft: bool,
27    pub weight: i32,
28    pub collection: liquid::model::KString,
29    pub data: liquid::Object,
30    pub pagination: Option<pagination::PaginationConfig>,
31}
32
33impl Frontmatter {
34    pub fn from_config(config: cobalt_config::Frontmatter) -> Result<Frontmatter> {
35        let cobalt_config::Frontmatter {
36            permalink,
37            slug,
38            title,
39            description,
40            excerpt,
41            categories,
42            tags,
43            excerpt_separator,
44            published_date,
45            format,
46            templated,
47            layout,
48            is_draft,
49            weight,
50            collection,
51            data,
52            pagination,
53        } = config;
54
55        let collection = collection.unwrap_or_default();
56
57        let permalink = permalink.unwrap_or_default();
58
59        if let Some(tags) = &tags {
60            if tags.iter().any(|x| x.trim().is_empty()) {
61                anyhow::bail!("Empty strings are not allowed in tags");
62            }
63        }
64        let fm = Frontmatter {
65            pagination: pagination
66                .and_then(|p| pagination::PaginationConfig::from_config(p, &permalink)),
67            permalink,
68            slug: slug.ok_or_else(|| anyhow::format_err!("No slug"))?,
69            title: title.ok_or_else(|| anyhow::format_err!("No title"))?,
70            description,
71            excerpt,
72            categories: categories.unwrap_or_default(),
73            tags: tags.unwrap_or_default(),
74            excerpt_separator: excerpt_separator.unwrap_or_else(|| "\n\n".into()),
75            published_date,
76            format: format.unwrap_or_default(),
77            #[cfg(feature = "preview_unstable")]
78            templated: templated.unwrap_or(false),
79            #[cfg(not(feature = "preview_unstable"))]
80            templated: templated.unwrap_or(true),
81            layout,
82            is_draft: is_draft.unwrap_or(false),
83            weight: weight.unwrap_or(0),
84            collection,
85            data,
86        };
87
88        if let Some(pagination) = &fm.pagination {
89            if !pagination::is_date_index_sorted(&pagination.date_index) {
90                anyhow::bail!("date_index is not correctly sorted: Year > Month > Day...");
91            }
92        }
93        Ok(fm)
94    }
95}
96
97impl fmt::Display for Frontmatter {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        let converted = serde_yaml::to_string(self).expect("should always be valid");
100        let subset = converted
101            .strip_prefix("---")
102            .unwrap_or(converted.as_str())
103            .trim();
104        let converted = if subset == "{}" { "" } else { subset };
105        if converted.is_empty() {
106            Ok(())
107        } else {
108            write!(f, "{converted}")
109        }
110    }
111}