legend-cli 0.1.4

CLI for Legend Prime
use std::io::IsTerminal;

use comfy_table::Table;
use legend_client::types::*;
use serde::Serialize;

#[derive(Clone)]
pub enum OutputMode {
    Json,
    Table,
    Quiet,
}

pub fn detect_mode(json_flag: bool, quiet_flag: bool) -> OutputMode {
    if quiet_flag {
        OutputMode::Quiet
    } else if json_flag || !std::io::stdout().is_terminal() {
        OutputMode::Json
    } else {
        OutputMode::Table
    }
}

pub fn print_json<T: Serialize>(value: &T) {
    println!("{}", serde_json::to_string_pretty(value).unwrap());
}

pub fn print_account(account: &Account, accessible: bool, mode: &OutputMode) {
    match mode {
        OutputMode::Json => {
            #[derive(Serialize)]
            struct AccountWithAccessible<'a> {
                #[serde(flatten)]
                account: &'a Account,
                accessible: bool,
            }
            print_json(&AccountWithAccessible { account, accessible });
        }
        OutputMode::Quiet => println!("{}", account.account_id),
        OutputMode::Table => {
            let mut table = Table::new();
            table.set_header(vec!["Field", "Value"]);
            table.add_row(vec!["Account ID", &account.account_id]);
            if let Some(st) = &account.signer_type {
                table.add_row(vec!["Signer Type", st]);
            }
            if let Some(addr) = &account.ethereum_signer_address {
                table.add_row(vec!["Ethereum Signer", addr]);
            }
            if let Some(pk) = &account.p256_public_key {
                table.add_row(vec!["P256 Public Key", pk]);
            }
            if let Some(addr) = &account.legend_wallet_address {
                table.add_row(vec!["Legend Wallet", addr]);
            }
            if let Some(addr) = &account.solana_wallet_address {
                table.add_row(vec!["Solana Wallet", addr]);
            }
            if let Some(org) = &account.turnkey_sub_org_id {
                table.add_row(vec!["Turnkey Sub-Org", org]);
            }
            if let Some(ks) = &account.key_storage {
                table.add_row(vec!["Key Storage", ks]);
            }
            table.add_row(vec!["Accessible", if accessible { "yes" } else { "no" }]);
            table.add_row(vec!["Created At", &account.created_at]);
            println!("{table}");
        }
    }
}

pub fn print_account_list(
    list: &AccountList,
    accessibility: &std::collections::HashMap<String, bool>,
    all: bool,
    mode: &OutputMode,
) {
    match mode {
        OutputMode::Json => {
            if all {
                #[derive(Serialize)]
                struct AccountWithAccessible<'a> {
                    #[serde(flatten)]
                    account: &'a Account,
                    accessible: bool,
                }
                #[derive(Serialize)]
                struct AccountListWithAccessible<'a> {
                    accounts: Vec<AccountWithAccessible<'a>>,
                }
                let annotated = AccountListWithAccessible {
                    accounts: list
                        .accounts
                        .iter()
                        .map(|a| AccountWithAccessible {
                            account: a,
                            accessible: accessibility.get(&a.account_id).copied().unwrap_or(false),
                        })
                        .collect(),
                };
                print_json(&annotated);
            } else {
                print_json(list);
            }
        }
        OutputMode::Quiet => {
            for a in &list.accounts {
                println!("{}", a.account_id);
            }
        }
        OutputMode::Table => {
            let mut table = Table::new();
            if all {
                table.set_header(vec![
                    "Account ID",
                    "Signer Type",
                    "Ethereum Signer",
                    "Key Storage",
                    "Accessible",
                    "Created At",
                ]);
                for a in &list.accounts {
                    let accessible = accessibility.get(&a.account_id).copied().unwrap_or(false);
                    let accessible_str = if accessible { "yes" } else { "no" };
                    table.add_row(vec![
                        &a.account_id,
                        a.signer_type.as_deref().unwrap_or("-"),
                        a.ethereum_signer_address.as_deref().unwrap_or("-"),
                        a.key_storage.as_deref().unwrap_or("-"),
                        accessible_str,
                        &a.created_at,
                    ]);
                }
            } else {
                table.set_header(vec![
                    "Account ID",
                    "Signer Type",
                    "Ethereum Signer",
                    "Key Storage",
                    "Created At",
                ]);
                for a in &list.accounts {
                    table.add_row(vec![
                        &a.account_id,
                        a.signer_type.as_deref().unwrap_or("-"),
                        a.ethereum_signer_address.as_deref().unwrap_or("-"),
                        a.key_storage.as_deref().unwrap_or("-"),
                        &a.created_at,
                    ]);
                }
            }
            println!("{table}");
        }
    }
}

pub fn print_plan(plan: &Plan, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(plan),
        OutputMode::Quiet => println!("{}", plan.plan_id),
        OutputMode::Table => {
            let mut table = Table::new();
            table.set_header(vec!["Field", "Value"]);
            table.add_row(vec!["Plan ID", &plan.plan_id]);
            table.add_row(vec!["Expires At", &plan.expires_at]);
            if let Some(digest) = plan.digest() {
                table.add_row(vec!["Digest", digest]);
            }
            println!("{table}");
        }
    }
}

pub fn print_execute_result(result: &ExecuteResult, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(result),
        OutputMode::Quiet => println!("{}", result.plan_id),
        OutputMode::Table => {
            let mut table = Table::new();
            table.set_header(vec!["Field", "Value"]);
            table.add_row(vec!["Plan ID", &result.plan_id]);
            if let Some(ref activity_id) = result.activity_id {
                table.add_row(vec!["Activity ID", activity_id]);
            }
            table.add_row(vec!["Status", &result.status]);
            println!("{table}");
        }
    }
}

pub fn print_networks(networks: &NetworkList, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(networks),
        OutputMode::Quiet => {
            for n in &networks.networks {
                println!("{}", n.name);
            }
        }
        OutputMode::Table => {
            let mut table = Table::new();
            table.set_header(vec!["Name", "Chain ID", "Display Name"]);
            for n in &networks.networks {
                table.add_row(vec![&n.name, &n.chain_id.to_string(), &n.display_name]);
            }
            println!("{table}");
        }
    }
}

pub fn print_assets(assets: &AssetMap, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(assets),
        _ => print_json(assets),
    }
}

pub fn print_folio(folio: &Folio, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(folio),
        _ => print_json(folio),
    }
}

pub fn print_activities(list: &ActivityList, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(list),
        OutputMode::Quiet => {
            for a in &list.activities {
                println!("{}", a.id);
            }
        }
        OutputMode::Table => {
            let mut table = Table::new();
            table.set_header(vec!["ID", "Status"]);
            for a in &list.activities {
                table.add_row(vec![&a.id.to_string(), a.status.as_deref().unwrap_or("-")]);
            }
            println!("{table}");
        }
    }
}

pub fn print_activity(activity: &Activity, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(activity),
        OutputMode::Quiet => println!("{}", activity.id),
        OutputMode::Table => print_json(activity),
    }
}

pub fn print_prime_account(pa: &PrimeAccount, mode: &OutputMode) {
    match mode {
        OutputMode::Json => print_json(pa),
        OutputMode::Quiet => println!("{}", pa.id),
        OutputMode::Table => {
            let mut table = Table::new();
            table.set_header(vec!["Field", "Value"]);
            table.add_row(vec!["ID", &pa.id]);
            if let Some(name) = &pa.name {
                table.add_row(vec!["Name", name]);
            }
            if let Some(email) = &pa.email {
                table.add_row(vec!["Email", email]);
            }
            println!("{table}");
        }
    }
}