harakit/src/libgetopt.rs

203 lines
5.7 KiB
Rust
Raw Normal View History

2024-04-02 02:37:35 +00:00
/*
2024-06-06 07:03:01 +00:00
* Copyright (c) 20232024 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/.
*/
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 {
arg: Option<String>, /* option argument */
ind: *mut i32, /* option index */
opt: Result<String, OptError>, /* option option */
2024-04-02 02:37:35 +00:00
}
impl Opt {
2024-06-23 04:18:13 +00:00
pub fn arg(&self) -> Option<String> { self.arg.clone() }
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()
}
/* 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 } }
/* this is patently terrible and is only happening because Im 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> {
let c_strings: Vec<_> = self
.iter()
.cloned()
2024-06-23 04:18:13 +00:00
.map(|x| CString::new(x).unwrap().into_raw())
.collect();
2024-04-02 02:37:35 +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());
let argv = boxed as *const *mut c_char;
2024-06-23 04:18:13 +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();
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. */
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())),
})
},
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-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. */
-1 => return None,
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 {
arg,
ind: std::ptr::addr_of_mut!(optind),
/* I didnt need to cast this before; I rewrote the
* pointer logic and now I do
*
* I dont know why this is */
2024-06-23 04:18:13 +00:00
opt: Ok((opt as u8 as char).to_string()),
})
},
2024-06-23 04:18:13 +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-02 02:37:35 +00:00
}
2024-06-23 04:18:13 +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);
}
}
}