ztmux 0.1.0

A Rust port of tmux — the full terminal multiplexer, server and client
Documentation
// Copyright (c) 1987, 1993, 1994
// The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the University nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use crate::*;

// originally generated by c2rust
// refactored to remove the BSD prefix, we don't need to worry about collisions with C

pub static mut OPTERR: i32 = 1;
pub static mut OPTIND: i32 = 1;
pub static mut OPTOPT: u8 = 0;
pub static mut OPTRESET: i32 = 0;
pub static mut OPTARG: *mut u8 = null_mut();

/// C `vendor/tmux/compat/getopt_long.c:535`: `int getopt(int nargc, char * const *nargv, const char *options)`
pub unsafe fn getopt(nargc: i32, nargv: *const *mut u8, ostr: *const u8) -> Option<u8> {
    unsafe {
        static mut PLACE: *const u8 = c"".as_ptr().cast();
        let mut oli: *mut u8;
        if ostr.is_null() {
            return None;
        }
        if OPTRESET != 0 || *PLACE == 0 {
            OPTRESET = 0;
            if OPTIND >= nargc || {
                PLACE = *nargv.offset(OPTIND as isize);
                *PLACE != b'-'
            } {
                PLACE = c"".as_ptr().cast();
                return None;
            }
            if *PLACE.add(1) != 0 && {
                PLACE = PLACE.add(1);
                *PLACE == b'-'
            } {
                if *PLACE.add(1) != 0 {
                    return Some(b'?');
                }
                OPTIND += 1;
                PLACE = c"".as_ptr().cast();
                return None;
            }
        }
        let fresh0 = PLACE;
        PLACE = PLACE.add(1);
        OPTOPT = *fresh0;
        if OPTOPT == b':' || {
            oli = crate::libc::strchr(ostr, OPTOPT as i32);
            oli.is_null()
        } {
            if OPTOPT == b'-' {
                return None;
            }
            if *PLACE == 0 {
                OPTIND += 1;
            }
            if OPTERR != 0 && *ostr != b':' {
                let tmp = OPTOPT;
                eprintln!("tmux-rs: unknown option -- {}", tmp as char);
            }
            return Some(b'?');
        }
        oli = oli.add(1);
        if *oli != b':' {
            OPTARG = null_mut();
            if *PLACE == 0 {
                OPTIND += 1;
            }
        } else {
            if *PLACE != 0 {
                OPTARG = PLACE.cast_mut();
            } else {
                OPTIND += 1;
                if nargc <= OPTIND {
                    PLACE = c"".as_ptr().cast();
                    if *ostr == b':' {
                        return Some(b':');
                    }
                    if OPTERR != 0 {
                        let tmp = OPTOPT;
                        eprintln!("tmux-rs: option requires an argument -- {}", tmp as char);
                    }
                    return Some(b'?');
                } else {
                    OPTARG = *nargv.offset(OPTIND as isize);
                }
            }
            PLACE = c"".as_ptr().cast();
            OPTIND += 1;
        }

        Some(OPTOPT)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // The getopt globals (OPTIND/OPTARG/OPTOPT/OPTERR/OPTRESET) plus the
    // function-internal `PLACE` static are shared mutable state, exactly like
    // C's getopt(3). Serialize every test so parallel test threads can't clobber
    // each other's parse state.
    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());

    /// Reset getopt state the way the C manual requires between independent
    /// argument vectors: `optreset = 1; optind = 1;`
    /// (see vendor/tmux/compat/getopt_long.c and getopt.c). We also silence
    /// error output via `OPTERR = 0` so the "unknown option" / "requires an
    /// argument" diagnostics don't spam the test log; the return values are
    /// unaffected by OPTERR.
    unsafe fn reset() {
        unsafe {
            OPTRESET = 1;
            OPTIND = 1;
            OPTERR = 0;
            OPTARG = null_mut();
            OPTOPT = 0;
        }
    }

    // Helper: run getopt once against an argv slice of `*mut u8`.
    unsafe fn go(argv: &[*mut u8], ostr: *const u8) -> Option<u8> {
        unsafe { getopt(argv.len() as i32, argv.as_ptr(), ostr) }
    }

    // Copy the mutable-static globals into locals so assert_eq!/assert! don't
    // form references to them (forbidden in edition 2024).
    unsafe fn optind() -> i32 {
        unsafe { OPTIND }
    }
    unsafe fn optopt() -> u8 {
        unsafe { OPTOPT }
    }
    unsafe fn optarg() -> *mut u8 {
        unsafe { OPTARG }
    }

    #[test]
    fn simple_option_string() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // ostr "ab": both are flags with no argument.
            let argv: [*mut u8; 4] = [
                crate::c!("prog").cast_mut(),
                crate::c!("-a").cast_mut(),
                crate::c!("-b").cast_mut(),
                crate::c!("arg").cast_mut(),
            ];
            let ostr = crate::c!("ab");

            // "-a": flag consumed, optind advances past argv[1].
            assert_eq!(go(&argv, ostr), Some(b'a'));
            assert_eq!(optind(), 2);
            // "-b": flag consumed, optind advances past argv[2].
            assert_eq!(go(&argv, ostr), Some(b'b'));
            assert_eq!(optind(), 3);
            // argv[3] "arg" does not start with '-', so parsing stops (EOF).
            assert_eq!(go(&argv, ostr), None);
            // The simple BSD getopt does not permute; optind is left pointing
            // at the first non-option operand.
            assert_eq!(optind(), 3);
        }
    }

    #[test]
    fn clustered_flags() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // "-ab" packs two flags into one word.
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-ab").cast_mut()];
            let ostr = crate::c!("ab");

            // First 'a': still more chars in the word, so optind stays put.
            assert_eq!(go(&argv, ostr), Some(b'a'));
            assert_eq!(optind(), 1);
            // Then 'b': word exhausted, optind advances.
            assert_eq!(go(&argv, ostr), Some(b'b'));
            assert_eq!(optind(), 2);
            assert_eq!(go(&argv, ostr), None);
        }
    }

    #[test]
    fn required_arg_separate_word() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // ostr "a:b": -a takes an argument, -b is a flag.
            let argv: [*mut u8; 4] = [
                crate::c!("prog").cast_mut(),
                crate::c!("-a").cast_mut(),
                crate::c!("val").cast_mut(),
                crate::c!("-b").cast_mut(),
            ];
            let ostr = crate::c!("a:b");

            assert_eq!(go(&argv, ostr), Some(b'a'));
            // Argument taken from the following word; optind now past both.
            assert!(!optarg().is_null());
            assert_eq!(crate::libc::strcmp(optarg(), crate::c!("val")), 0);
            assert_eq!(optind(), 3);

            assert_eq!(go(&argv, ostr), Some(b'b'));
            assert_eq!(optind(), 4);
            assert_eq!(go(&argv, ostr), None);
        }
    }

    #[test]
    fn required_arg_attached() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // "-aval": the argument is glued to the option letter.
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-aval").cast_mut()];
            let ostr = crate::c!("a:");

            assert_eq!(go(&argv, ostr), Some(b'a'));
            assert!(!optarg().is_null());
            assert_eq!(crate::libc::strcmp(optarg(), crate::c!("val")), 0);
            assert_eq!(optind(), 2);
            assert_eq!(go(&argv, ostr), None);
        }
    }

    #[test]
    fn unknown_option_returns_question_mark() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-x").cast_mut()];
            let ostr = crate::c!("ab");

            // 'x' is not in "ab": BADCH ('?'), and optopt records the offender.
            assert_eq!(go(&argv, ostr), Some(b'?'));
            assert_eq!(optopt(), b'x');
            assert_eq!(optind(), 2);
            assert_eq!(go(&argv, ostr), None);
        }
    }

    #[test]
    fn missing_required_arg_default() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // "-a" requires an argument but none follows -> BADCH ('?')
            // because the option string does not begin with ':'.
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-a").cast_mut()];
            let ostr = crate::c!("a:");

            assert_eq!(go(&argv, ostr), Some(b'?'));
            assert_eq!(optopt(), b'a');
        }
    }

    #[test]
    fn missing_required_arg_colon_ostr() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // Leading ':' in the option string switches the missing-argument
            // return from '?' (BADCH) to ':' (BADARG).
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-a").cast_mut()];
            let ostr = crate::c!(":a:");

            assert_eq!(go(&argv, ostr), Some(b':'));
            assert_eq!(optopt(), b'a');
        }
    }

    #[test]
    fn double_dash_terminates() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // "--" ends option processing; the following "-a" is an operand.
            let argv: [*mut u8; 3] = [
                crate::c!("prog").cast_mut(),
                crate::c!("--").cast_mut(),
                crate::c!("-a").cast_mut(),
            ];
            let ostr = crate::c!("a");

            // First call swallows "--" and reports EOF, leaving optind past it.
            assert_eq!(go(&argv, ostr), None);
            assert_eq!(optind(), 2);
        }
    }

    #[test]
    fn lone_dash_is_operand() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // A bare "-" is a non-option operand (conventionally stdin), so
            // getopt returns EOF and does not advance optind.
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-").cast_mut()];
            let ostr = crate::c!("a");

            assert_eq!(go(&argv, ostr), None);
            assert_eq!(optind(), 1);
        }
    }

    #[test]
    fn no_permutation_stops_at_first_operand() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            // The BSD getopt(3) never permutes: once a non-option word is seen,
            // parsing stops and later options are NOT reordered/parsed.
            let argv: [*mut u8; 4] = [
                crate::c!("prog").cast_mut(),
                crate::c!("-a").cast_mut(),
                crate::c!("file").cast_mut(),
                crate::c!("-b").cast_mut(),
            ];
            let ostr = crate::c!("ab");

            assert_eq!(go(&argv, ostr), Some(b'a'));
            assert_eq!(optind(), 2);
            // Stops at "file"; "-b" is left unparsed.
            assert_eq!(go(&argv, ostr), None);
            assert_eq!(optind(), 2);
        }
    }

    #[test]
    fn null_option_string_returns_none() {
        let _g = LOCK.lock().unwrap();
        unsafe {
            reset();
            let argv: [*mut u8; 2] =
                [crate::c!("prog").cast_mut(), crate::c!("-a").cast_mut()];
            // A null option string is rejected immediately with EOF.
            assert_eq!(getopt(argv.len() as i32, argv.as_ptr(), null()), None);
        }
    }
}