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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
use std::path::{Path, PathBuf};
use string_proc::format_path::format_path;
use tokio::fs;
use crate::constants::CLIENT_FOLDER_WORKSPACE_ROOT_NAME;
pub struct RelativeFiles {
pub(crate) files: Vec<PathBuf>,
}
impl IntoIterator for RelativeFiles {
type Item = PathBuf;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.files.into_iter()
}
}
impl RelativeFiles {
pub fn iter(&self) -> std::slice::Iter<'_, PathBuf> {
self.files.iter()
}
}
/// Read the relative paths within the project from the input file list
pub async fn get_relative_paths(local_path: &PathBuf, paths: &[PathBuf]) -> Option<RelativeFiles> {
// Get Relative Paths
let Ok(paths) = format_input_paths_and_ignore_outside_paths(local_path, paths).await else {
return None;
};
let files: Vec<PathBuf> = abs_paths_to_abs_files(paths).await;
let Ok(files) = parse_to_relative(local_path, files) else {
return None;
};
Some(RelativeFiles { files })
}
/// Normalize the input paths
async fn format_input_paths(
local_path: &Path,
track_files: &[PathBuf],
) -> Result<Vec<PathBuf>, std::io::Error> {
let current_dir = local_path;
let mut real_paths = Vec::new();
for file in track_files {
let path = current_dir.join(file);
// Skip paths that contain .jv directories
if path.components().any(|component| {
if let std::path::Component::Normal(name) = component {
name.to_str() == Some(CLIENT_FOLDER_WORKSPACE_ROOT_NAME)
} else {
false
}
}) {
continue;
}
match format_path(path) {
Ok(path) => real_paths.push(path),
Err(e) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Failed to format path: {}", e),
));
}
}
}
Ok(real_paths)
}
/// Ignore files outside the workspace
async fn format_input_paths_and_ignore_outside_paths(
local_path: &PathBuf,
files: &[PathBuf],
) -> Result<Vec<PathBuf>, std::io::Error> {
let result = format_input_paths(local_path, files).await?;
let result: Vec<PathBuf> = result
.into_iter()
.filter(|path| path.starts_with(local_path))
.collect();
Ok(result)
}
/// Normalize the input paths to relative paths
fn parse_to_relative(
local_dir: &PathBuf,
files: Vec<PathBuf>,
) -> Result<Vec<PathBuf>, std::io::Error> {
let result: Result<Vec<PathBuf>, _> = files
.iter()
.map(|p| {
p.strip_prefix(local_dir)
.map(|relative| relative.to_path_buf())
.map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Path prefix stripping failed",
)
})
})
.collect();
result
}
/// Convert absolute paths to absolute file paths, expanding directories to their contained files
async fn abs_paths_to_abs_files(paths: Vec<PathBuf>) -> Vec<PathBuf> {
let mut files = Vec::new();
for path in paths {
if !path.exists() {
continue;
}
let metadata = match fs::metadata(&path).await {
Ok(meta) => meta,
Err(_) => continue,
};
if metadata.is_file() {
files.push(path);
} else if metadata.is_dir() {
let walker = walkdir::WalkDir::new(&path);
for entry in walker.into_iter().filter_map(|e| e.ok()) {
if entry.path().components().any(|component| {
if let std::path::Component::Normal(name) = component {
name == CLIENT_FOLDER_WORKSPACE_ROOT_NAME
} else {
false
}
}) {
continue;
}
if entry.file_type().is_file() {
files.push(entry.path().to_path_buf());
}
}
}
}
files
}
|