1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Defines the [`Post`] type.

use crate::tag::Tag;
use gtmpl::Value;
use std::collections::HashSet;
use std::path::PathBuf;
use url::Url;

/// Represents a blog post.
#[derive(Clone)]
pub struct Post {
    /// The output path where the final post file will be rendered.
    pub file_path: PathBuf,

    /// The address for the rendered post.
    pub url: Url,

    /// The title of the post.
    pub title: String,

    /// The date of the post.
    pub date: String,

    /// The body of the post.
    pub body: String,

    /// The tags associated with the post.
    pub tags: HashSet<Tag>,
}

impl Post {
    /// Converts a [`Post`] into a template-renderable [`Value`], representing
    /// a full post (as opposed to [`Post::summarize`] which represents a
    /// post summary). The resulting [`Value`] has fields:
    ///
    /// * `url`: The url of the post
    /// * `title`: The title of the post
    /// * `date`: The published date of the post
    /// * `body`: The post body
    /// * `tags`: A list of tags associated with the post
    pub fn to_value(&self) -> Value {
        use std::collections::HashMap;
        let mut m = HashMap::new();
        m.insert("url".to_owned(), Value::String(self.url.to_string()));
        m.insert("title".to_owned(), Value::String(self.title.clone()));
        m.insert("date".to_owned(), Value::String(self.date.clone()));
        m.insert("body".to_owned(), Value::String(self.body.clone()));
        m.insert(
            "tags".to_owned(),
            Value::Array(self.tags.iter().map(Value::from).collect()),
        );
        Value::Object(m)
    }

    /// Returns the full post body unless a `<!-- more -->` tag was found, in
    /// which case it returns the text up to that tag (the "summary" text). It
    /// also returns a boolean value indicating whether or not the tag was
    /// found.
    pub fn summary(&self) -> (&str, bool) {
        match self.body.find("<!-- more -->") {
            None => (self.body.as_str(), false),
            Some(idx) => (&self.body[..idx], true),
        }
    }

    /// Converts a [`Post`] into a template-renderable [`Value`] representing a
    /// post summary. The resulting [`Value`] has fields:
    ///
    /// * `url`: The url of the post
    /// * `title`: The title of the post
    /// * `date`: The published date of the post
    /// * `summary`: The post summary if there is a `<!-- more -->` tag or else
    ///   the full post body
    /// * `summarized`: A boolean value representing whether or not a `<!--
    ///   more -->` tag was found and thus the post was truncated.
    /// * `tags`: A list of tags associated with the post
    pub fn summarize(&self) -> Value {
        use std::collections::HashMap;
        let (summary, summarized) = self.summary();

        let mut m = HashMap::new();
        m.insert("url".to_owned(), Value::String(self.url.to_string()));
        m.insert("title".to_owned(), Value::String(self.title.clone()));
        m.insert("date".to_owned(), Value::String(self.date.clone()));
        m.insert("summary".to_owned(), Value::String(summary.to_string()));
        m.insert("summarized".to_owned(), Value::Bool(summarized));
        m.insert(
            "tags".to_owned(),
            Value::Array(self.tags.iter().map(Value::from).collect()),
        );
        Value::Object(m)
    }
}