hayson-engine 0.17.0

Storage engine for hayson document databases — the embeddable core behind hayson-server, hayson-cli and hayson-browser
Documentation
/// True for a target markdown recognizes as one of its own virtual paths — an absolute path
/// (so a same-document `#anchor` or a relative `../x` never counts) ending in a known extension.
fn is_virtual_path(target: &str) -> bool {
    if !target.starts_with('/') {
        return false;
    }
    let target = target.split(['#', '?']).next().unwrap_or(target);
    [".md", ".markdown", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"]
        .iter()
        .any(|ext| target.ends_with(ext))
}

/// Scans `content` for `[text](target)` and `![alt](target)` markdown links/embeds and returns
/// the `target`s that look like a virtual path of this engine (see `is_virtual_path`). Plain
/// prose that happens to contain parentheses is not parsed as markdown — this is a link scanner,
/// not a markdown parser.
pub fn extract_links(content: &str) -> Vec<String> {
    let bytes = content.as_bytes();
    let mut links = Vec::new();
    let mut at = 0usize;
    while let Some(bracket_offset) = content[at..].find('[') {
        let bracket = at + bracket_offset;
        let Some(close_bracket_offset) = content[bracket..].find(']') else {
            break;
        };
        let close_bracket = bracket + close_bracket_offset;
        let paren = close_bracket + 1;
        if bytes.get(paren) != Some(&b'(') {
            at = close_bracket + 1;
            continue;
        }
        let Some(close_paren_offset) = content[paren..].find(')') else {
            break;
        };
        let close_paren = paren + close_paren_offset;
        let target = &content[paren + 1..close_paren];
        if is_virtual_path(target) {
            let stripped = target.split(['#', '?']).next().unwrap_or(target);
            links.push(stripped.to_string());
        }
        at = close_paren + 1;
    }
    links
}

/// Every link `extract_links` found in `content` whose target `exists` reports as absent.
pub fn broken_links(content: &str, exists: impl Fn(&str) -> bool) -> Vec<String> {
    extract_links(content)
        .into_iter()
        .filter(|target| !exists(target))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn extracts_a_link_and_an_embed_pointing_at_a_virtual_path() {
        let content = "See [the guide](/docs/quick-start.md) and ![logo](/images/logo.png).";
        assert_eq!(
            extract_links(content),
            vec!["/docs/quick-start.md".to_string(), "/images/logo.png".to_string()]
        );
    }

    #[test]
    fn ignores_a_link_that_is_not_an_absolute_known_extension() {
        let content = "[external](https://example.com) and [anchor](#section) and [rel](../x.md)";
        assert!(extract_links(content).is_empty());
    }

    #[test]
    fn strips_an_anchor_or_query_before_matching_the_extension() {
        let content = "[section](/docs/guide.md#install)";
        assert_eq!(extract_links(content), vec!["/docs/guide.md".to_string()]);
    }

    #[test]
    fn reports_only_the_links_that_do_not_exist() {
        let content = "[a](/docs/a.md) and [b](/docs/b.md)";
        let broken = broken_links(content, |target| target == "/docs/a.md");
        assert_eq!(broken, vec!["/docs/b.md".to_string()]);
    }
}