1#![cfg_attr(feature = "os_str_bytes", feature(os_str_bytes))]
2
3#![allow(clippy::tabs_in_doc_comments)] use std::ffi::OsStr;
62use std::fmt::{Debug, Formatter};
63use std::hash::{Hash, Hasher};
64use std::ops::{Deref, DerefMut};
65use getargs::Argument;
66
67mod utf8_bs;
68
69#[cfg(test)]
70mod test;
71
72#[repr(transparent)]
78pub struct OsArgument(pub OsStr);
79
80impl<'a> From<&'a OsStr> for &'a OsArgument {
81 fn from(from: &'a OsStr) -> Self {
82 unsafe { std::mem::transmute(from) }
84 }
85}
86
87impl<'a> From<&'a OsArgument> for &'a OsStr {
88 fn from(from: &'a OsArgument) -> Self {
89 unsafe { std::mem::transmute(from) }
91 }
92}
93
94#[cfg(feature = "os_str_bytes")]
95impl From<&str> for &OsArgument {
96 fn from(from: &str) -> Self {
97 Self::from(unsafe { OsStr::from_os_str_bytes_unchecked(from.as_bytes()) })
98 }
99}
100
101impl OsArgument {
102 fn as_bytes(&self) -> &[u8] {
103 #[cfg(windows)]
104 unsafe { std::mem::transmute(&self.0) }
108
109 #[cfg(not(windows))]
110 std::os::unix::ffi::OsStrExt::as_bytes(&self.0)
112 }
113
114 fn from_bytes(bytes: &[u8]) -> &Self {
115 #[cfg(windows)]
116 unsafe { std::mem::transmute(bytes) }
118
119 #[cfg(not(windows))]
120 <&Self as From<&OsStr>>::from(std::os::unix::ffi::OsStrExt::from_bytes(bytes))
122 }
123}
124
125impl Deref for OsArgument {
126 type Target = OsStr;
127
128 fn deref(&self) -> &Self::Target { &self.0 }
129}
130
131impl DerefMut for OsArgument {
132 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
133}
134
135impl PartialEq for OsArgument {
136 fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
137}
138
139#[cfg(feature = "os_str_bytes")]
140impl PartialEq<&str> for &OsArgument {
141 fn eq(&self, other: &str) -> bool {
142 self == other.into()
143 }
144}
145
146#[cfg(feature = "os_str_bytes")]
147impl PartialEq<&OsArgument> for &str {
148 fn eq(&self, other: &OsArgument) -> bool {
149 self.into() == other
150 }
151}
152
153impl Eq for OsArgument {}
154
155impl Debug for OsArgument {
156 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) }
157}
158
159impl Hash for OsArgument {
160 fn hash<H: Hasher>(&self, state: &mut H) { self.0.hash(state) }
161}
162
163#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
166pub enum ShortOpt {
167 Codepoint(u32),
170
171 Byte(u8)
175}
176
177impl From<char> for ShortOpt {
178 fn from(codepoint: char) -> Self {
179 Self::Codepoint(codepoint as u32)
180 }
181}
182
183impl From<u32> for ShortOpt {
184 fn from(codepoint: u32) -> Self {
185 Self::Codepoint(codepoint)
186 }
187}
188
189impl From<u8> for ShortOpt {
190 fn from(byte: u8) -> Self {
191 Self::Byte(byte)
192 }
193}
194
195impl Argument for &'_ OsArgument {
196 type ShortOpt = ShortOpt;
197
198 #[inline]
199 fn ends_opts(self) -> bool {
200 self.as_bytes() == b"--"
201 }
202
203 #[inline]
204 fn parse_long_opt(self) -> Option<(Self, Option<Self>)> {
205 self.as_bytes().parse_long_opt().map(|(name, value)| (OsArgument::from_bytes(name), value.map(OsArgument::from_bytes)))
207 }
208
209 #[inline]
210 fn parse_short_cluster(self) -> Option<Self> {
211 self.as_bytes().parse_short_cluster().map(OsArgument::from_bytes)
213 }
214
215 #[cfg_attr(not(windows), inline)] fn consume_short_opt(self) -> (Self::ShortOpt, Option<Self>) {
217 #[cfg(windows)] {
218 let mut iter = self.as_bytes().iter();
220 let codepoint = unsafe { utf8_bs::next_code_point(&mut iter).unwrap_unchecked() };
221 (ShortOpt::Codepoint(codepoint), Some(iter.as_slice()).filter(|&slice| !slice.is_empty()).map(OsArgument::from_bytes))
222 }
223
224 #[cfg(not(windows))] {
225 let bytes = self.as_bytes();
226
227 let first = unsafe { *bytes.get_unchecked(0) };
229 let encoded_length = utf8_bs::utf8_char_width(first);
230
231 let (codepoint, rest) = if let Some(Ok(Some(char))) = bytes.get(0..encoded_length).map(|slice| std::str::from_utf8(slice).map(|str| str.chars().next())) {
232 (ShortOpt::Codepoint(char as u32), unsafe { bytes.get_unchecked(encoded_length..) })
234 } else {
235 (ShortOpt::Byte(first), unsafe { bytes.get_unchecked(1..) })
237 };
238
239 (codepoint, Some(OsArgument::from_bytes(rest)).filter(|s| !s.is_empty()))
240 }
241 }
242
243 #[inline]
244 fn consume_short_val(self) -> Self {
245 self
246 }
247}
248
249#[macro_export]
255macro_rules! os {
256 ($string:literal) => { <&$crate::OsArgument as From<&::std::ffi::OsStr>>::from(unsafe { std::mem::transmute(str::as_bytes($string as &str)) }) }
257}
258
259#[macro_export]
264macro_rules! osb {
265 ($bytes:literal) => { <&$crate::OsArgument as From<&::std::ffi::OsStr>>::from(std::mem::transmute($bytes as &[u8])) }
266}