engram-core 0.21.1

AI Memory Infrastructure - Persistent memory for AI agents with semantic search
Documentation
pub(crate) fn strip_ansi(input: &str) -> String {
    let mut out = String::with_capacity(input.len());
    let mut chars = input.chars().peekable();

    while let Some(ch) = chars.next() {
        if ch == '\u{1b}' && chars.peek() == Some(&'[') {
            chars.next();
            for next in chars.by_ref() {
                if next.is_ascii_alphabetic() {
                    break;
                }
            }
            continue;
        }

        out.push(ch);
    }

    out
}

pub(crate) fn truncate(input: &str, max_chars: usize) -> (String, bool) {
    let char_count = input.chars().count();
    if char_count <= max_chars {
        return (input.to_string(), false);
    }

    if max_chars == 0 {
        return (String::new(), true);
    }

    const MARKER: &str = "...[truncated]";
    if max_chars <= MARKER.len() {
        return (input.chars().take(max_chars).collect(), true);
    }

    let keep = max_chars - MARKER.len();
    let mut truncated: String = input.chars().take(keep).collect();
    truncated.push_str(MARKER);
    (truncated, true)
}

pub(crate) fn parse_path_line_col(candidate: &str) -> Option<(String, u64, Option<u64>)> {
    let cleaned = candidate
        .trim()
        .trim_matches('`')
        .trim_matches('\'')
        .trim_matches('"')
        .trim_end_matches(':')
        .trim_end_matches(',');
    let parts: Vec<&str> = cleaned.split(':').collect();
    if parts.len() < 2 {
        return None;
    }

    let last = parts.last()?;
    if let Ok(column) = last.parse::<u64>() {
        if parts.len() >= 3 {
            let line_part = parts.get(parts.len() - 2)?;
            if let Ok(line) = line_part.parse::<u64>() {
                let path = parts[..parts.len() - 2].join(":");
                if !path.is_empty() {
                    return Some((path, line, Some(column)));
                }
            }
        }
    }

    if let Ok(line) = last.parse::<u64>() {
        let path = parts[..parts.len() - 1].join(":");
        if !path.is_empty() {
            return Some((path, line, None));
        }
    }

    None
}

pub(crate) fn extract_clippy_rule(input: &str) -> Option<String> {
    let start = input.find("clippy::")?;
    let rule: String = input[start..]
        .chars()
        .take_while(|ch| ch.is_ascii_alphanumeric() || *ch == '_' || *ch == ':')
        .collect();

    if rule.len() > "clippy::".len() {
        Some(rule)
    } else {
        None
    }
}

pub(crate) fn is_numeric(input: &str) -> bool {
    !input.is_empty() && input.chars().all(|ch| ch.is_ascii_digit())
}