liber-cli 0.1.0

AI-agent-readable company directory CLI
//! `liber people …` verbs.

use std::path::Path;

use serde_json::{Map, Value};

use crate::data;
use crate::output::{emit, CliError, Ctx};

pub fn all_people(data: &Value) -> Vec<(String, Map<String, Value>)> {
    let mut out: Vec<(String, Map<String, Value>)> = Vec::new();
    if let Some(depts) = data.get("departments").and_then(|v| v.as_object()) {
        for (dept_name, dept) in depts {
            if let Some(members) = dept.get("members").and_then(|v| v.as_array()) {
                for m in members {
                    if let Some(obj) = m.as_object() {
                        out.push((dept_name.clone(), obj.clone()));
                    }
                }
            }
        }
    }
    if let Some(root) = data.get("root_users").and_then(|v| v.as_array()) {
        for m in root {
            if let Some(obj) = m.as_object() {
                out.push(("Root".to_string(), obj.clone()));
            }
        }
    }
    out
}

pub fn find_person<'a>(
    people: &'a [(String, Map<String, Value>)],
    needle: &str,
) -> Option<&'a (String, Map<String, Value>)> {
    let n = needle.to_lowercase();
    for entry in people {
        let m = &entry.1;
        if m.get("name").and_then(|v| v.as_str()) == Some(needle) {
            return Some(entry);
        }
        if let Some(gh) = m.get("github").and_then(|v| v.as_str()) {
            if gh.to_lowercase() == n {
                return Some(entry);
            }
        }
        if let Some(arr) = m.get("git_aliases").and_then(|v| v.as_array()) {
            for a in arr {
                if let Some(s) = a.as_str() {
                    if s.to_lowercase() == n {
                        return Some(entry);
                    }
                }
            }
        }
    }
    None
}

fn with_department(person: &Map<String, Value>, dept: &str) -> Value {
    let mut out = person.clone();
    out.insert("department".to_string(), Value::String(dept.to_string()));
    Value::Object(out)
}

pub fn list(dir: &Path, ctx: Ctx, dept_filter: Option<&str>) -> Result<(), CliError> {
    let data = data::load(dir, "people")?;
    let people = all_people(&data);
    let items: Vec<Value> = people
        .iter()
        .filter(|(d, _)| dept_filter.map_or(true, |want| d == want))
        .map(|(d, p)| with_department(p, d))
        .collect();
    emit(ctx, &Value::Array(items));
    Ok(())
}

pub fn get(dir: &Path, ctx: Ctx, name: &str) -> Result<(), CliError> {
    let data = data::load(dir, "people")?;
    let people = all_people(&data);
    match find_person(&people, name) {
        Some((dept, person)) => {
            emit(ctx, &with_department(person, dept));
            Ok(())
        }
        None => Err(CliError::not_found(
            format!("person not found: {name}"),
            Some("try `liber people list` or check git_aliases / github username".into()),
        )),
    }
}