use std::{borrow::Cow, collections::BTreeSet};
mod parse_impl;
pub fn merge<'a, 'b>(mut a: Depfile<'a>, b: Depfile<'b>) -> Depfile<'a>
where
'b: 'a,
{
a.rules.extend(b.rules);
a
}
pub fn parse(input: &str) -> Result<Depfile<'_>, usize> {
input.try_into()
}
#[derive(Clone, PartialEq, Eq, Default)]
pub struct Depfile<'a> {
pub(crate) rules: Vec<(Cow<'a, str>, Vec<Cow<'a, str>>)>,
}
impl std::fmt::Debug for Depfile<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = f.debug_map();
for (target, deps) in &self.rules {
let entry = map.key(&format!("'{target}'"));
let value = deps.iter().map(|x| format!("'{x}'")).collect::<Vec<_>>();
entry.value(&value);
}
map.finish()
}
}
impl Depfile<'_> {
pub fn len(&self) -> usize {
self.rules.len()
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn sort(&mut self) {
self.rules.sort_unstable_by(|a, b| a.0.cmp(&b.0))
}
pub fn binary_search(&self, target: &str) -> Option<&[Cow<'_, str>]> {
let i = self
.rules
.binary_search_by(|(t, _)| t.as_ref().cmp(target))
.ok()?;
Some(self.rules[i].1.as_slice())
}
pub fn find(&self, target: &str) -> Option<&[Cow<'_, str>]> {
let (_, deps) = self.rules.iter().find(|(t, _)| t == target)?;
Some(deps.as_slice())
}
pub fn iter(&self) -> DepfileIter<'_> {
DepfileIter(self.rules.iter())
}
pub fn recurse_deps(&self, target: &str) -> DepfileRecurDeps<'_> {
match self.find(target) {
None => DepfileRecurDeps {
seen: Default::default(),
rules: self,
stack: Default::default(),
next_target: None,
},
Some(deps) => DepfileRecurDeps {
seen: Default::default(),
rules: self,
stack: vec![deps.iter()],
next_target: None,
},
}
}
}
pub struct DepfileIter<'a>(std::slice::Iter<'a, (Cow<'a, str>, Vec<Cow<'a, str>>)>);
impl<'a> Iterator for DepfileIter<'a> {
type Item = (&'a str, &'a [Cow<'a, str>]);
fn next(&mut self) -> Option<Self::Item> {
let (target, deps) = self.0.next()?;
Some((target.as_ref(), deps.as_slice()))
}
}
pub struct DepfileRecurDeps<'a> {
seen: BTreeSet<&'a str>,
rules: &'a Depfile<'a>,
stack: Vec<std::slice::Iter<'a, Cow<'a, str>>>,
next_target: Option<&'a str>,
}
impl<'a> Iterator for DepfileRecurDeps<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if let Some(target) = self.next_target.take() {
if let Some(deps) = self.rules.find(target) {
if !deps.is_empty() {
self.stack.push(deps.iter());
}
}
}
while let Some(iter) = self.stack.last_mut() {
for target in iter.by_ref() {
let target_str = target.as_ref();
if self.seen.insert(target_str) {
self.next_target = Some(target_str);
return self.next_target;
}
}
self.stack.pop();
}
None
}
}
pub fn escape(input: &str) -> Cow<'_, str> {
let mut s = String::new();
macro_rules! handle_escape {
($i:ident, $seq:literal) => {{
if s.is_empty() {
s.push_str(&input[..$i]);
}
s.push_str($seq);
}};
}
for (i, c) in input.char_indices() {
match c {
' ' => handle_escape!(i, "\\ "),
'\\' => handle_escape!(i, "\\\\"),
':' => handle_escape!(i, "\\:"),
_ => {
if !s.is_empty() {
s.push(c)
}
}
}
}
if s.is_empty() {
return input.into();
}
s.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_escape() {
assert_eq!(escape(""), Cow::Borrowed(""));
assert_eq!(escape("abc"), Cow::Borrowed("abc"));
assert_eq!(escape(" "), Cow::<'static, str>::Owned("\\ ".to_string()));
assert_eq!(escape(":"), Cow::<'static, str>::Owned("\\:".to_string()));
assert_eq!(escape("/"), Cow::Borrowed("/"));
assert_eq!(escape("\\"), Cow::<'static, str>::Owned("\\\\".to_string()));
assert_eq!(
escape("a bc"),
Cow::<'static, str>::Owned("a\\ bc".to_string())
);
assert_eq!(
escape("a:bc"),
Cow::<'static, str>::Owned("a\\:bc".to_string())
);
assert_eq!(escape("a/bc"), Cow::Borrowed("a/bc"));
assert_eq!(
escape("a\\bc"),
Cow::<'static, str>::Owned("a\\\\bc".to_string())
);
assert_eq!(escape("x "), Cow::<'static, str>::Owned("x\\ ".to_string()));
assert_eq!(escape("x:"), Cow::<'static, str>::Owned("x\\:".to_string()));
assert_eq!(escape("x/"), Cow::Borrowed("x/"));
assert_eq!(
escape("x\\"),
Cow::<'static, str>::Owned("x\\\\".to_string())
);
assert_eq!(
escape("x "),
Cow::<'static, str>::Owned("x\\ \\ ".to_string())
);
assert_eq!(
escape("x :"),
Cow::<'static, str>::Owned("x\\ \\:".to_string())
);
assert_eq!(
escape("x /"),
Cow::<'static, str>::Owned("x\\ /".to_string())
);
assert_eq!(
escape("x \\"),
Cow::<'static, str>::Owned("x\\ \\\\".to_string())
);
assert_eq!(
escape("x::"),
Cow::<'static, str>::Owned("x\\:\\:".to_string())
);
assert_eq!(
escape("x:/"),
Cow::<'static, str>::Owned("x\\:/".to_string())
);
assert_eq!(
escape("x:\\"),
Cow::<'static, str>::Owned("x\\:\\\\".to_string())
);
assert_eq!(escape("x//"), Cow::Borrowed("x//"));
assert_eq!(
escape("x/\\"),
Cow::<'static, str>::Owned("x/\\\\".to_string())
);
}
#[test]
fn test_sort_search() {
let mut df = crate::parse("x:1\nb:2\na:3\nc:4\nz:5\n").unwrap();
df.sort();
assert_eq!(df.binary_search("x").unwrap(), [Cow::from("1")].as_ref());
assert_eq!(df.binary_search("b").unwrap(), [Cow::from("2")].as_ref());
assert_eq!(df.binary_search("a").unwrap(), [Cow::from("3")].as_ref());
assert_eq!(df.binary_search("c").unwrap(), [Cow::from("4")].as_ref());
assert_eq!(df.binary_search("z").unwrap(), [Cow::from("5")].as_ref());
}
#[test]
fn test_recursive() {
let df = crate::parse("x:a b\nb:c\na:c\nc:d\nd:a\n").unwrap();
let deps = df.recurse_deps("x").collect::<Vec<_>>();
assert_eq!(deps, vec!["a", "c", "d", "b"])
}
}