bash-builtins 0.4.1

This crate provides utilities to implement loadable builtins for bash.
Documentation
use assert_matches::assert_matches;
use bash_builtins::{BuiltinOptions, Error};
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};

#[cfg(unix)]
#[test]
fn parse_options() {
    #[derive(BuiltinOptions, Debug)]
    enum Opt<'a> {
        #[opt = 'a']
        A,

        #[opt = 'b']
        B(i64),

        #[opt = 'c']
        C(&'a str),

        #[opt = 'd']
        D(&'a std::path::Path),

        #[opt = 'e']
        E(String),

        #[opt = 'f']
        F(Option<&'a str>),

        #[opt = 'g']
        G(std::path::PathBuf),
    }

    assert_eq!(Opt::options(), b"ab:c:d:e:f;g:\0");

    // Valid option

    assert_matches!(
        Opt::from_option(
            b'c' as _,
            Some(CStr::from_bytes_with_nul(b"0123\0").unwrap())
        ),
        Ok(Opt::C("0123"))
    );

    assert_eq!(SH_NEEDARG_CALLS.swap(0, SeqCst), 0);
    assert_eq!(BUILTIN_USAGE_CALLS.swap(0, SeqCst), 0);

    // Invalid option

    assert_matches!(Opt::from_option(b'z' as _, None), Err(Error::Usage));

    assert_eq!(SH_NEEDARG_CALLS.swap(0, SeqCst), 0);
    assert_eq!(BUILTIN_USAGE_CALLS.swap(0, SeqCst), 1);

    // Missing argument

    assert_matches!(Opt::from_option(b'e' as _, None), Err(Error::Usage));

    assert_eq!(SH_NEEDARG_CALLS.swap(0, SeqCst), 1);
    assert_eq!(BUILTIN_USAGE_CALLS.swap(0, SeqCst), 0);

    // Non-required arguments.

    assert_matches!(Opt::from_option(b'f' as _, None), Ok(Opt::F(None)));

    assert_eq!(SH_NEEDARG_CALLS.swap(0, SeqCst), 0);
    assert_eq!(BUILTIN_USAGE_CALLS.swap(0, SeqCst), 0);

    assert_matches!(
        Opt::from_option(
            b'f' as _,
            Some(CStr::from_bytes_with_nul(b"abc\0").unwrap())
        ),
        Ok(Opt::F(Some("abc")))
    );

    assert_eq!(SH_NEEDARG_CALLS.swap(0, SeqCst), 0);
    assert_eq!(BUILTIN_USAGE_CALLS.swap(0, SeqCst), 0);
}

// Mock bash functions and static varibles required by the
// `BuiltinOptions` trait.

static SH_NEEDARG_CALLS: AtomicUsize = AtomicUsize::new(0);

static BUILTIN_USAGE_CALLS: AtomicUsize = AtomicUsize::new(0);

#[no_mangle]
extern "C" fn builtin_error(_: *const c_char) {}

#[no_mangle]
extern "C" fn builtin_warning(_: *const c_char) {}

#[no_mangle]
extern "C" fn builtin_usage() {
    BUILTIN_USAGE_CALLS.fetch_add(1, SeqCst);
}

#[no_mangle]
extern "C" fn builtin_help() {}

#[no_mangle]
extern "C" fn sh_needarg(_: *const c_char) {
    SH_NEEDARG_CALLS.fetch_add(1, SeqCst);
}

#[no_mangle]
static mut list_opttype: c_int = 0;

#[no_mangle]
static mut list_optopt: c_int = 0;