use std::collections::HashSet;
use std::path::Path;
use super::super::scanner::ScanResult;
use super::canonicalize::canonicalize_path;
use super::error::DepfileError;
pub fn parse_depfile(content: &str, source: &Path, cwd: &Path) -> Result<ScanResult, DepfileError> {
if content.trim().is_empty() {
return Err(DepfileError::Malformed("empty depfile content".to_string()));
}
let joined = join_continuations(content);
let colon_pos = find_separator_colon(&joined)?;
let deps_str = &joined[colon_pos + 1..];
let tokens = split_and_unescape(deps_str);
let source_canonical = canonicalize_path(source, cwd);
let mut seen = HashSet::with_capacity(tokens.len());
let mut resolved = Vec::with_capacity(tokens.len());
for token in tokens {
if token.is_empty() {
continue;
}
let dep_path = Path::new(&token);
let abs_path = if dep_path.is_absolute() {
canonicalize_path(dep_path, cwd)
} else {
canonicalize_path(&cwd.join(dep_path), cwd)
};
if abs_path == source_canonical {
continue;
}
if seen.insert(abs_path.clone()) {
resolved.push(abs_path);
}
}
Ok(ScanResult {
resolved,
unresolved: Vec::new(),
has_computed: false,
})
}
pub fn parse_depfile_path(
path: &Path,
source: &Path,
cwd: &Path,
) -> Result<ScanResult, DepfileError> {
let content = std::fs::read_to_string(path)?;
parse_depfile(&content, source, cwd)
}
pub(super) fn join_continuations(content: &str) -> String {
let mut result = String::with_capacity(content.len());
let mut chars = content.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
match chars.peek() {
Some('\n') => {
chars.next();
result.push(' ');
}
Some('\r') => {
chars.next();
if chars.peek() == Some(&'\n') {
chars.next();
}
result.push(' ');
}
_ => {
result.push(ch);
}
}
} else {
result.push(ch);
}
}
result
}
pub(super) fn find_separator_colon(line: &str) -> Result<usize, DepfileError> {
let bytes = line.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == b':' {
let is_drive_letter = i > 0
&& (i == 1 || !bytes[i - 2].is_ascii_alphanumeric())
&& bytes[i - 1].is_ascii_alphabetic()
&& i + 1 < len
&& (bytes[i + 1] == b'\\' || bytes[i + 1] == b'/');
if !is_drive_letter {
return Ok(i);
}
}
i += 1;
}
Err(DepfileError::Malformed(
"no colon separator found".to_string(),
))
}
pub(super) fn split_and_unescape(deps: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
let mut chars = deps.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
match chars.peek() {
Some(' ') => {
chars.next();
current.push(' ');
}
Some('#') => {
chars.next();
current.push('#');
}
_ => {
current.push(ch);
}
}
} else if ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' {
if !current.is_empty() {
tokens.push(std::mem::take(&mut current));
}
} else {
current.push(ch);
}
}
if !current.is_empty() {
tokens.push(current);
}
tokens
}