wsb 0.275.7622

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
//! `wsb plugin` — which plugins this machine lets use wsb's services.
//!
//! Enabling is the owner's act and a file's worth of state: the list is read
//! by the daemon at every registration and every call, so a change here takes
//! effect at once, daemon running or not. See [`crate::plugin`].

use crate::plugin::{PluginName, ENABLED_FILE};

pub const USAGE: &str = "\
Usage: wsb plugin <command>

  list               the plugins enabled on this machine, and which are running
  add <name>         enable a plugin: it may register its services with the
                     daemon, and use the peer bridge
  remove <name>      disable it; it stops answering at once

A plugin's services are carried between paired machines over the connection
they already hold, each switched per machine and off until switched on:
`wsb peer service <peer> <plugin>.<service> on`.";

pub fn dispatch(args: &[String]) -> u8 {
    let verb = args.get(2).map(String::as_str);
    let rest = if args.len() > 3 { &args[3..] } else { &[] };
    let result = match verb {
        None | Some("list") => list(),
        Some("add") => add(rest),
        Some("remove") => remove(rest),
        Some("help") | Some("--help") | Some("-h") => {
            crate::outln!("{USAGE}");
            return 0;
        }
        Some(other) => {
            crate::errln!("wsb plugin: unknown command '{other}'\n\n{USAGE}");
            return 2;
        }
    };
    match result {
        Ok(()) => 0,
        Err(message) => {
            crate::errln!("wsb plugin: {message}");
            1
        }
    }
}

fn name(rest: &[String]) -> Result<PluginName, String> {
    match rest {
        [one] => PluginName::parse(one),
        _ => Err(format!("name one plugin\n\n{USAGE}")),
    }
}

fn add(rest: &[String]) -> Result<(), String> {
    let plugin = name(rest)?;
    let machine_dir = crate::daemon::machine_dir()?;
    if crate::plugin::enable(&machine_dir, &plugin)? {
        crate::outln!("enabled plugin {plugin} on this machine");
    } else {
        crate::outln!("plugin {plugin} was already enabled on this machine");
    }
    Ok(())
}

fn remove(rest: &[String]) -> Result<(), String> {
    let plugin = name(rest)?;
    let machine_dir = crate::daemon::machine_dir()?;
    if crate::plugin::disable(&machine_dir, &plugin)? {
        crate::outln!("disabled plugin {plugin}: it answers nothing from now on");
    } else {
        crate::outln!(
            "plugin {plugin} is not enabled on this machine ({})",
            machine_dir.join(ENABLED_FILE).display()
        );
    }
    Ok(())
}

/// What is enabled, from the file, and what is running, from the daemon.
///
/// The daemon is asked only if it is already running: a list of what runs
/// does not start anything, and "no daemon" answers it.
fn list() -> Result<(), String> {
    let machine_dir = crate::daemon::machine_dir()?;
    let enabled = crate::plugin::enabled(&machine_dir)?;
    if enabled.is_empty() {
        crate::outln!("no plugin is enabled on this machine; `wsb plugin add <name>` enables one");
        return Ok(());
    }
    let running: Vec<serde_json::Value> = match crate::daemon::MachineRendezvous::read(&machine_dir)? {
        None => Vec::new(),
        Some(rendezvous) => {
            let client = crate::daemon::Client::from_rendezvous(&rendezvous)?;
            let answer: serde_json::Value = client.operator_get_json(crate::plugin::route::LIST)?;
            answer["running"].as_array().cloned().unwrap_or_default()
        }
    };
    for plugin in &enabled {
        let entry = running.iter().find(|entry| entry["plugin"] == plugin.as_str());
        match entry {
            Some(entry) => {
                let services: Vec<&str> = entry["services"]
                    .as_array()
                    .map(|services| services.iter().filter_map(|s| s.as_str()).collect())
                    .unwrap_or_default();
                crate::outln!(
                    "{plugin}  running (pid {}): {}",
                    entry["pid"],
                    services.join(", ")
                );
            }
            None => crate::outln!("{plugin}  enabled, not running"),
        }
    }
    Ok(())
}