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
pub mod commands;
pub mod templates;
use std::{fs, path::PathBuf};

use clap::{Parser, Subcommand};
use commands::{compile::Compile, init::Init, new_post::NewPost};

use thiserror::Error;

#[derive(Error, Debug)]
pub enum BlogError {
    #[error("IO Error")]
    Io {
        error: std::io::Error,
        message: String,
    },
    #[error("Invalid Front Matter: {0}")]
    InvalidFrontMatter(String),
    #[error("Invalid File Name: {0}")]
    InvalidFileName(String),
    #[error("Invalid File Extension: {0}")]
    SimpleIoError(#[from] std::io::Error),
}

impl BlogError {
    pub fn io(error: std::io::Error, message: String) -> Self {
        BlogError::Io { error, message }
    }
}

type BlogResult<T> = Result<T, BlogError>;

pub trait Run {
    fn run(&self) -> BlogResult<()>;
}

#[derive(Debug, Parser)] // requires `derive` feature
#[command(name = "blog-rs", version, author, about = "A blog CLI")]
#[command(about = "Blogging Tool", long_about = None)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}

impl Cli {
    pub fn run(&self) -> BlogResult<()> {
        self.command.run()
    }
}

#[derive(Debug, Subcommand)]
pub enum Commands {
    #[command(about = "Compile the blog")]
    Compile,
    #[command(about = "Initialize a new blog")]
    Init { path: String },
    #[command(about = "Create a new post")]
    Post { title: String },
}

impl Commands {
    pub fn run(&self) -> BlogResult<()> {
        match self {
            Commands::Compile => Compile.run(),
            Commands::Init { path } => Init.run(path),
            Commands::Post { title } => NewPost.run(title),
        }
    }
}

#[derive(Debug, Default)]
pub struct Config {
    posts: Vec<PathBuf>,
    output: PathBuf,
}

impl Config {
    pub fn load() -> BlogResult<Self> {
        let dir = fs::read_dir("./posts")
            .map_err(|e| BlogError::io(e, "Unable to read posts directory".to_string()))?;

        let legible_posts = dir
            .filter_map(|entry| {
                let entry = entry.ok()?;
                let path = entry.path();
                if path.is_file() {
                    Some(path)
                } else {
                    None
                }
            })
            .collect();

        Ok(Self {
            posts: legible_posts,
            output: PathBuf::from("./static"),
        })
    }
}