c-ffi 0.4.6

C FFI utilities.
Documentation
//!Collection of utilities to work with C stuff
#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
#![no_std]

mod sys;
pub use sys::*;

#[cfg(feature = "libc")]
pub mod locale;
#[cfg(all(feature = "libc", feature = "memory"))]
pub mod memory;
#[cfg(all(feature = "libc", feature = "memory"))]
pub mod env;
#[cfg(feature = "libc")]
pub mod process;
pub mod args;
pub use args::Args;

#[cfg(feature = "libc")]
#[allow(non_camel_case_types)]
///Alias to C's `int` type
///
///With `libc` enabled, alias to `libc::c_int`
pub type int = libc::c_int;
#[cfg(not(feature = "libc"))]
#[allow(non_camel_case_types)]
///Alias to C's `int` type
pub type int = i32;

///Converts C string to Rust's, verifying it is UTF-8
///
///It is UB to pass non-C string as it requires \0
pub unsafe fn c_str_to_rust(ptr: *const u8) -> Result<&'static str, core::str::Utf8Error> {
    let len = strlen(ptr as *const i8);
    let parts = core::slice::from_raw_parts(ptr, len);
    core::str::from_utf8(parts)
}

///Converts C string to Rust's one assuming it is UTF-8
///
///It is UB to pass non-C string as it requires \0
pub unsafe fn c_str_to_rust_unchecked(ptr: *const u8) -> &'static str {
    let len = strlen(ptr as *const i8);
    let parts = core::slice::from_raw_parts(ptr, len);
    core::str::from_utf8_unchecked(parts)
}

#[cfg(any(windows, unix, target_env = "wasi", target_os = "wasi", target_os = "vxworks", target_os = "fuchsia"))]
#[doc(hidden)]
#[cold]
#[inline(never)]
pub unsafe fn invalid_cli_args_error() -> int {
    #[cfg_attr(all(windows, target_env="msvc"), link(name="legacy_stdio_definitions", kind="dylib"))]
    extern "C" {
        pub fn printf(format: *const i8, ...) -> int;
    }

    printf(c_lit!("Unable to use non-utf8 arguments\n").as_ptr() as _);
    1
}

#[cfg(not(any(windows, unix, target_env = "wasi", target_os = "wasi", target_os = "vxworks", target_os = "fuchsia")))]
#[doc(hidden)]
#[cold]
#[inline(never)]
pub unsafe fn invalid_cli_args_error() -> int {
    panic!("Unable to use non-utf8 arguments");
}

///Creates C compatible string that ends with null character
#[macro_export]
macro_rules! c_lit {
    ($e:expr) => {
        core::concat!($e, "\0")
    };
    ($($e:tt)+) => {
        core::concat!($($e)+, "\0")
    };
}

///Declares main function with C like signature, passing CLI arugments via `Args`
///
///## Arguments
///
///- `runner` - Function name to accepts `Args` instance
///- `int` - Integer type used for `argc` and return value. In C it is `int` and therefore by default macro uses `$crate::int`.
///
///## Return value
///
///C++ `main` expects integer return value, therefore it automatically converts to `int` type
///
///## Panic
///
///In case of supplied command line arguments contain non-UTF8 string, then terminates.
///
///If `printf` is avalable then it prints to `stdout` and exits with return code 1
///Otherwise it panics.
///
///## Usage
///
/// Default `int` type`
///
///```rust
///#![no_main]
///
///fn my_main(args: c_ffi::Args) -> bool {
///    match args.into_iter().count() {
///        0 => true,
///        _ => false,
///    }
///}
///
///c_ffi::c_main!(my_main);
/////In case you'd like to specify return type yourself
/////c_ffi::c_main!(my_main -> i32);
#[macro_export]
macro_rules! c_main {
    ($runner:ident) => {
        use $crate::int;
        $crate::c_main!($runner -> int);
    };
    ($runner:ident -> $int:ident) => {
        #[no_mangle]
        pub unsafe extern fn main(argc: $int, argv: *const *const u8) -> $int {
            match $crate::Args::new(argc as isize, argv) {
                Ok(args) => $runner(args).into(),
                Err(_) => $crate::invalid_cli_args_error(),
            }
        }
    }
}