1pub mod categories;
2pub mod lists;
3pub mod flag;
4pub mod files;
5pub mod deploy;
6
7mod structs;
8mod accessors;
9mod serialize_impl;
10
11pub mod correctness;
12
13
14use std::path::Path;
15
16use serde_yaml::{
18 Mapping as YamlMapping,
19 Value as YamlValue,
20};
21
22
23use {
25 categories::value_to_categories,
26 flag::get_flag,
27 files::file_list,
28 lists::as_str_list,
29 deploy::parse_deploy,
30};
31
32use {
33 files::structs::Files,
34 flag::Flag,
35 lists::structs::{ Authors, Hints },
36 categories::Categories,
37};
38pub use deploy::structs::DeployOptions;
39pub use files::structs::File;
40
41use structs::{
43 ValueType,
44 get_type,
45};
46
47
48pub use structs::{
50 YamlVerifyError,
51 YamlAttribVerifyError
52};
53use correctness::YamlCorrectness;
54
55
56
57
58
59#[derive(PartialEq, Debug)]
60pub struct YamlShape {
61 authors: Authors,
62 categories: Categories,
63 hints: Hints,
64 files: Option<Files>,
65
66 deploy: Option<DeployOptions>,
67
68 points: u64,
69 flag: Flag,
70
71 name: String,
72 description: String,
73
74 visible: bool,
75}
76
77
78
79macro_rules! collect_errors {
80 ($($vals:ident),+ $(,)?) => {
81 collect_errors!(@impl left: $($vals,)+; good: []; errors: [])
82 };
83 (@impl left: $val:ident, $($next_vals:ident,)*; good: [$($good_exprs:expr,)*]; errors: [$($err_exprs:expr,)*]) => {
84 match &$val {
85 Ok(_) => collect_errors!(@impl left: $($next_vals,)*; good: [$($good_exprs,)* $val.unwrap(),]; errors: [$($err_exprs,)*]),
86 Err(_) => collect_errors!(@impl left: $($next_vals,)*; good: [$($good_exprs,)*]; errors: [$($err_exprs,)* $val.unwrap_err(),]),
87 }
88 };
89 (@impl left: ; good: [$($good_exprs:expr,)*]; errors: []) => {
90 Ok(($($good_exprs,)*))
91 };
92 (@impl left: ; good: [$($good_exprs:expr,)*]; errors: [$($err_exprs:expr,)*]) => {
93 Err(vec![$($err_exprs,)*])
94 };
95}
96
97macro_rules! get_map {
98 ($base:ident.$key:ident, $map:expr, missing: $err:expr $(,)?) => {
99 $base
100 .get(stringify!($key))
101 .map_or(Err($err), $map)
102 };
103 ($base:ident.$key:ident, $map:expr, $(default $(,)?)?) => {
104 $base
105 .get(stringify!($key))
106 .map_or_else(|| Err(Default::default()), $map)
107 };
108 ($base:ident.$key:ident, $map:expr, missing: $err:expr, error_wrap: $err_mapper:expr $(,)?) => {
109 $base
110 .get(stringify!($key))
111 .map_or(Err($err), $map)
112 .map_err($err_mapper)
113 };
114}
115
116macro_rules! get_primitive {
117 ($base:ident.$key:ident ($fn:ident $(=> $map:expr)?) else $err:expr) => {
118 if let Some(val) = $base.get(stringify!($key)) {
119 val.$fn()$(.map($map))?.ok_or_else(|| $err(get_type(val)))
120 } else { Err($err(ValueType::NULL)) }
121 };
122}
123
124fn verify_yaml(yaml_text: &str, correctness_options: Option<YamlCorrectness>, base_path: &Path) -> Result<YamlShape, YamlVerifyError> {
125 use YamlVerifyError::*;
126 use YamlAttribVerifyError::*;
127 use YamlAttribVerifyError as AttribError;
128
129 let correctness = correctness_options.unwrap_or_default();
130
131 let base: YamlValue = serde_yaml::from_str(yaml_text).map_err(Unparsable)?;
132 let base: &YamlMapping = if let Some(base) = base.as_mapping() { base } else {
133 return Err(BaseNotMap(get_type(&base)))
134 };
135
136 let (
137 categories,
138 authors,
139 hints,
140 files,
141 ) = {
142 let categories = get_map!(
143 base.categories, value_to_categories,
144 default,
145 ).map_err(AttribError::Categories);
146
147 let authors = get_map!(
148 base.authors, as_str_list,
149 default,
150 ).map_err(AttribError::Authors);
151
152 let hints = get_map!(
153 base.hints, as_str_list,
154 ).map_err(AttribError::Hints);
155
156 let files = base.get("files")
157 .map(|value| file_list(value, base_path)).flop()
158 .map_err(Files);
159
160 (categories, authors, hints, files)
161 };
162
163
164 let deploy = base
165 .get("deploy")
166 .map(parse_deploy)
167 .flop()
168 .map_err(Deploy);
169
170
171 let points = get_primitive!(base.value (as_u64) else PointsNotInt);
172
173 let flag = get_map!(
174 base.flag, |value| get_flag(value, base_path),
175 default,
176 ).map_err(AttribError::Flag);
177
178 let name = get_primitive!(base.name (as_str => str::to_string) else NameNotString);
179 let description = get_primitive!(base.description (as_str => str::to_string) else DescNotString);
180 let visible = get_primitive!(base.visible (as_bool) else VisNotBool);
181
182 let (
183 authors,
184 categories,
185 hints,
186 files,
187
188 deploy,
189
190 points,
191 flag,
192
193 name,
194 description,
195
196 visible,
197 ) = collect_errors!(
198 authors,
199 categories,
200 hints,
201 files,
202
203 deploy,
204
205 points,
206 flag,
207
208 name,
209 description,
210
211 visible,
212 ).map_err(PartErrors)?;
213
214 let shape = YamlShape {
215 authors, categories, hints, files,
216 deploy,
217 points, flag,
218 name, description,
219 visible,
220 };
221 correctness.verify(&shape).map_err(Correctness)?;
222
223 Ok(shape)
224}
225
226#[doc(hidden)]
227pub mod __main {
228 use std::path::PathBuf;
229 use std::str::FromStr;
230 use std::sync::atomic::AtomicBool;
231
232 use crate::correctness::YamlCorrectness;
233 use crate::YamlShape;
234
235 pub fn main(yaml_correctness: &YamlCorrectness) {
236 let errors_encountered = AtomicBool::new(false);
237
238 macro_rules! set_err_if {
239 ($result:expr; $err_ctr:ident: CL ($err_print_stmt:expr); $($mapper:expr)?) => {
240 match ($result) {
241 Ok(value) => Some($($mapper)?(value)),
242 Err(err) => {
243 ($err_print_stmt)(err);
244 $err_ctr.store(true, core::sync::atomic::Ordering::SeqCst);
245 None
246 }
247 }
248 };
249 ($result:expr; $err_ctr:ident: $err_print_stmt:expr; $($mapper:expr)?) => {{
250 match ($result) {
251 Ok(value) => Some($($mapper)?(value)),
252 Err(_) => {
253 $err_print_stmt;
254 $err_ctr.store(true, core::sync::atomic::Ordering::SeqCst);
255 None
256 }
257 }
258 }};
259 }
260
261 std::env::args()
262 .skip(1)
263 .filter_map(|path| set_err_if!(
264 PathBuf::from_str(&path);
265 errors_encountered: println!("`{path}` is not a valid path!");
266 ))
267 .filter_map(|mut path| {
268 println!("{:-^40}", path.display());
269 set_err_if!(
270 std::fs::read_to_string(&path);
271 errors_encountered: println!("Failed to read `{}` to string. Check location, permissions, and encoding of the file.", path.display());
272 |data| {
273 path.pop();
274 (data, path)
275 }
276 )
277 })
278 .for_each(|(data, base_path)| {
279 set_err_if!(
280 YamlShape::try_from_str(&data, &yaml_correctness.clone(), Some(&base_path));
281 errors_encountered: CL (|err| eprintln!("{err}"));
282 |yaml| println!("{yaml:#?}")
283 );
284 });
285 if errors_encountered.load(core::sync::atomic::Ordering::SeqCst) {
286 std::process::exit(1);
287 }
288 }
289}
290
291
292trait Flop {
293 type Target;
294 fn flop(self) -> Self::Target;
295}
296impl<T, E> Flop for Option<Result<T, E>> {
297 type Target = Result<Option<T>, E>;
298 fn flop(self) -> Self::Target {
299 if let Some(res) = self {
300 res.map(Some)
301 } else { Ok(None) }
302 }
303}
304impl<T, E> Flop for Result<Option<T>, E> {
305 type Target = Option<Result<T, E>>;
306 fn flop(self) -> Self::Target {
307 match self {
308 Ok(Some(res)) => Some(Ok(res)),
309 Ok(None) => None,
310 Err(e) => Some(Err(e)),
311 }
312 }
313}
314