relay-knowledge 1.1.6

Graph-database-based knowledge graph project.
Documentation
use super::{
    model::ConfigFact,
    source::{push_definition, source_lines},
};
use crate::project::KNOWLEDGE_MAP_RELATIVE_PATH;

pub(super) fn facts(
    path: &str,
    language_id: &str,
    content: &str,
    definitions: &mut Vec<ConfigFact>,
) {
    if path != KNOWLEDGE_MAP_RELATIVE_PATH || language_id != "yaml" {
        return;
    }

    let mut in_topics = false;
    let mut topic_list_indent = None;
    let mut topic_item_indent = None;
    for line in source_lines(content) {
        let code = yaml_code_prefix(line.text);
        let trimmed = code.trim();
        if let Some(section) = top_level_yaml_section(code) {
            in_topics = section == "topics";
            topic_list_indent = None;
            topic_item_indent = None;
            continue;
        }
        if !in_topics || trimmed.is_empty() {
            continue;
        }

        let indent = leading_spaces(code);
        if let Some(item) = trimmed.strip_prefix("- ") {
            if !accept_topic_item_indent(&mut topic_list_indent, indent) {
                continue;
            }
            topic_item_indent = Some(indent);
            let item = item.trim_start();
            if let Some(id) = item.strip_prefix("id:") {
                push_topic_definition(definitions, id, line.range());
            }
            continue;
        }
        if trimmed == "-" {
            if !accept_topic_item_indent(&mut topic_list_indent, indent) {
                continue;
            }
            topic_item_indent = Some(indent);
            continue;
        }
        if topic_item_indent.is_some_and(|item_indent| indent == item_indent + 2) {
            let Some(id) = trimmed.strip_prefix("id:") else {
                continue;
            };
            push_topic_definition(definitions, id, line.range());
        }
    }
}

fn push_topic_definition(
    definitions: &mut Vec<ConfigFact>,
    value: &str,
    range: super::model::ConfigRange,
) {
    let name = value.trim().trim_matches('"').trim_matches('\'');
    push_definition(definitions, name, "knowledge_map_topic", range);
}

fn accept_topic_item_indent(topic_list_indent: &mut Option<usize>, indent: usize) -> bool {
    match *topic_list_indent {
        Some(list_indent) => indent == list_indent,
        None => {
            *topic_list_indent = Some(indent);
            true
        }
    }
}

fn leading_spaces(line: &str) -> usize {
    line.chars()
        .take_while(|character| *character == ' ')
        .count()
}

fn yaml_code_prefix(line: &str) -> &str {
    let mut in_single = false;
    let mut in_double = false;
    let mut escaped = false;
    for (index, character) in line.char_indices() {
        match character {
            '\\' if in_double && !escaped => escaped = true,
            '"' if !in_single && !escaped => in_double = !in_double,
            '\'' if !in_double => in_single = !in_single,
            '#' if !in_single && !in_double => return &line[..index],
            _ => escaped = false,
        }
        if character != '\\' {
            escaped = false;
        }
    }

    line
}

fn top_level_yaml_section(line: &str) -> Option<&str> {
    if line.starts_with(' ') || line.starts_with('\t') {
        return None;
    }
    let key = line.trim().strip_suffix(':')?;
    if key.is_empty() || key.contains(' ') {
        return None;
    }

    Some(key)
}