rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use rmux_core::command_inventory::render_list_commands_for_socket;
use rmux_core::command_parser::ParsedCommand;
use rmux_proto::{CommandOutput, RmuxError};

use super::command_args::command_arguments_as_strings;
use super::queue::QueueCommandAction;
use super::tokens::{parse_compact_flag_cluster, CommandTokens};
use super::values::unsupported_flag;
use super::RequestHandler;

#[derive(Debug, Clone)]
pub(super) struct ParsedListCommandsCommand {
    format: Option<String>,
    command: Option<String>,
}

pub(super) fn parse_queued_list_commands(
    command: ParsedCommand,
) -> Result<ParsedListCommandsCommand, RmuxError> {
    let arguments = command_arguments_as_strings(command.name(), command.arguments())?;
    let mut args = CommandTokens::new(arguments);
    let mut format = None;

    while let Some(token) = args.peek().map(str::to_owned) {
        match token.as_str() {
            "--" => {
                let _ = args.optional();
                break;
            }
            "-F" => {
                let _ = args.optional();
                format = Some(args.required("-F format")?);
            }
            token => {
                let Some(cluster) = parse_compact_flag_cluster("list-commands", token, "", "F")?
                else {
                    if token.starts_with('-') && token != "-" {
                        return Err(unsupported_flag("list-commands", token));
                    }
                    break;
                };
                let _ = args.optional();
                let flag = cluster
                    .into_iter()
                    .next()
                    .expect("nonempty compact list-commands flag");
                format = Some(flag.value_or_next(&mut args, "-F format")?);
            }
        }
    }

    let command = args.optional();
    args.no_extra("list-commands")?;
    Ok(ParsedListCommandsCommand { format, command })
}

impl RequestHandler {
    pub(super) fn execute_queued_list_commands(
        &self,
        command: ParsedListCommandsCommand,
    ) -> Result<QueueCommandAction, RmuxError> {
        let socket_path = self.socket_path();
        let socket_path = socket_path.to_string_lossy();
        let lines = render_list_commands_for_socket(
            command.format.as_deref(),
            command.command.as_deref(),
            &socket_path,
        )
        .map_err(|error| RmuxError::Server(error.to_string()))?;
        let stdout = if lines.is_empty() {
            Vec::new()
        } else {
            format!("{}\n", lines.join("\n")).into_bytes()
        };
        Ok(QueueCommandAction::Normal {
            output: Some(CommandOutput::from_stdout(stdout)),
            error: None,
            source_file_error: None,
            exit_status: None,
        })
    }
}

#[cfg(test)]
mod tests {
    use rmux_core::command_parser::CommandParser;

    use super::*;

    fn parse(command: &str) -> ParsedListCommandsCommand {
        let parsed = CommandParser::new()
            .parse_one_group(command)
            .expect("list-commands parses");
        parse_queued_list_commands(
            parsed
                .commands()
                .first()
                .expect("one parsed command")
                .clone(),
        )
        .expect("queued list-commands parses")
    }

    #[test]
    fn compact_format_flag_and_explicit_command_parse() {
        let parsed = parse("list-commands '-F#{command_list_name}' new-window");
        assert_eq!(parsed.format.as_deref(), Some("#{command_list_name}"));
        assert_eq!(parsed.command.as_deref(), Some("new-window"));
    }
}