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
use serde::Deserialize;
use std::{error::Error, fs::metadata};

#[derive(Debug, Deserialize)]
pub struct Config {
    pub html_dir: Vec<String>,
    pub css_dir: Vec<String>,
    pub output_dir: String,
    pub assets_dir: Option<Vec<String>>,
    pub js_dir: Option<Vec<String>>,
    pub ignored_css_files: Option<Vec<String>>,
}

pub fn read_config<'a>() -> Result<Config, ConfigError<'a>> {
    match std::fs::read_to_string("css-knife.toml") {
        Ok(s) => match toml::from_str::<Config>(&s) {
            Ok(config) => Ok(config),
            Err(e) => Err(ConfigError::ConfigNotFound(e.to_string())),
        },
        Err(e) => Err(ConfigError::ConfigNotFound(e.to_string())),
    }
}

impl Config {
    pub fn validate(&self) -> Result<(), ConfigError> {
        self.check_dirs(
            &self.html_dir,
            ConfigError::EmptyDir(ConfigDir::HTMLDir),
        )?;
        self.check_dirs(
            &self.css_dir,
            ConfigError::EmptyDir(ConfigDir::CSSDir),
        )?;
        let output = metadata(&self.output_dir);
        match output {
            Ok(data) => {
                if !data.is_dir() {
                    return Err(ConfigError::NoOutputDir);
                }
            }
            Err(_) => {
                return Err(ConfigError::NoOutputDir);
            }
        }
        if let Some(assets) = &self.assets_dir {
            self.check_dirs(
                assets,
                ConfigError::EmptyDir(ConfigDir::AssetsDir),
            )?;
        }
        if let Some(js) = &self.js_dir {
            self.check_dirs(js, ConfigError::EmptyDir(ConfigDir::JSDir))?;
        }
        Ok(())
    }

    fn check_dirs<'a>(
        &self,
        dirs: &'a Vec<String>,
        err: ConfigError<'a>,
    ) -> Result<(), ConfigError<'a>> {
        let mut empty = true;
        for dir in dirs {
            empty = false;
            if let Ok(data) = metadata(dir) {
                if !data.is_dir() {
                    return Err(ConfigError::NotDir(dir));
                }
            }
        }
        if empty {
            Err(err)
        } else {
            Ok(())
        }
    }
}

#[derive(Debug)]
pub enum ConfigDir {
    HTMLDir,
    CSSDir,
    JSDir,
    AssetsDir,
    IgnoredCssDir,
}

impl std::fmt::Display for ConfigDir {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConfigDir::HTMLDir => write!(f, "HTML"),
            ConfigDir::CSSDir => write!(f, "CSS"),
            ConfigDir::JSDir => write!(f, "JS"),
            ConfigDir::AssetsDir => write!(f, "Asset"),
            ConfigDir::IgnoredCssDir => write!(f, "ignored css files"),
        }
    }
}

#[derive(Debug)]
pub enum ConfigError<'a> {
    NoOutputDir,
    EmptyDir(ConfigDir),
    NotDir(&'a String),
    ConfigNotFound(String),
}

impl<'a> std::fmt::Display for ConfigError<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self {
            ConfigError::NoOutputDir => {
                write!(f, "Output directory not found.")
            }
            ConfigError::EmptyDir(dir) => {
                write!(f, "Array of directories for {} is empty.", dir)
            }
            ConfigError::NotDir(item) => {
                write!(f, "Specified item({}) is not directory.", item)
            }
            ConfigError::ConfigNotFound(err) => {
                write!(f, "Config not found: {}", err)
            }
        }
    }
}

impl<'a> Error for ConfigError<'a> {}