clipmem 0.5.6

macOS clipboard memory backed by SQLite and searchable from agent runtimes
Documentation
pub(crate) fn normalize_file_path(value: &str) -> String {
    decode_file_url_path(value).unwrap_or_else(|| value.to_string())
}

pub(crate) fn projected_file_url_path(value: &str) -> Option<String> {
    if has_ascii_prefix(value, FILE_URL_PREFIX) {
        decode_file_url_path(value)
    } else {
        Some(value.to_string())
    }
}

pub(crate) fn decode_file_url_path(value: &str) -> Option<String> {
    if !has_ascii_prefix(value, FILE_URL_PREFIX) {
        return None;
    }

    let rest = strip_localhost_authority(&value[FILE_URL_PREFIX.len()..]);

    if rest.is_empty() {
        None
    } else if rest.as_bytes().contains(&b'%') {
        Some(percent_decode(rest))
    } else {
        Some(rest.to_string())
    }
}

pub(crate) fn file_url_cache_path_fragment(value: &str) -> String {
    value
        .to_ascii_lowercase()
        .bytes()
        .map(|byte| match byte {
            b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' | b'/' | b':' => {
                (byte as char).to_string()
            }
            _ => format!("%{byte:02x}"),
        })
        .collect()
}

const FILE_URL_PREFIX: &str = "file://";

fn strip_localhost_authority(value: &str) -> &str {
    const LOCALHOST: &str = "localhost";
    if has_ascii_prefix(value, LOCALHOST)
        && (value.len() == LOCALHOST.len() || value.as_bytes()[LOCALHOST.len()] == b'/')
    {
        &value[LOCALHOST.len()..]
    } else {
        value
    }
}

fn has_ascii_prefix(value: &str, prefix: &str) -> bool {
    value
        .as_bytes()
        .get(..prefix.len())
        .is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix.as_bytes()))
}

fn percent_decode(value: &str) -> String {
    let bytes = value.as_bytes();
    if !bytes.contains(&b'%') {
        return value.to_string();
    }

    let mut out = Vec::with_capacity(bytes.len());
    let mut index = 0;

    while index < bytes.len() {
        if bytes[index] == b'%' && index + 2 < bytes.len() {
            if let (Some(first), Some(second)) =
                (hex_value(bytes[index + 1]), hex_value(bytes[index + 2]))
            {
                out.push((first << 4) | second);
                index += 3;
                continue;
            }
        }

        out.push(bytes[index]);
        index += 1;
    }

    String::from_utf8_lossy(&out).into_owned()
}

fn hex_value(byte: u8) -> Option<u8> {
    match byte {
        b'0'..=b'9' => Some(byte - b'0'),
        b'a'..=b'f' => Some(byte - b'a' + 10),
        b'A'..=b'F' => Some(byte - b'A' + 10),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::{normalize_file_path, projected_file_url_path};

    #[test]
    fn file_url_paths_decode_percent_encoding_and_localhost_authority() {
        assert_eq!(
            normalize_file_path("file:///Users/test/Documents/hello%20world.txt"),
            "/Users/test/Documents/hello world.txt"
        );
        assert_eq!(
            normalize_file_path("file://LOCALHOST/Users/test/Documents/hello%20world.txt"),
            "/Users/test/Documents/hello world.txt"
        );
    }

    #[test]
    fn file_url_projection_keeps_non_url_values_but_drops_empty_file_urls() {
        assert_eq!(
            projected_file_url_path("/Users/test/Documents/hello world.txt").as_deref(),
            Some("/Users/test/Documents/hello world.txt")
        );
        assert_eq!(projected_file_url_path("file://"), None);
        assert_eq!(projected_file_url_path("file://LOCALHOST"), None);
    }
}