openauth-telemetry 0.0.4

Telemetry support for OpenAuth.
Documentation
use std::path::PathBuf;

use crate::types::DetectionInfo;

pub(super) fn detect_from_current_manifest(
    candidates: &[(&'static str, &'static str)],
) -> Option<DetectionInfo> {
    let manifest = std::fs::read_to_string(current_manifest_path()?).ok()?;
    detect_from_manifest(&manifest, candidates)
}

pub(super) fn detect_from_manifest(
    manifest: &str,
    candidates: &[(&'static str, &'static str)],
) -> Option<DetectionInfo> {
    candidates.iter().find_map(|(package, name)| {
        dependency_version(manifest, package).map(|version| DetectionInfo {
            name: (*name).to_owned(),
            version,
        })
    })
}

fn current_manifest_path() -> Option<PathBuf> {
    let dir = std::env::var("CARGO_MANIFEST_DIR")
        .ok()
        .map(PathBuf::from)
        .or_else(|| std::env::current_dir().ok())?;
    Some(dir.join("Cargo.toml"))
}

fn dependency_version(manifest: &str, package: &str) -> Option<String> {
    let mut in_dependency_section = false;
    for raw_line in manifest.lines() {
        let line = strip_comment(raw_line).trim();
        if line.is_empty() {
            continue;
        }
        if let Some(section) = section_name(line) {
            in_dependency_section = is_dependency_section(section);
            continue;
        }
        if !in_dependency_section {
            continue;
        }
        if let Some(version) = parse_dependency_line(line, package) {
            return Some(version);
        }
    }
    None
}

fn section_name(line: &str) -> Option<&str> {
    line.strip_prefix('[')?.strip_suffix(']').map(str::trim)
}

fn is_dependency_section(section: &str) -> bool {
    matches!(
        section,
        "dependencies" | "dev-dependencies" | "build-dependencies"
    ) || (section.starts_with("target.") && section.ends_with(".dependencies"))
}

fn parse_dependency_line(line: &str, package: &str) -> Option<String> {
    let rest = line.strip_prefix(package)?.trim_start();
    if let Some(rest) = rest.strip_prefix(".workspace") {
        return parse_workspace_assignment(rest);
    }
    let value = rest.strip_prefix('=')?.trim();
    parse_dependency_value(value)
}

fn parse_dependency_value(value: &str) -> Option<String> {
    if let Some(version) = parse_quoted(value) {
        return Some(version.to_owned());
    }
    if value.starts_with('{') {
        if let Some(version) = table_string_value(value, "version") {
            return Some(version.to_owned());
        }
        if table_bool_value(value, "workspace") == Some(true) {
            return Some("workspace".to_owned());
        }
    }
    None
}

fn parse_workspace_assignment(rest: &str) -> Option<String> {
    let value = rest.trim_start().strip_prefix('=')?.trim();
    (value == "true").then(|| "workspace".to_owned())
}

fn table_string_value<'a>(table: &'a str, key: &str) -> Option<&'a str> {
    let value = table_value(table, key)?;
    parse_quoted(value)
}

fn table_bool_value(table: &str, key: &str) -> Option<bool> {
    let value = table_value(table, key)?;
    match value {
        value if value.starts_with("true") => Some(true),
        value if value.starts_with("false") => Some(false),
        _ => None,
    }
}

fn table_value<'a>(table: &'a str, key: &str) -> Option<&'a str> {
    let fields = table.trim_start_matches('{').trim_end_matches('}');
    for field in fields.split(',') {
        let field = field.trim();
        let Some(rest) = field.strip_prefix(key) else {
            continue;
        };
        let Some(value) = rest.trim_start().strip_prefix('=') else {
            continue;
        };
        return Some(value.trim_start());
    }
    None
}

fn parse_quoted(value: &str) -> Option<&str> {
    let quote = value.chars().next()?;
    if quote != '"' && quote != '\'' {
        return None;
    }
    let rest = value.get(quote.len_utf8()..)?;
    let end = rest.find(quote)?;
    rest.get(..end)
}

fn strip_comment(line: &str) -> &str {
    let mut in_single_quote = false;
    let mut in_double_quote = false;
    for (index, ch) in line.char_indices() {
        match ch {
            '\'' if !in_double_quote => in_single_quote = !in_single_quote,
            '"' if !in_single_quote => in_double_quote = !in_double_quote,
            '#' if !in_single_quote && !in_double_quote => return &line[..index],
            _ => {}
        }
    }
    line
}