halley-cli 0.1.0

Command-line interface for interacting with and controlling the Halley Wayland compositor.
use halley_ipc::{ClusterRequest, ClusterTarget, Request};

use crate::help::HelpTopic;
use crate::parse::{ParseOutcome, UsageError, contains_help_flag, parse_output_option};

pub(crate) fn parse_cluster_request(args: &[String]) -> Result<ParseOutcome, UsageError> {
    match args.first().map(String::as_str) {
        None | Some("-h" | "--help") => Ok(ParseOutcome::Help(HelpTopic::Cluster)),
        Some("list") => parse_cluster_list(&args[1..]),
        Some("inspect") => parse_cluster_inspect(&args[1..]),
        Some("layout") => parse_cluster_layout_request(&args[1..]),
        Some(other) => Err(UsageError::new(
            format!("unknown cluster command: {other}"),
            HelpTopic::Cluster,
        )),
    }
}

fn parse_cluster_list(args: &[String]) -> Result<ParseOutcome, UsageError> {
    if contains_help_flag(args) {
        return Ok(ParseOutcome::Help(HelpTopic::ClusterList));
    }
    let output = parse_output_option(args, HelpTopic::ClusterList)?;
    Ok(ParseOutcome::Request(Request::Cluster(
        ClusterRequest::List { output },
    )))
}

fn parse_cluster_inspect(args: &[String]) -> Result<ParseOutcome, UsageError> {
    if contains_help_flag(args) {
        return Ok(ParseOutcome::Help(HelpTopic::ClusterInspect));
    }

    let mut target = None;
    let mut output = None;
    let mut index = 0usize;
    while index < args.len() {
        match args[index].as_str() {
            "-o" | "--output" => {
                index += 1;
                let Some(value) = args.get(index) else {
                    return Err(UsageError::new(
                        "missing value for -o/--output",
                        HelpTopic::ClusterInspect,
                    ));
                };
                output = Some(value.clone());
            }
            "--json" => {}
            other if other.starts_with('-') => {
                return Err(UsageError::new(
                    format!("unknown option for cluster inspect: {other}"),
                    HelpTopic::ClusterInspect,
                ));
            }
            other => {
                if target.is_some() {
                    return Err(UsageError::new(
                        format!("unexpected extra cluster target: {other}"),
                        HelpTopic::ClusterInspect,
                    ));
                }
                target = Some(parse_cluster_target(other)?);
            }
        }
        index += 1;
    }

    Ok(ParseOutcome::Request(Request::Cluster(
        ClusterRequest::Inspect { target, output },
    )))
}

fn parse_cluster_layout_request(args: &[String]) -> Result<ParseOutcome, UsageError> {
    match args.first().map(String::as_str) {
        None | Some("-h" | "--help") => Ok(ParseOutcome::Help(HelpTopic::ClusterLayout)),
        Some("cycle") => parse_cluster_layout_cycle(&args[1..]),
        Some(other) => Err(UsageError::new(
            format!("unknown cluster layout command: {other}"),
            HelpTopic::ClusterLayout,
        )),
    }
}

fn parse_cluster_layout_cycle(args: &[String]) -> Result<ParseOutcome, UsageError> {
    if contains_help_flag(args) {
        return Ok(ParseOutcome::Help(HelpTopic::ClusterLayoutCycle));
    }
    let output = parse_output_option(args, HelpTopic::ClusterLayoutCycle)?;
    Ok(ParseOutcome::Request(Request::Cluster(
        ClusterRequest::LayoutCycle { output },
    )))
}

fn parse_cluster_target(text: &str) -> Result<ClusterTarget, UsageError> {
    if text.eq_ignore_ascii_case("current") {
        return Ok(ClusterTarget::Current);
    }
    text.parse::<u64>().map(ClusterTarget::Id).map_err(|_| {
        UsageError::new(
            format!("unknown cluster target: {text}"),
            HelpTopic::ClusterInspect,
        )
    })
}