huff_utils/
files.rs

1use std::{path::PathBuf, time::SystemTime};
2use uuid::Uuid;
3
4/// An aliased output location to derive from the cli arguments.
5#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
6pub struct OutputLocation(pub String);
7
8impl Default for OutputLocation {
9    fn default() -> Self {
10        Self("./artifacts/".to_string())
11    }
12}
13
14/// File Encapsulation
15#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)]
16pub struct FileSource {
17    /// File ID
18    pub id: Uuid,
19    /// File Path
20    pub path: String,
21    /// File Source
22    pub source: Option<String>,
23    /// Last File Access Time
24    pub access: Option<SystemTime>,
25    /// An Ordered List of File Dependencies
26    pub dependencies: Option<Vec<FileSource>>,
27}
28
29impl FileSource {
30    /// Generates a fully flattened source code for the given `FileSource` and all its dependencies
31    ///
32    /// ### Examples
33    ///
34    /// Let's say you have a file, `a.txt` with two dependencies, `b.txt` and `c.txt`,
35    /// `fully_flatten()` will generate a source code string with the contents of `b.txt` and
36    /// `c.txt` appended to the end of the contents of `a.txt`.
37    pub fn fully_flatten(&self) -> String {
38        // First grab the parent file source
39        let mut full_source = if let Some(s) = &self.source { s.clone() } else { "".to_string() };
40
41        // Then recursively grab source code for dependencies
42        match &self.dependencies {
43            Some(vfs) => {
44                for fs in vfs {
45                    full_source.push_str(&fs.fully_flatten())
46                }
47            }
48            None => {}
49        }
50
51        // Return the full source
52        full_source
53    }
54
55    /// Derives a File Path's directory
56    pub fn derive_dir(path: &str) -> Option<String> {
57        let path = PathBuf::from(path);
58        match path.parent() {
59            Some(p) => p.to_str().map(String::from),
60            None => None,
61        }
62    }
63
64    /// Localizes a file path, if path is relative
65    pub fn localize_file(parent: &str, child: &str) -> Option<String> {
66        let mut prefix = match FileSource::derive_dir(parent) {
67            Some(p) => {
68                if p.is_empty() {
69                    String::from(".")
70                } else {
71                    p
72                }
73            }
74            None => String::from("."),
75        };
76        if child.starts_with("../") {
77            let mut res_str = child.to_string();
78            while res_str.starts_with("../") {
79                let path = PathBuf::from(prefix.clone());
80                match path.parent() {
81                    Some(p) => match p.to_str().map(String::from) {
82                        Some(pref) => {
83                            if pref.is_empty() || prefix.ends_with("..") {
84                                if prefix.is_empty() || prefix == "." {
85                                    prefix = "..".to_string();
86                                } else {
87                                    prefix = format!("../{}", prefix);
88                                }
89                            } else {
90                                prefix = pref
91                            }
92                        }
93                        None => {
94                            tracing::warn!("Failed to convert path to string");
95                            return None
96                        }
97                    },
98                    None => {
99                        tracing::warn!("Failed to find parent for path: {:?}", path);
100                        return None
101                    }
102                }
103                res_str = res_str.replacen("../", "", 1);
104            }
105            Some(format!("{}/{}", prefix, res_str))
106        } else if child.starts_with("./") {
107            Some(child.replacen("./", &format!("{}/", prefix), 1))
108        } else if child.starts_with('/') {
109            Some(child.to_string())
110        } else {
111            Some(format!("{}/{}", prefix, child))
112        }
113    }
114}