2024-04-02 02:37:35 +00:00
|
|
|
|
/*
|
2024-06-06 07:03:01 +00:00
|
|
|
|
* Copyright (c) 2023–2024 Emma Tebibyte <emma@tebibyte.media>
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
2024-04-02 02:37:35 +00:00
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify it under
|
2024-06-06 07:03:01 +00:00
|
|
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
2024-04-02 02:37:35 +00:00
|
|
|
|
* 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
|
2024-06-06 07:03:01 +00:00
|
|
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
2024-04-02 02:37:35 +00:00
|
|
|
|
* details.
|
|
|
|
|
*
|
2024-06-06 07:03:01 +00:00
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
2024-04-02 02:37:35 +00:00
|
|
|
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-04-12 02:33:42 +00:00
|
|
|
|
use std::ffi::{ c_int, c_char, CString, CStr };
|
2024-04-02 02:37:35 +00:00
|
|
|
|
|
2024-06-04 20:28:19 +00:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 04:18:13 +00:00
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub enum OptError {
|
|
|
|
|
MissingArg(String),
|
|
|
|
|
UnknownOpt(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
2024-04-02 02:37:35 +00:00
|
|
|
|
pub struct Opt {
|
2024-06-23 04:30:30 +00:00
|
|
|
|
arg: Option<String>, /* option argument */
|
|
|
|
|
ind: *mut i32, /* option index */
|
|
|
|
|
opt: Result<String, OptError>, /* option option */
|
2024-04-02 02:37:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 17:54:55 +00:00
|
|
|
|
impl Opt {
|
2024-06-23 04:18:13 +00:00
|
|
|
|
pub fn arg(&self) -> Option<String> { self.arg.clone() }
|
2024-06-05 17:54:55 +00:00
|
|
|
|
|
2024-06-23 04:18:13 +00:00
|
|
|
|
/* sets optarg if default is desired */
|
|
|
|
|
pub fn arg_or(&self, default: impl std::fmt::Display) -> String {
|
|
|
|
|
default.to_string()
|
|
|
|
|
}
|
2024-06-05 17:54:55 +00:00
|
|
|
|
|
2024-06-23 04:30:30 +00:00
|
|
|
|
/* makes matching the output of this method more bearable */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
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 } }
|
|
|
|
|
|
2024-06-23 04:30:30 +00:00
|
|
|
|
/* this is patently terrible and is only happening because I’m stubborn */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } }
|
2024-04-02 02:37:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-12 05:51:52 +00:00
|
|
|
|
/* function signature */
|
2024-04-02 02:37:35 +00:00
|
|
|
|
pub trait GetOpt {
|
2024-06-23 04:18:13 +00:00
|
|
|
|
fn getopt(&self, optstring: &str) -> Option<Opt>;
|
2024-04-02 02:37:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GetOpt for Vec<String> {
|
2024-06-23 04:18:13 +00:00
|
|
|
|
fn getopt(&self, optstring: &str) -> Option<Opt> {
|
2024-04-12 02:33:42 +00:00
|
|
|
|
let c_strings: Vec<_> = self
|
2024-04-12 02:21:01 +00:00
|
|
|
|
.iter()
|
|
|
|
|
.cloned()
|
2024-06-23 04:18:13 +00:00
|
|
|
|
.map(|x| CString::new(x).unwrap().into_raw())
|
2024-04-12 02:33:42 +00:00
|
|
|
|
.collect();
|
2024-04-02 02:37:35 +00:00
|
|
|
|
|
2024-06-23 04:30:30 +00:00
|
|
|
|
/* god knows what this does */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
let boxed = Box::into_raw(c_strings.into_boxed_slice());
|
2024-06-28 16:22:20 +00:00
|
|
|
|
let argv = boxed as *const *mut c_char;
|
2024-06-23 04:18:13 +00:00
|
|
|
|
|
2024-06-23 04:30:30 +00:00
|
|
|
|
/* operations are separated out so that everything lives long enough */
|
2024-04-12 05:51:52 +00:00
|
|
|
|
let opts = CString::new(optstring).unwrap().into_raw();
|
2024-04-12 02:33:42 +00:00
|
|
|
|
let len = self.len() as c_int;
|
|
|
|
|
|
2024-06-04 20:28:19 +00:00
|
|
|
|
unsafe {
|
2024-06-23 04:18:13 +00:00
|
|
|
|
let ret = match getopt(len, argv, opts) {
|
2024-04-02 02:37:35 +00:00
|
|
|
|
/* From getopt(3p):
|
|
|
|
|
*
|
2024-06-23 04:18:13 +00:00
|
|
|
|
* The getopt() function shall return the next option character
|
2024-04-02 02:37:35 +00:00
|
|
|
|
* specified on the command line.
|
|
|
|
|
*
|
|
|
|
|
* A <colon> (':') shall be returned if getopt() detects a
|
|
|
|
|
* missing argument and the first character of optstring was a
|
|
|
|
|
* <colon> (':').
|
|
|
|
|
*
|
|
|
|
|
* A <question-mark> ('?') 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 <colon> (':').
|
|
|
|
|
*
|
|
|
|
|
* Otherwise, getopt() shall return -1 when all command line
|
|
|
|
|
* options are parsed. */
|
2024-06-23 04:30:30 +00:00
|
|
|
|
58 => { /* ASCII ':' */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
Some(Opt {
|
|
|
|
|
arg: None,
|
|
|
|
|
ind: std::ptr::addr_of_mut!(optind),
|
|
|
|
|
/* error containing option */
|
|
|
|
|
opt: Err(OptError::MissingArg(optopt.to_string())),
|
|
|
|
|
})
|
2024-06-05 17:54:55 +00:00
|
|
|
|
},
|
2024-06-23 04:30:30 +00:00
|
|
|
|
63 => { /* ASCII '?' */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
Some(Opt {
|
|
|
|
|
arg: None,
|
|
|
|
|
ind: std::ptr::addr_of_mut!(optind),
|
|
|
|
|
/* error containing option */
|
|
|
|
|
opt: Err(OptError::UnknownOpt(optopt.to_string())),
|
|
|
|
|
})
|
2024-06-05 17:54:55 +00:00
|
|
|
|
},
|
2024-04-02 02:37:35 +00:00
|
|
|
|
/* 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. */
|
2024-04-12 02:33:42 +00:00
|
|
|
|
-1 => return None,
|
2024-04-13 23:11:04 +00:00
|
|
|
|
opt => {
|
2024-06-23 04:18:13 +00:00
|
|
|
|
let arg: Option<String>;
|
|
|
|
|
|
|
|
|
|
if optarg.is_null() { arg = None; }
|
|
|
|
|
else {
|
|
|
|
|
arg = Some(CStr::from_ptr(optarg)
|
|
|
|
|
.to_string_lossy()
|
|
|
|
|
.into_owned());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(Opt {
|
2024-06-23 04:30:30 +00:00
|
|
|
|
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 */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
opt: Ok((opt as u8 as char).to_string()),
|
|
|
|
|
})
|
2024-04-13 23:11:04 +00:00
|
|
|
|
},
|
2024-06-23 04:18:13 +00:00
|
|
|
|
};
|
|
|
|
|
|
2024-06-23 04:30:30 +00:00
|
|
|
|
/* delloc argv (something online said I should do this) */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
let _ = Box::from_raw(boxed);
|
|
|
|
|
return ret;
|
2024-04-12 02:33:42 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-02 02:37:35 +00:00
|
|
|
|
}
|
2024-06-23 04:18:13 +00:00
|
|
|
|
|
2024-06-23 04:30:30 +00:00
|
|
|
|
/* tests (good) */
|
2024-06-23 04:18:13 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use GetOpt;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn testing() {
|
|
|
|
|
let argv: Vec<String> = ["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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|