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: Option<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(ref 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 tags = if tags.as_ref().map(|t| t.len()).unwrap_or(0) == 0 {
65            None
66        } else {
67            tags
68        };
69        let fm = Frontmatter {
70            pagination: pagination
71                .and_then(|p| pagination::PaginationConfig::from_config(p, &permalink)),
72            permalink,
73            slug: slug.ok_or_else(|| anyhow::format_err!("No slug"))?,
74            title: title.ok_or_else(|| anyhow::format_err!("No title"))?,
75            description,
76            excerpt,
77            categories: categories.unwrap_or_default(),
78            tags,
79            excerpt_separator: excerpt_separator.unwrap_or_else(|| "\n\n".into()),
80            published_date,
81            format: format.unwrap_or_default(),
82            #[cfg(feature = "preview_unstable")]
83            templated: templated.unwrap_or(false),
84            #[cfg(not(feature = "preview_unstable"))]
85            templated: templated.unwrap_or(true),
86            layout,
87            is_draft: is_draft.unwrap_or(false),
88            weight: weight.unwrap_or(0),
89            collection,
90            data,
91        };
92
93        if let Some(pagination) = &fm.pagination {
94            if !pagination::is_date_index_sorted(&pagination.date_index) {
95                anyhow::bail!("date_index is not correctly sorted: Year > Month > Day...");
96            }
97        }
98        Ok(fm)
99    }
100}
101
102impl fmt::Display for Frontmatter {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        let converted = serde_yaml::to_string(self).expect("should always be valid");
105        let subset = converted
106            .strip_prefix("---")
107            .unwrap_or(converted.as_str())
108            .trim();
109        let converted = if subset == "{}" { "" } else { subset };
110        if converted.is_empty() {
111            Ok(())
112        } else {
113            write!(f, "{converted}")
114        }
115    }
116}