websocat 4.0.0-alpha3

Command-line client for web sockets, like netcat/curl/socat for ws://.
Documentation
use std::ffi::OsStr;

use base64::Engine as _;

use super::{
    scenarioprinter::StrLit,
    types::{Endpoint, ScenarioPrintingEnvironment},
};

pub fn format_osstr(arg: &OsStr) -> String {
    if let Ok(s) = arg.try_into() {
        let s: &str = s;
        return format!("osstr_str({})", StrLit(s));
    }
    #[cfg(any(unix, target_os = "wasi"))]
    {
        #[cfg(unix)]
        use std::os::unix::ffi::OsStrExt;
        #[cfg(all(not(unix), target_os = "wasi"))]
        use std::os::wasi::ffi::OsStrExt;

        let x = base64::prelude::BASE64_STANDARD.encode(arg.as_bytes());
        return format!("osstr_base64_unix_bytes(\"{x}\")");
    }
    #[cfg(windows)]
    {
        use std::os::windows::ffi::OsStrExt;

        let b: Vec<u16> = arg.encode_wide().collect();
        let bb: Vec<u8> =
            Vec::from_iter(b.into_iter().map(|x| u16::to_le_bytes(x))).into_flattened();
        let x = base64::prelude::BASE64_STANDARD.encode(bb);

        return format!("osstr_base64_windows_utf16le(\"{}\")", x);
    }
    #[allow(unreachable_code)]
    {
        let x = base64::prelude::BASE64_STANDARD.encode(arg.as_encoded_bytes());
        format!("osstr_base64_unchecked_encoded_bytes(\"{x}\")")
    }
}

impl Endpoint {
    fn continue_printing_cmd_or_exec(
        &self,
        env: &mut ScenarioPrintingEnvironment<'_>,
        var_cmd: String,
    ) -> anyhow::Result<String> {
        if let Some(ref x) = env.opts.exec_chdir {
            if let Some(s) = x.to_str() {
                env.printer
                    .print_line(&format!("{var_cmd}.chdir({});", StrLit(s)));
            } else {
                env.printer.print_line(&format!(
                    "{var_cmd}.chdir_osstr({});",
                    format_osstr(x.as_os_str())
                ));
            }
        }

        if let Some(ref x) = env.opts.exec_arg0 {
            if let Some(s) = x.to_str() {
                env.printer
                    .print_line(&format!("{var_cmd}.arg0({});", StrLit(s)));
            } else {
                env.printer.print_line(&format!(
                    "{var_cmd}.arg0_osstr({});",
                    format_osstr(x.as_os_str())
                ));
            }
        }

        if let Some(x) = env.opts.exec_uid {
            env.printer.print_line(&format!("{var_cmd}.uid({x});"));
        }
        if let Some(x) = env.opts.exec_gid {
            env.printer.print_line(&format!("{var_cmd}.gid({x});"));
        }
        if let Some(x) = env.opts.exec_windows_creation_flags {
            env.printer
                .print_line(&format!("{var_cmd}.windows_creation_flags({x});"));
        }

        let var_chld = env.vars.getnewvarname("chld");
        let var_s = env.vars.getnewvarname("pstdio");

        if env.opts.exec_dup2.is_none() {
            env.printer
                .print_line(&format!("{var_cmd}.configure_fds(2, 2, 1);"));
            env.printer
                .print_line(&format!("let {var_chld} = {var_cmd}.execute();"));
            env.printer
                .print_line(&format!("let {var_s} = {var_chld}.socket();"));

            if env.opts.exec_monitor_exits {
                env.printer
                    .print_line(&format!("put_hangup_part({var_s}, {var_chld}.wait());"));
            }
            Ok(var_s)
        } else {
            env.printer
                .print_line(&format!("{var_cmd}.configure_fds(1, 1, 1);"));

            Ok(var_cmd)
        }
    }

    pub(super) fn begin_print_exec(
        &self,
        env: &mut ScenarioPrintingEnvironment<'_>,
    ) -> anyhow::Result<String> {
        match self {
            Endpoint::Exec(s) => {
                let var_cmd = env.vars.getnewvarname("cmd");
                if let Ok(s) = s.as_os_str().try_into() {
                    let s: &str = s;
                    env.printer
                        .print_line(&format!("let {var_cmd} = subprocess_new({});", StrLit(s)));
                } else {
                    env.printer.print_line(&format!(
                        "let {var_cmd} = subprocess_new_osstr({});",
                        format_osstr(s)
                    ));
                }

                for arg in &env.opts.exec_args {
                    if let Some(s) = arg.to_str() {
                        env.printer
                            .print_line(&format!("{var_cmd}.arg({});", StrLit(s)));
                    } else {
                        env.printer
                            .print_line(&format!("{var_cmd}.arg_osstr({});", format_osstr(arg)));
                    }
                }

                self.continue_printing_cmd_or_exec(env, var_cmd)
            }
            Endpoint::Cmd(s) => {
                let var_cmd = env.vars.getnewvarname("cmd");
                if cfg!(windows) {
                    env.printer
                        .print_line(&format!("let {var_cmd} = subprocess_new(\"cmd\");"));
                    env.printer.print_line(&format!("{var_cmd}.arg(\"/C\");",));
                    env.printer
                        .print_line(&format!("{var_cmd}.raw_windows_arg({});", format_osstr(s)));
                } else {
                    env.printer
                        .print_line(&format!("let {var_cmd} = subprocess_new(\"sh\");"));
                    env.printer.print_line(&format!("{var_cmd}.arg(\"-c\");",));
                    if let Ok(s) = s.as_os_str().try_into() {
                        let s: &str = s;
                        env.printer
                            .print_line(&format!("{var_cmd}.arg({});", StrLit(s)));
                    } else {
                        env.printer
                            .print_line(&format!("{var_cmd}.arg_osstr({});", format_osstr(s)));
                    }
                }

                self.continue_printing_cmd_or_exec(env, var_cmd)
            }
            _ => panic!(),
        }
    }

    pub(super) fn end_print_exec(&self, _env: &mut ScenarioPrintingEnvironment<'_>) {
        match self {
            Endpoint::Exec(_) => {}
            Endpoint::Cmd(_) => {}
            _ => panic!(),
        }
    }
}