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