comtrya_lib/manifests/
load.rs

1use super::Manifest;
2use crate::{
3    contexts::{to_tera, Contexts},
4    manifests::get_manifest_name,
5    tera_functions::register_functions,
6};
7use ignore::WalkBuilder;
8use std::{
9    collections::HashMap, error::Error, ffi::OsStr, fs::canonicalize, ops::Deref, path::PathBuf,
10};
11use tera::Tera;
12use tracing::{error, span};
13
14pub fn load(manifest_path: PathBuf, contexts: &Contexts) -> HashMap<String, Manifest> {
15    let mut manifests: HashMap<String, Manifest> = HashMap::new();
16
17    let mut walker = WalkBuilder::new(&manifest_path);
18
19    // FIXME: get rid of all .unwrap() calls
20    walker
21        .standard_filters(true)
22        .follow_links(false)
23        .same_file_system(true)
24        // Arbitrary for now, 9 "should" be enough?
25        .max_depth(Some(9))
26        .filter_entry(|entry| {
27            !(entry.file_type().map_or(false, |ft| ft.is_dir())
28            && entry.file_name() == OsStr::new("files"))
29        })
30        .build()
31        // Don't walk directories
32        .filter(|entry| {
33            !entry
34                .as_ref()
35                .ok()
36                .and_then(|entry| entry.metadata().ok().map(|entry| entry.is_dir()))
37                .unwrap_or(false)
38        })
39        .filter(|entry| {
40            entry
41                .as_ref()
42                .ok()
43                .and_then(|entry| entry.file_name().to_str())
44                .map(|file_name| {
45                    file_name.ends_with(".yaml")
46                        || file_name.ends_with(".yml")
47                        || file_name.ends_with(".toml")
48                })
49                .unwrap_or(false)
50        })
51        .for_each(|entry| {
52            if let Ok(filename) = entry {
53                let span = span!(
54                    tracing::Level::INFO,
55                    "manifest_load",
56                    manifest = filename.file_name().to_str()
57                )
58                .entered();
59
60                let entry = canonicalize(filename.into_path()).ok().unwrap_or_default();
61                let contents =
62                    std::fs::read_to_string(entry.clone()).unwrap_or_else(|_| String::from(""));
63                let template = contents.as_str();
64
65                let mut tera = Tera::default();
66                register_functions(&mut tera);
67
68                let template = match tera.render_str(template, &to_tera(contexts)) {
69                    Ok(template) => template,
70                    Err(err) => {
71                        match err.source() {
72                            Some(err) => error!(message = err.source()),
73                            None => error!(message = err.to_string().as_str()),
74                        }
75
76                        span.exit();
77
78                        return;
79                    }
80                };
81
82                let manifest: anyhow::Result<Manifest> = match entry
83                    .extension()
84                    .and_then(OsStr::to_str)
85                {
86                    Some("yaml") | Some("yml") => {
87                        serde_yml::from_str::<Manifest>(template.deref()).map_err(anyhow::Error::from)
88                    }
89                    Some("toml") => toml::from_str::<Manifest>(template.deref()).map_err(anyhow::Error::from),
90                    _ => {
91                        error!("Unrecognized file extension for manifest");
92                        span.exit();
93
94                        return;
95                    }
96                };
97
98                match manifest {
99                    Ok(mut manifest) => {
100                        let name = get_manifest_name(&manifest_path, &entry)
101                            .expect("Failed to get manifest name");
102
103                        manifest.root_dir = entry.parent().map(|parent| parent.to_path_buf());
104
105                        manifest.name = Some(name.clone());
106
107                        manifests.insert(name, manifest);
108                    }
109                    Err(err) => {
110                        let manifest_name =
111                            get_manifest_name(&manifest_path, &entry).unwrap_or_default();
112
113                        error!("Manifest '{manifest_name}' in file with path '{}' cannot be parsed. Reason: {err}", &entry.display());
114                    }
115                }
116
117                span.exit();
118            }
119        });
120
121    manifests
122}