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 is_blank(byte: &u8) -> bool {
return BLANK.contains(byte);
}
fn is_not_blank(byte: &u8) -> bool {
return !BLANK.contains(byte);
}
fn is_nonempty_piece(piece: &&[u8]) -> bool {
return !piece.is_empty();
}
fn is_newline(byte: &u8) -> bool {
return *byte == b'\n';
}
fn is_nul(byte: &u8) -> bool {
return *byte == 0;
}
fn is_slash(byte: &u8) -> bool {
return *byte == b'/';
}
fn is_equals_sign(byte: &u8) -> bool {
return *byte == b'=';
}
fn is_not_negation_mark(byte: &u8) -> bool {
return *byte != b'-' && *byte != b'!';
}
fn is_backslash_or_bracket(byte: &u8) -> bool {
return *byte == b'\\' || *byte == b'[';
}
fn is_double_star(pair: &[u8]) -> bool {
return pair == b"**";
}
fn blank_separated(line: &[u8]) -> Vec<Vec<u8>> {
return line.split(is_blank).filter(is_nonempty_piece).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(is_newline).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(is_nul).next().unwrap_or(&[]);
let start: usize = text.iter().position(is_not_blank).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(is_not_negation_mark).unwrap_or(token.len());
let rest: &[u8] = &token[start..];
return rest.split(is_equals_sign).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.contains(&true) {
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(is_backslash_or_bracket) || pattern.windows(2).any(is_double_star) || pattern.ends_with(b"/") {
return true;
}
let relative: &[u8] = if base.is_empty() {
path
} else {
let below_base: Option<&[u8]> = match path.strip_prefix(base) {
Some(rest) => rest.strip_prefix(b"/"),
None => None,
};
match below_base {
Some(rest) => rest,
None => return false,
}
};
let compared: &[u8] = if pattern.contains(&b'/') { relative } else { relative.rsplit(is_slash).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;