/* * Copyright (c) 2024 Emma Tebibyte * SPDX-License-Identifier: LGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ use std::ffi::{ c_int, c_char, CString, CStr }; /* binding to getopt(3p) */ extern "C" { static mut optarg: *mut c_char; static mut _opterr: c_int; static mut optind: c_int; static mut optopt: c_int; fn getopt( ___argc: c_int, ___argv: *const *mut c_char, __shortopts: *const c_char, ) -> c_int; } /* From getopt(3p): * * The variable optind is the index of the next element of the argv[] * vector to be processed. It shall be initialized to 1 by the system, and * getopt() shall update it when it finishes with each element of argv[]. * If the application sets optind to zero before calling getopt(), the * behavior is unspecified. When an element of argv[] contains multiple * option characters, it is unspecified how getopt() determines which * options have already been processed. * * This API can be utilized using unsafe blocks and dereferencing: * * use getopt::OPTIND; * * unsafe { while *OPTIND < 5 { * println!("{}", *OPTIND); // 1..4 * *OPTIND += 1; * }} */ pub static mut OPTIND: *mut i32 = unsafe { std::ptr::addr_of_mut!(optind) }; pub struct Opt { pub arg: Option, pub ind: i32, pub opt: String, } pub enum OptError { MissingArg(String), UnknownOpt(String), } /* function signature */ pub trait GetOpt { fn getopt(&self, optstring: &str) -> Option>; } impl GetOpt for Vec { fn getopt(&self, optstring: &str) -> Option> { let c_strings: Vec<_> = self .iter() .cloned() .map(CString::new) .map(Result::unwrap) .collect(); /* these operations must be separated out into separate operations so * the CStrings can live long enough */ let argv: Vec<_> = c_strings.iter().map(|x| x.as_ptr()).collect(); let argv_ptr = argv.as_ptr() as *const *mut c_char; let opts = CString::new(optstring).unwrap().into_raw(); let len = self.len() as c_int; unsafe { match getopt(len, argv_ptr, opts) { /* From getopt(3p): * * The getopt() function shall return the next option character * specified on the command line. * * A (':') shall be returned if getopt() detects a * missing argument and the first character of optstring was a * (':'). * * A ('?') shall be returned if getopt() * encounters an option character not in optstring or detects a * missing argument and the first character of optstring was not * a (':'). * * Otherwise, getopt() shall return -1 when all command line * options are parsed. */ 58 => { /* numerical ASCII value for ':' */ Some(Err(OptError::MissingArg(optopt.to_string()))) }, 63 => { /* numerical ASCII value for '?' */ Some(Err(OptError::UnknownOpt(optopt.to_string()))) }, /* From getopt(3p): * * If, when getopt() is called: * * argv[optind] is a null pointer * *argv[optind] is not the character - * argv[optind] points to the string "-" * * getopt() shall return -1 without changing optind. If: * * argv[optind] points to the string "--" * * getopt() shall return -1 after incrementing optind. */ -1 => return None, opt => { let arg = CStr::from_ptr(optarg) .to_string_lossy() .into_owned(); Some(Ok(Opt { arg: Some(arg), /* opt argument */ ind: optind, /* opt index */ opt: opt.to_string(), /* option itself */ })) }, } } } }