aboutsummaryrefslogtreecommitdiff
path: root/src/archiver/mod.rs
blob: 4631bcbfc040f0a9badb453ceedec1574cfa19da (plain)
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
mod gemini;
mod gopher;
mod raw;

use crate::template::{TemplateContext, TemplateValue};
use std::collections::HashMap;
use std::fs::read_dir;
use std::io::Result;
use std::path::{Path, PathBuf};
use time::{format_description::FormatItem, macros::format_description, OffsetDateTime};

const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]");

struct ArchiveEntry {
    id: String,
    slug: String,
}

type Archiver = fn(&Path, &Path, &Path, &TemplateContext) -> Result<()>;

impl ArchiveEntry {
    pub fn to_template_context(archive_entries: &[ArchiveEntry]) -> TemplateContext {
        let mut context = HashMap::new();

        let archive_entries_collection = archive_entries
            .iter()
            .map(ArchiveEntry::to_template_value)
            .collect();

        context.insert(
            "archive_length".to_string(),
            TemplateValue::Unsigned(archive_entries.len().try_into().unwrap()),
        );
        context.insert(
            "posts".to_string(),
            TemplateValue::Collection(archive_entries_collection),
        );

        context
    }

    pub fn to_template_value(&self) -> TemplateContext {
        let mut context = HashMap::new();

        context.insert("id".to_string(), TemplateValue::String(self.id.clone()));

        context.insert("slug".to_string(), TemplateValue::String(self.slug.clone()));

        if let Some(title) = self.title() {
            context.insert("title".to_string(), TemplateValue::String(title));
        }

        context
    }

    fn title(&self) -> Option<String> {
        let date = OffsetDateTime::from_unix_timestamp_nanos(
            (self.id.parse::<u64>().ok()? * 1_000_000).into(),
        )
        .ok()?;
        let short_date = date.format(&DATE_FORMAT).ok()?;
        let title = self.slug.replace('-', " ");
        Some(format!("{short_date} {title}"))
    }
}

fn read_archive(archive_directory: &PathBuf) -> Vec<ArchiveEntry> {
    let mut archive_entries = Vec::new();
    if let Ok(entries) = read_dir(archive_directory) {
        for entry in entries.filter_map(Result::ok) {
            let entry_path = entry.path();
            let post_id = entry.file_name();
            if let Ok(entry_type) = entry.file_type() {
                if entry_type.is_dir() {
                    if let Ok(candidates) = read_dir(&entry_path) {
                        for candidate in candidates.filter_map(Result::ok) {
                            let candidate_path = candidate.path();
                            match candidate_path.extension() {
                                Some(extension) => {
                                    if extension == "gmi" {
                                        if let Some(slug) = candidate_path.file_stem() {
                                            if let (Some(post_id), Some(slug)) =
                                                (post_id.to_str(), slug.to_str())
                                            {
                                                archive_entries.push(ArchiveEntry {
                                                    id: post_id.to_string(),
                                                    slug: slug.to_string(),
                                                });
                                            }
                                        }
                                    }
                                }
                                _ => continue,
                            }
                        }
                    }
                }
            }
        }
    }

    archive_entries.sort_by(|a, b| b.id.cmp(&a.id));
    archive_entries
}

pub fn archive(
    archive_directory: &PathBuf,
    template_directory: &Path,
    output_directory: &Path,
) -> Result<()> {
    let archivers = available_archivers();
    let archive_entries = read_archive(archive_directory);
    let context = ArchiveEntry::to_template_context(&archive_entries);
    for archiver in archivers {
        archiver(
            archive_directory,
            template_directory,
            output_directory,
            &context,
        )?;
    }
    Ok(())
}

fn available_archivers() -> Vec<Archiver> {
    vec![raw::archive, gemini::archive, gopher::archive]
}