1use std::{
47 fmt::Debug,
48 fs::{DirEntry, ReadDir},
49 path::PathBuf,
50};
51
52use self::task::DADKTask;
53use anyhow::Result;
54use dadk_config::user::UserConfigFile;
55use log::{debug, error, info};
56
57pub mod task;
58pub mod task_log;
59
60#[derive(Debug)]
64pub struct Parser {
65 config_dir: PathBuf,
67 config_files: Vec<PathBuf>,
69}
70
71pub struct ParserError {
72 pub config_file: Option<PathBuf>,
73 pub error: InnerParserError,
74}
75impl Debug for ParserError {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match &self.error {
78 InnerParserError::IoError(e) => {
79 if let Some(config_file) = &self.config_file {
80 write!(
81 f,
82 "IO Error while parsing config file {}: {}",
83 config_file.display(),
84 e
85 )
86 } else {
87 write!(f, "IO Error while parsing config files: {}", e)
88 }
89 }
90 InnerParserError::TomlError(e) => {
91 if let Some(config_file) = &self.config_file {
92 write!(
93 f,
94 "Toml Error while parsing config file {}: {}",
95 config_file.display(),
96 e
97 )
98 } else {
99 write!(f, "Toml Error while parsing config file: {}", e)
100 }
101 }
102 InnerParserError::TaskError(e) => {
103 if let Some(config_file) = &self.config_file {
104 write!(
105 f,
106 "Error while parsing config file {}: {}",
107 config_file.display(),
108 e
109 )
110 } else {
111 write!(f, "Error while parsing config file: {}", e)
112 }
113 }
114 }
115 }
116}
117
118#[derive(Debug)]
119pub enum InnerParserError {
120 IoError(std::io::Error),
121 TomlError(toml::de::Error),
122 TaskError(String),
123}
124
125impl Parser {
126 pub fn new(config_dir: PathBuf) -> Self {
127 Self {
128 config_dir,
129 config_files: Vec::new(),
130 }
131 }
132
133 pub fn parse(&mut self) -> Result<Vec<(PathBuf, DADKTask)>> {
144 self.scan_config_files()?;
145 info!("Found {} config files", self.config_files.len());
146 let r: Result<Vec<(PathBuf, DADKTask)>> = self.gen_tasks();
147 if r.is_err() {
148 error!("Error while parsing config files: {:?}", r);
149 }
150 return r;
151 }
152
153 fn scan_config_files(&mut self) -> Result<()> {
155 info!("Scanning config files in {}", self.config_dir.display());
156
157 let mut dir_queue: Vec<PathBuf> = Vec::new();
158 dir_queue.push(self.config_dir.clone());
160
161 while !dir_queue.is_empty() {
162 let dir = dir_queue.pop().unwrap();
164 let entries: ReadDir = std::fs::read_dir(&dir)?;
165
166 for entry in entries {
167 let entry: DirEntry = entry?;
168
169 let path: PathBuf = entry.path();
170 if path.is_dir() {
171 dir_queue.push(path);
172 } else if path.is_file() {
173 let extension: Option<&std::ffi::OsStr> = path.extension();
174 if extension.is_none() {
175 continue;
176 }
177 let extension: &std::ffi::OsStr = extension.unwrap();
178 if extension.to_ascii_lowercase() != "toml" {
179 continue;
180 }
181 self.config_files.push(path);
183 }
184 }
185 }
186
187 return Ok(());
188 }
189
190 fn gen_tasks(&self) -> Result<Vec<(PathBuf, DADKTask)>> {
199 let mut result_vec = Vec::new();
200 for config_file in &self.config_files {
201 let task: DADKTask = self.parse_config_file(config_file)?;
202 debug!("Parsed config file {}: {:?}", config_file.display(), task);
203 result_vec.push((config_file.clone(), task));
204 }
205
206 return Ok(result_vec);
207 }
208
209 pub(super) fn parse_config_file(&self, config_file: &PathBuf) -> Result<DADKTask> {
220 log::trace!("Parsing config file {}", config_file.display());
221 let mut task: DADKTask = Self::parse_toml_file(config_file)?;
223
224 task.trim();
226
227 task.validate()?;
229
230 return Ok(task);
231 }
232
233 pub fn parse_toml_file(config_file: &PathBuf) -> Result<DADKTask> {
235 let dadk_user_config = UserConfigFile::load(config_file)?;
236 DADKTask::try_from(dadk_user_config)
237 }
238}