argtokens 0.5.0

Command-line argument parser, supporting POSIX+GNU syntax and no-std, no-alloc usage
Documentation
// Copyright (C) 2021-2022, 2025  Johannes Sasongko <johannes sasongko org>
// SPDX-License-Identifier: MPL-2.0
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Various optional helpers that may be useful.
//!
//! The most interesting thing here is [`OsStrExt::parse_str`].

#[cfg(feature = "std")]
use std::{
	borrow::Cow,
	ffi::{OsStr, OsString},
	str::FromStr,
};

#[cfg(windows)]
type CharBase = u16;
#[cfg(not(windows))]
type CharBase = u8;

/// Alias for the platform's native character type.
///
/// This is [`u16`] on Windows, [`u8`] elsewhere.
pub type PlatChar = CharBase;

/// Creates [`PlatChar`] from an ASCII character constant.
///
/// This is equivalent to casting the character to [`PlatChar`] and is only
/// provided for consistency with [`plat_str`].
///
/// # Examples
///
/// ```
/// # use argtokens::util::*;
/// const OPT: PlatChar = plat_char!('o');
/// assert_eq!(OPT, 'o' as PlatChar);
/// ```
///
/// ```compile_fail,E0080
/// # use argtokens::*;
/// // Compile error - not an ASCII character:
/// plat_char!('\u{E2}');
/// ```
#[doc(hidden)]
#[macro_export]
macro_rules! plat_char {
	($ch:expr) => {{
		// We store the result in a const so that errors happen at compile time.
		const RESULT: $crate::util::PlatChar = {
			const CH: ::core::primitive::char = $ch;
			assert!(CH.is_ascii(), "argument must be an ASCII character");
			CH as $crate::util::PlatChar
		};
		RESULT
	}};
}
// The doc(hidden)+doc(inline) trick is because Rust exports macros at the crate
// root, but we want this in a module instead.
#[doc(inline)]
pub use crate::plat_char;

/// Creates <code>&[[PlatChar]]</code> from an ASCII string constant.
///
/// This is equivalent to casting each character in the string to [`PlatChar`].
///
/// # Examples
///
/// ```
/// # use argtokens::util::*;
/// const OPT: &[PlatChar] = plat_str!("opt");
/// assert_eq!(OPT, &['o' as PlatChar, 'p' as PlatChar, 't' as PlatChar]);
/// ```
///
/// ```compile_fail,E0080
/// # use argtokens::*;
/// // Compile error - not an ASCII string:
/// plat_str!("\u{E2}");
/// ```
#[doc(hidden)]
#[macro_export]
macro_rules! plat_str {
	($str:expr) => {{
		// We store the result in a const so that errors happen at compile time.
		const RESULT: &[$crate::util::PlatChar] = &{
			const STR: &::core::primitive::str = $str;
			assert!(STR.is_ascii(), "argument must be an ASCII string");
			let mut result = [0; STR.len()];
			let mut i = 0;
			while i < STR.len() {
				let ch = STR.as_bytes()[i];
				result[i] = ch as $crate::util::PlatChar;
				i += 1;
			}
			result
		};
		RESULT
	}};
}
// The doc(hidden)+doc(inline) trick is because Rust exports macros at the crate
// root, but we want this in a module instead.
#[doc(inline)]
pub use crate::plat_str;

/// Helper extension methods for [`OsStr`].
#[cfg(feature = "std")]
pub trait OsStrExt {
	/// Converts to another type through [`FromStr`].
	///
	/// This function returns `None` if any error is encountered (it discards
	/// error values).
	///
	/// Because the conversion goes through `str`, this is not suitable for
	/// parsing anything that supports invalid character sequences, such as
	/// [`PathBuf`](std::path::PathBuf).
	fn parse_str<T: FromStr>(&self) -> Option<T>;

	/// Converts [`OsStr`] to <code>[Cow]<[[PlatChar]]></code>.
	///
	/// This returns [`Cow::Owned`] on Windows, [`Cow::Borrowed`] elsewhere.
	#[must_use]
	fn to_plat(&self) -> Cow<'_, [PlatChar]>;
}

#[cfg(feature = "std")]
impl OsStrExt for OsStr {
	#[inline]
	fn parse_str<T: FromStr>(&self) -> Option<T> {
		self.to_str().and_then(|s| T::from_str(s).ok())
	}

	#[inline]
	fn to_plat(&self) -> Cow<'_, [PlatChar]> {
		#[cfg(windows)]
		{
			use std::os::windows::ffi::OsStrExt as _;
			Cow::Owned(self.encode_wide().collect())
		}
		#[cfg(not(windows))]
		{
			#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
			use std::os::fortanix_sgx::ffi::OsStrExt as _;
			#[cfg(target_os = "hermit")]
			use std::os::hermit::ffi::OsStrExt as _;
			#[cfg(target_os = "solid_asp3")]
			use std::os::solid::ffi::OsStrExt as _;
			#[cfg(unix)]
			use std::os::unix::ffi::OsStrExt as _;
			#[cfg(target_os = "wasi")]
			use std::os::wasi::ffi::OsStrExt as _;
			#[cfg(target_os = "xous")]
			use std::os::xous::ffi::OsStrExt as _;
			Cow::Borrowed(self.as_bytes())
		}
	}
}

/// Helper extension methods for [`OsString`].
#[cfg(feature = "std")]
pub trait OsStringExt {
	/// Converts <code>&[[PlatChar]]</code> to <code>[Cow]<[OsStr]></code>.
	///
	/// This returns [`Cow::Owned`] on Windows, [`Cow::Borrowed`] elsewhere.
	#[must_use]
	fn from_plat(slice: &[PlatChar]) -> Cow<'_, OsStr>;
}

#[cfg(feature = "std")]
impl OsStringExt for OsString {
	#[inline]
	fn from_plat(slice: &[PlatChar]) -> Cow<'_, OsStr> {
		#[cfg(windows)]
		{
			use std::{ffi::OsString, os::windows::ffi::OsStringExt as _};
			Cow::Owned(OsString::from_wide(slice))
		}
		#[cfg(not(windows))]
		{
			#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
			use std::os::fortanix_sgx::ffi::OsStrExt as _;
			#[cfg(target_os = "hermit")]
			use std::os::hermit::ffi::OsStrExt as _;
			#[cfg(target_os = "solid_asp3")]
			use std::os::solid::ffi::OsStrExt as _;
			#[cfg(unix)]
			use std::os::unix::ffi::OsStrExt as _;
			#[cfg(target_os = "wasi")]
			use std::os::wasi::ffi::OsStrExt as _;
			#[cfg(target_os = "xous")]
			use std::os::xous::ffi::OsStrExt as _;
			Cow::Borrowed(OsStr::from_bytes(slice))
		}
	}
}