1use serde::Deserialize;
2use std::{error::Error, fs::metadata};
3
4#[derive(Debug, Deserialize)]
5pub struct Config {
6 pub html_dir: Vec<String>,
7 pub css_dir: Vec<String>,
8 pub output_dir: String,
9 pub assets_dir: Option<Vec<String>>,
10 pub js_map: Option<Vec<String>>,
11 pub ignored_files: Option<Vec<String>>,
12 pub macro_classes: Option<Vec<String>>,
13}
14
15pub fn read_config<'a>() -> Result<Config, ConfigError<'a>> {
16 match std::fs::read_to_string("css-knife.toml") {
17 Ok(s) => match toml::from_str::<Config>(&s) {
18 Ok(config) => Ok(config),
19 Err(e) => Err(ConfigError::ConfigNotFound(e.to_string())),
20 },
21 Err(e) => Err(ConfigError::ConfigNotFound(e.to_string())),
22 }
23}
24
25impl Config {
26 pub fn validate(&self) -> Result<(), ConfigError> {
27 self.check_dirs(
28 &self.html_dir,
29 ConfigError::EmptyDir(ConfigDir::HTMLDir),
30 )?;
31 self.check_dirs(
32 &self.css_dir,
33 ConfigError::EmptyDir(ConfigDir::CSSDir),
34 )?;
35 let output = metadata(&self.output_dir);
36 match output {
37 Ok(data) => {
38 if !data.is_dir() {
39 return Err(ConfigError::NoOutputDir);
40 }
41 }
42 Err(_) => {
43 return Err(ConfigError::NoOutputDir);
44 }
45 }
46 if let Some(assets) = &self.assets_dir {
47 self.check_dirs(
48 assets,
49 ConfigError::EmptyDir(ConfigDir::AssetsDir),
50 )?;
51 }
52 if let Some(js) = &self.js_map {
53 self.check_dirs(js, ConfigError::EmptyDir(ConfigDir::JSDir))?;
54 }
55 if let Some(macro_classes) = &self.macro_classes {
56 self.check_dirs(
57 macro_classes,
58 ConfigError::EmptyDir(ConfigDir::MacroClasses),
59 )?;
60 }
61 Ok(())
62 }
63
64 fn check_dirs<'a>(
65 &self,
66 dirs: &'a Vec<String>,
67 err: ConfigError<'a>,
68 ) -> Result<(), ConfigError<'a>> {
69 let mut empty = true;
70 for dir in dirs {
71 empty = false;
72 if let Ok(data) = metadata(dir) {
73 if !data.is_dir() {
74 return Err(ConfigError::NotDir(dir));
75 }
76 }
77 }
78 if empty {
79 Err(err)
80 } else {
81 Ok(())
82 }
83 }
84}
85
86#[derive(Debug)]
87pub enum ConfigDir {
88 HTMLDir,
89 CSSDir,
90 JSDir,
91 AssetsDir,
92 IgnoredCssDir,
93 MacroClasses,
94}
95
96impl std::fmt::Display for ConfigDir {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self {
99 ConfigDir::HTMLDir => write!(f, "HTML"),
100 ConfigDir::CSSDir => write!(f, "CSS"),
101 ConfigDir::JSDir => write!(f, "JS"),
102 ConfigDir::AssetsDir => write!(f, "Asset"),
103 ConfigDir::IgnoredCssDir => write!(f, "ignored css files"),
104 ConfigDir::MacroClasses => write!(f, "macro files"),
105 }
106 }
107}
108
109#[derive(Debug)]
110pub enum ConfigError<'a> {
111 NoOutputDir,
112 EmptyDir(ConfigDir),
113 NotDir(&'a String),
114 ConfigNotFound(String),
115}
116
117impl<'a> std::fmt::Display for ConfigError<'a> {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 match &self {
120 ConfigError::NoOutputDir => {
121 write!(f, "Output directory not found.")
122 }
123 ConfigError::EmptyDir(dir) => {
124 write!(f, "Array of directories for {} is empty.", dir)
125 }
126 ConfigError::NotDir(item) => {
127 write!(f, "Specified item({}) is not directory.", item)
128 }
129 ConfigError::ConfigNotFound(err) => {
130 write!(f, "Config not found: {}", err)
131 }
132 }
133 }
134}
135
136impl<'a> Error for ConfigError<'a> {}