pub const CONVERTING_ATTRIBUTES: &[&[u8]] = &[b"filter", b"ident", b"working-tree-encoding"];
const BLANK: &[u8] = b" \t\r\n";
const MACRO_PREFIX: &[u8] = b"[attr]";
pub const MATCH_WORK_LIMIT: usize = 1 << 22;
const BYTE_ORDER_MARK: &[u8] = &[0xEF, 0xBB, 0xBF];
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AttributeLine {
pub subject: Vec<u8>,
pub is_macro: bool,
pub quoted: bool,
pub tokens: Vec<Vec<u8>>,
}
fn blank_separated(line: &[u8]) -> Vec<Vec<u8>> {
return line.split(|byte: &u8| { return BLANK.contains(byte); }).filter(|piece: &&[u8]| { return !piece.is_empty(); }).map(<[u8]>::to_vec).collect();
}
pub fn parse_attribute_lines(bytes: &[u8]) -> Vec<AttributeLine> {
let mut lines: Vec<AttributeLine> = Vec::new();
for (number, raw) in bytes.split(|byte: &u8| { return *byte == b'\n'; }).enumerate() {
let without_mark: &[u8] = if number == 0 { raw.strip_prefix(BYTE_ORDER_MARK).unwrap_or(raw) } else { raw };
let text: &[u8] = without_mark.split(|byte: &u8| { return *byte == 0; }).next().unwrap_or(&[]);
let start: usize = text.iter().position(|byte: &u8| { return !BLANK.contains(byte); }).unwrap_or(text.len());
let line: &[u8] = &text[start..];
if line.is_empty() || line[0] == b'#' {
continue;
}
if line[0] == b'"' {
lines.push(AttributeLine { subject: line.to_vec(), is_macro: false, quoted: true, tokens: Vec::new() });
continue;
}
let mut pieces: Vec<Vec<u8>> = blank_separated(line);
let name: Vec<u8> = pieces.remove(0);
if name.len() > MACRO_PREFIX.len() && name.starts_with(MACRO_PREFIX) {
lines.push(AttributeLine { subject: name[MACRO_PREFIX.len()..].to_vec(), is_macro: true, quoted: false, tokens: pieces });
} else {
lines.push(AttributeLine { subject: name, is_macro: false, quoted: false, tokens: pieces });
}
}
return lines;
}
pub fn attribute_name(token: &[u8]) -> &[u8] {
let start: usize = token.iter().position(|byte: &u8| { return *byte != b'-' && *byte != b'!'; }).unwrap_or(token.len());
let rest: &[u8] = &token[start..];
return rest.split(|byte: &u8| { return *byte == b'='; }).next().unwrap_or(&[]);
}
pub fn wildcard(pattern: &[u8], text: &[u8]) -> bool {
let mut reach: Vec<bool> = vec![false; text.len() + 1];
let mut next: Vec<bool> = vec![false; text.len() + 1];
reach[0] = true;
for &symbol in pattern {
next.fill(false);
if symbol == b'*' {
next[0] = reach[0];
for at in 1..=text.len() {
next[at] = reach[at] || (next[at - 1] && text[at - 1] != b'/');
}
} else {
for at in 1..=text.len() {
let same: bool = if symbol == b'?' { text[at - 1] != b'/' } else { symbol.eq_ignore_ascii_case(&text[at - 1]) };
next[at] = reach[at - 1] && same;
}
}
std::mem::swap(&mut reach, &mut next);
if !reach.iter().any(|alive: &bool| { return *alive; }) {
return false;
}
}
return reach[text.len()];
}
pub fn may_apply(pattern: &[u8], base: &[u8], path: &[u8]) -> bool {
if pattern.is_empty() {
return false;
}
if pattern[0] == b'"' || pattern.iter().any(|byte: &u8| { return *byte == b'\\' || *byte == b'['; }) || pattern.windows(2).any(|pair: &[u8]| { return pair == b"**"; }) || pattern.ends_with(b"/") {
return true;
}
let relative: &[u8] = if base.is_empty() {
path
} else {
match path.strip_prefix(base).and_then(|rest: &[u8]| { return rest.strip_prefix(b"/"); }) {
Some(rest) => rest,
None => return false,
}
};
let compared: &[u8] = if pattern.contains(&b'/') { relative } else { relative.rsplit(|byte: &u8| { return *byte == b'/'; }).next().unwrap_or(&[]) };
if pattern.len().saturating_mul(compared.len() + 1) > MATCH_WORK_LIMIT {
return true;
}
if pattern.contains(&b'/') {
return wildcard(pattern.strip_prefix(b"/").unwrap_or(pattern), compared);
}
return wildcard(pattern, compared);
}
#[cfg(test)]
#[path = "worktree_attributes_tests.rs"]
mod tests;