huff_neo_utils/file/
file_source.rs1use crate::prelude::Span;
2use crate::time;
3use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5use std::fmt;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9#[derive(Default, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone)]
11#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
12#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
13pub struct FileSource {
14 pub path: String,
16 pub source: Option<String>,
18 #[cfg_attr(feature = "wasm", tsify(type = "number | undefined"))]
20 pub access: Option<time::Time>,
21 pub dependencies: Vec<Arc<FileSource>>,
23}
24
25impl fmt::Debug for FileSource {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 f.debug_struct("FileSource")
29 .field("path", &self.path)
30 .field("source", &"...")
31 .field("access", &self.access)
32 .field("dependencies", &"...")
33 .finish()
34 }
35}
36
37impl FileSource {
38 fn discover_all_file_sources(node_file_source: Arc<FileSource>, visited_nodes: &mut HashSet<String>) -> Vec<Arc<FileSource>> {
40 if visited_nodes.contains(&node_file_source.path) {
41 return vec![];
42 }
43 visited_nodes.insert(node_file_source.path.clone());
44
45 let mut result = vec![];
46 for child_file_source in node_file_source.dependencies.iter() {
47 let mut child_result = FileSource::discover_all_file_sources(child_file_source.clone(), visited_nodes);
48 result.append(&mut child_result);
49 }
50
51 result.push(node_file_source);
52
53 result
54 }
55
56 pub fn fully_flatten(node_file_source: Arc<FileSource>) -> (String, Vec<(Arc<FileSource>, Span)>) {
66 let all_file_sources = FileSource::discover_all_file_sources(node_file_source, &mut HashSet::new());
67
68 let mut full_source = "".to_string();
69 let mut relative_positions = vec![];
70 let mut shift_pos = 0;
71
72 for (i, child_file_source) in all_file_sources.into_iter().enumerate() {
74 let Some(source) = child_file_source.source.clone() else {
75 continue;
76 };
77
78 if i > 0 && !full_source.ends_with('\n') {
80 full_source.push('\n');
81 shift_pos += 1;
82 }
83
84 let span = Span::new(shift_pos..(shift_pos + source.len() - 1), Some(child_file_source.clone()));
85 relative_positions.push((child_file_source, span));
86 shift_pos += source.len();
87 full_source = format!("{full_source}{source}");
88 }
89
90 (full_source, relative_positions)
91 }
92
93 pub fn derive_dir(path: &str) -> Option<String> {
95 let path = PathBuf::from(path);
96 match path.parent() {
97 Some(p) => p.to_str().map(String::from),
98 None => None,
99 }
100 }
101
102 pub fn localize_file(parent: &str, child: &str) -> Option<String> {
104 let mut prefixed_parent;
105 if !parent.starts_with('.') && !Path::new(parent).is_absolute() {
107 prefixed_parent = "./".to_owned();
108 prefixed_parent.push_str(parent);
109 } else {
110 prefixed_parent = parent.to_owned();
111 }
112 let mut prefix = match FileSource::derive_dir(prefixed_parent.as_str()) {
113 Some(p) => {
114 if p.is_empty() {
115 String::from(".")
116 } else {
117 p
118 }
119 }
120 None => String::from("."),
121 };
122 if child.starts_with("../") {
123 let mut res_str = child.to_string();
124 while res_str.starts_with("../") {
125 let path = PathBuf::from(prefix.clone());
126 match path.parent() {
127 Some(p) => match p.to_str().map(String::from) {
128 Some(pref) => {
129 if pref.is_empty() || prefix.ends_with("..") {
130 if prefix.is_empty() || prefix == "." {
131 prefix = "..".to_string();
132 } else {
133 prefix = format!("../{prefix}");
134 }
135 } else {
136 prefix = pref
137 }
138 }
139 None => {
140 tracing::warn!("Failed to convert path to string");
141 return None;
142 }
143 },
144 None => {
145 tracing::warn!("Failed to find parent for path: {:?}", path);
146 return None;
147 }
148 }
149 res_str = res_str.replacen("../", "", 1);
150 }
151 Some(format!("{}/{res_str}", prefix.trim_end_matches('/')))
152 } else if child.starts_with("./") {
153 Some(child.replacen("./", &format!("{}/", prefix.trim_end_matches('/')), 1))
154 } else if child.starts_with('/') {
155 Some(child.to_string())
156 } else {
157 Some(format!("{}/{child}", prefix.trim_end_matches('/')))
158 }
159 }
160}