/* * Copyright (c) 2023–2024 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero 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; } #[derive(Clone, Debug)] pub enum OptError { MissingArg(String), UnknownOpt(String), } #[derive(Clone, Debug)] pub struct Opt { arg: Option, /* option argument */ ind: *mut i32, /* option index */ opt: Result, /* option option */ } impl Opt { pub fn arg(&self) -> Option { self.arg.clone() } /* sets optarg if default is desired */ pub fn arg_or(&self, default: impl std::fmt::Display) -> String { default.to_string() } /* makes matching the output of this method more bearable */ pub fn opt(&self) -> Result<&str, OptError> { self.opt.as_ref().map(|o| o.as_str()).map_err(OptError::clone) } /* 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. */ pub fn ind(&self) -> usize { unsafe { *self.ind as usize } } /* this is patently terrible and is only happening because I’m stubborn */ pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } } } /* 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(|x| CString::new(x).unwrap().into_raw()) .collect(); /* god knows what this does */ let boxed = Box::into_raw(c_strings.into_boxed_slice()); let argv = boxed as *const *mut c_char; /* operations are separated out so that everything lives long enough */ let opts = CString::new(optstring).unwrap().into_raw(); let len = self.len() as c_int; unsafe { let ret = match getopt(len, argv, 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 => { /* ASCII ':' */ Some(Opt { arg: None, ind: std::ptr::addr_of_mut!(optind), /* error containing option */ opt: Err(OptError::MissingArg(optopt.to_string())), }) }, 63 => { /* ASCII '?' */ Some(Opt { arg: None, ind: std::ptr::addr_of_mut!(optind), /* error containing option */ opt: 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: Option; if optarg.is_null() { arg = None; } else { arg = Some(CStr::from_ptr(optarg) .to_string_lossy() .into_owned()); } Some(Opt { arg, ind: std::ptr::addr_of_mut!(optind), /* I didn’t need to cast this before; I rewrote the * pointer logic and now I do * * I don’t know why this is */ opt: Ok((opt as u8 as char).to_string()), }) }, }; /* delloc argv (something online said I should do this) */ let _ = Box::from_raw(boxed); return ret; } } } /* tests (good) */ #[cfg(test)] mod tests { use GetOpt; #[test] fn testing() { let argv: Vec = ["test", "-b", "-f", "arg", "-o", "arg"] .iter() .map(|s| s.to_string()) .collect(); while let Some(opt) = argv.getopt(":abf:o:") { match opt.opt() { Ok("a") => assert_eq!(opt.ind(), 1), Ok("b") => assert_eq!(opt.ind(), 2), Ok("f") | Ok("o") => { assert_eq!(opt.arg(), Some("arg".into())); }, _ => assert!(false), }; } if let Some(opt) = argv.getopt("abc:") { opt.clone().set_ind(1); assert_eq!(opt.ind(), 1); } } }