relay-knowledge 1.1.16

Graph-database-based knowledge graph project.
Documentation
use crate::domain::{
    CODEGRAPH_CONTEXT_DEFAULT_LIMIT, CODEGRAPH_CONTEXT_DEFAULT_MAX_BYTES, CodeQueryKind,
    FreshnessPolicy,
};

use super::{CliError, RepoCommand, value_after};

pub(super) fn parse_query(tokens: &[String]) -> Result<RepoCommand, CliError> {
    let alias = positional_alias(tokens)?;
    let mut query = None;
    let mut kind = CodeQueryKind::Hybrid;
    let mut limit = 10;
    let mut ref_selector = "HEAD".to_owned();
    let mut path_filters = Vec::new();
    let mut language_filters = Vec::new();
    let mut freshness = FreshnessPolicy::AllowStale;
    let mut exclude_generated = false;
    let mut index = 1;
    while index < tokens.len() {
        match tokens[index].as_str() {
            "--query" => {
                let (value, next_index) = collect_query_value(tokens, index, "--query")?;
                query = Some(value);
                index = next_index;
            }
            "--kind" => {
                kind = parse_query_kind(&value_after(tokens, index, "--kind")?)?;
                index += 2;
            }
            "--limit" => {
                limit = parse_limit(tokens, index, "--limit")?;
                index += 2;
            }
            "--ref" => {
                ref_selector = value_after(tokens, index, "--ref")?;
                index += 2;
            }
            "--path" => {
                path_filters.push(value_after(tokens, index, "--path")?);
                index += 2;
            }
            "--language" => {
                language_filters.push(value_after(tokens, index, "--language")?);
                index += 2;
            }
            "--freshness" => {
                freshness = super::parse_freshness(&value_after(tokens, index, "--freshness")?)?;
                index += 2;
            }
            "--exclude-generated" => {
                exclude_generated = true;
                index += 1;
            }
            other if !other.starts_with('-') && query.is_none() => {
                let (value, next_index) = collect_positional_query(tokens, index);
                query = Some(value);
                index = next_index;
            }
            other => return Err(CliError::UnexpectedArgument(other.to_owned())),
        }
    }

    Ok(RepoCommand::Query {
        alias,
        query: query.ok_or(CliError::MissingValue("--query"))?,
        kind,
        limit,
        ref_selector,
        path_filters,
        language_filters,
        freshness,
        exclude_generated,
    })
}

pub(super) fn parse_context(tokens: &[String]) -> Result<RepoCommand, CliError> {
    let alias = positional_alias(tokens)?;
    let mut query = None;
    let mut limit = CODEGRAPH_CONTEXT_DEFAULT_LIMIT;
    let mut ref_selector = "HEAD".to_owned();
    let mut path_filters = Vec::new();
    let mut language_filters = Vec::new();
    let mut freshness = FreshnessPolicy::AllowStale;
    let mut max_context_bytes = CODEGRAPH_CONTEXT_DEFAULT_MAX_BYTES;
    let mut include_code = true;
    let mut exclude_generated = false;
    let mut index = 1;
    while index < tokens.len() {
        match tokens[index].as_str() {
            "--query" => {
                let (value, next_index) = collect_query_value(tokens, index, "--query")?;
                query = Some(value);
                index = next_index;
            }
            "--limit" => {
                limit = parse_limit(tokens, index, "--limit")?;
                index += 2;
            }
            "--ref" => {
                ref_selector = value_after(tokens, index, "--ref")?;
                index += 2;
            }
            "--path" => {
                path_filters.push(value_after(tokens, index, "--path")?);
                index += 2;
            }
            "--language" => {
                language_filters.push(value_after(tokens, index, "--language")?);
                index += 2;
            }
            "--freshness" => {
                freshness = super::parse_freshness(&value_after(tokens, index, "--freshness")?)?;
                index += 2;
            }
            "--max-context-bytes" => {
                max_context_bytes = parse_limit(tokens, index, "--max-context-bytes")?;
                index += 2;
            }
            "--no-code" => {
                include_code = false;
                index += 1;
            }
            "--exclude-generated" => {
                exclude_generated = true;
                index += 1;
            }
            other if !other.starts_with('-') && query.is_none() => {
                let (value, next_index) = collect_positional_query(tokens, index);
                query = Some(value);
                index = next_index;
            }
            other => return Err(CliError::UnexpectedArgument(other.to_owned())),
        }
    }

    Ok(RepoCommand::Context {
        alias,
        query: query.ok_or(CliError::MissingValue("--query"))?,
        limit,
        ref_selector,
        path_filters,
        language_filters,
        freshness,
        max_context_bytes,
        include_code,
        exclude_generated,
    })
}

fn collect_query_value(
    tokens: &[String],
    index: usize,
    flag: &'static str,
) -> Result<(String, usize), CliError> {
    let first = value_after(tokens, index, flag)?;
    let mut values = vec![first];
    let mut cursor = index + 2;
    while cursor < tokens.len() && !tokens[cursor].starts_with("--") {
        values.push(tokens[cursor].clone());
        cursor += 1;
    }

    Ok((values.join(" "), cursor))
}

fn collect_positional_query(tokens: &[String], index: usize) -> (String, usize) {
    let mut values = Vec::new();
    let mut cursor = index;
    while cursor < tokens.len() && !tokens[cursor].starts_with("--") {
        values.push(tokens[cursor].clone());
        cursor += 1;
    }

    (values.join(" "), cursor)
}

fn parse_limit(tokens: &[String], index: usize, flag: &'static str) -> Result<usize, CliError> {
    let value = value_after(tokens, index, flag)?;
    value
        .parse::<usize>()
        .map_err(|_| CliError::InvalidLimit(value.clone()))
}

fn positional_alias(tokens: &[String]) -> Result<String, CliError> {
    tokens
        .first()
        .filter(|value| !value.starts_with('-'))
        .cloned()
        .ok_or(CliError::MissingValue("<alias>"))
}

pub(super) fn parse_query_kind(value: &str) -> Result<CodeQueryKind, CliError> {
    match value {
        "hybrid" => Ok(CodeQueryKind::Hybrid),
        "symbol" | "symbols" => Ok(CodeQueryKind::Symbol),
        "definition" | "definitions" => Ok(CodeQueryKind::Definition),
        "reference" | "references" => Ok(CodeQueryKind::References),
        "caller" | "callers" => Ok(CodeQueryKind::Callers),
        "callee" | "callees" => Ok(CodeQueryKind::Callees),
        "import" | "imports" => Ok(CodeQueryKind::Imports),
        "sbom" => Ok(CodeQueryKind::Sbom),
        other => Err(CliError::InvalidCodeQueryKind(other.to_owned())),
    }
}