getopt_long/
getopt_long.rs

1// -- getopt_long.rs --
2
3use {
4    crate::my_glibc,
5    std::{
6        collections::HashMap,
7        env,
8        ffi::{CStr, CString, NulError},
9        os::raw::{c_char, c_int},
10    },
11};
12
13// --
14
15#[derive(Debug)]
16pub enum OptError {
17    InvalidOption(String),
18    MissingOptionArgument(String),
19    InvalidOptind(i32),
20}
21
22impl std::error::Error for OptError {}
23
24impl std::fmt::Display for OptError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(
27            f,
28            "{}",
29            match self {
30                OptError::InvalidOption(s) => format!("invalid option: {}", s),
31                OptError::MissingOptionArgument(s) => format!("missing option argument: {}", s),
32                OptError::InvalidOptind(i) => format!("invalid optind: {}", i),
33            }
34        )
35    }
36}
37
38pub type OptResult<T> = Result<T, Box<dyn std::error::Error>>;
39
40// --
41
42#[repr(i32)]
43pub enum HasArg {
44    NoArgument = 0,
45    RequiredArgument,
46    OptionalArgument,
47}
48
49pub struct Opt {
50    long_name: Option<CString>,
51    short_name: Option<char>,
52    has_arg: HasArg,
53    desc: String,
54}
55impl Opt {
56    pub fn new(
57        long_name: Option<String>,
58        short_name: Option<char>,
59        has_arg: HasArg,
60        desc: &str,
61    ) -> Result<Self, NulError> {
62        Ok(Self {
63            long_name: long_name.map(|v| CString::new(v)).transpose()?,
64            short_name,
65            has_arg,
66            desc: desc.to_owned(),
67        })
68    }
69    fn optstring(&self) -> Option<String> {
70        self.short_name.map(|v| {
71            let mut s = String::with_capacity(3);
72            s.push(v);
73            match self.has_arg {
74                HasArg::NoArgument => {}
75                HasArg::OptionalArgument => {
76                    s.push_str("::");
77                }
78                HasArg::RequiredArgument => s.push(':'),
79            }
80            s
81        })
82    }
83    fn long_option(&self) -> Option<my_glibc::LongOption> {
84        self.long_name.as_ref().map(|v| my_glibc::LongOption {
85            name: v.as_ptr(),
86            has_arg: match self.has_arg {
87                HasArg::NoArgument => my_glibc::LongOption::NO_ARGUMENT,
88                HasArg::RequiredArgument => my_glibc::LongOption::REQUIRED_ARGUMENT,
89                HasArg::OptionalArgument => my_glibc::LongOption::OPTIONAL_ARGUMENT,
90            },
91            flag: std::ptr::null_mut(),
92            val: 0,
93        })
94    }
95}
96impl std::fmt::Display for Opt {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        write!(
99            f,
100            "{:<2}{:2}{:<16}{:<10} {}",
101            self.short_name
102                .map(|v| format!("-{}", v))
103                .unwrap_or(String::new()),
104            if self.short_name.is_none() || self.long_name.is_none() {
105                ""
106            } else {
107                ", "
108            },
109            self.long_name
110                .as_ref()
111                .map(|v| format!("--{}", v.to_str().unwrap_or("")))
112                .unwrap_or(String::new()),
113            match self.has_arg {
114                HasArg::NoArgument => "",
115                HasArg::OptionalArgument => "[=Arg]",
116                HasArg::RequiredArgument => " =Arg ",
117            },
118            self.desc
119        )
120    }
121}
122
123#[derive(Debug)]
124pub struct Arguments {
125    pub args: HashMap<String, String>,
126    pub operands: Vec<String>,
127}
128impl std::fmt::Display for Arguments {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        for a in self.args.iter() {
131            writeln!(f, "{}: {}", a.0, a.1)?;
132        }
133        for o in self.operands.iter() {
134            writeln!(f, "operand: {}", o)?;
135        }
136        Ok(())
137    }
138}
139
140// --
141
142pub fn getopt_long(opts: &[Opt]) -> OptResult<Arguments> {
143    let mut optstring = ":".to_owned();
144    let mut longopts = Vec::new();
145    opts.iter().for_each(|v| {
146        if let Some(s) = v.optstring() {
147            optstring.push_str(&s);
148        }
149        if let Some(lo) = v.long_option() {
150            longopts.push(lo);
151        }
152    });
153    let optstring = CString::new(optstring)?;
154    longopts.push(my_glibc::LongOption {
155        name: std::ptr::null(),
156        has_arg: 0,
157        flag: std::ptr::null_mut(),
158        val: 0,
159    });
160    let mut longindex: c_int = 0;
161
162    // create a vector of zero terminated strings
163    let mut argv = Vec::new();
164    for v in env::args() {
165        argv.push(CString::new(v)?);
166    }
167    // convert the strings to raw pointers
168    let argv = argv
169        .into_iter()
170        .map(|arg| arg.into_raw())
171        .collect::<Vec<*mut c_char>>();
172    let mut argv = recover_guard::RecoverGuard::new(argv, |a| {
173        // 回收传入c函数中的资源
174        a.into_iter().for_each(|v| unsafe {
175            CString::from_raw(v);
176        })
177    });
178    let argc = argv.len() as c_int;
179
180    let mut args = HashMap::new();
181    loop {
182        match unsafe {
183            my_glibc::getopt_long(
184                argc,
185                argv.as_ptr(),
186                optstring.as_ptr(),
187                longopts.as_ptr(),
188                &mut longindex,
189            )
190        } {
191            -1 => break,
192            0 => {
193                let longopt = &longopts[longindex as usize];
194                args.insert(
195                    unsafe { CStr::from_ptr(longopt.name) }
196                        .to_str()?
197                        .to_string(),
198                    if unsafe { my_glibc::optarg.is_null() } {
199                        String::new()
200                    } else {
201                        unsafe { CStr::from_ptr(my_glibc::optarg) }
202                            .to_str()?
203                            .to_string()
204                    },
205                );
206            }
207            i => {
208                let optopt = unsafe {
209                    CStr::from_ptr(
210                        *argv
211                            .get(my_glibc::optind as usize - 1)
212                            .ok_or(OptError::InvalidOptind(my_glibc::optind))?,
213                    )
214                }
215                .to_str()?
216                .to_string();
217                if i == b'?' as c_int {
218                    Err(OptError::InvalidOption(optopt.clone()))?;
219                }
220                if i == b':' as c_int {
221                    Err(OptError::MissingOptionArgument(optopt.clone()))?;
222                }
223
224                args.insert(
225                    CStr::from_bytes_with_nul(&[i as u8, 0])?
226                        .to_str()?
227                        .to_string(),
228                    if unsafe { my_glibc::optarg.is_null() } {
229                        String::new()
230                    } else {
231                        unsafe { CStr::from_ptr(my_glibc::optarg) }
232                            .to_str()?
233                            .to_string()
234                    },
235                );
236            }
237        }
238    }
239
240    let mut operands = Vec::new();
241    for v in argv.split_off(unsafe { my_glibc::optind } as usize) {
242        operands.push(unsafe { CStr::from_ptr(v) }.to_str()?.to_string());
243    }
244
245    Ok(Arguments { args, operands })
246}
247
248pub fn usage(name: &str, desc: &str, version: &str, opts: &[Opt]) {
249    println!(
250        r#"Description:
251    {}
252Version:
253    {}
254Usage:
255    {} [options [args]] [operands]
256Options:"#,
257        desc, version, name
258    );
259    opts.iter().for_each(|v| println!("    {}", v));
260    println!();
261}
262
263// --
264
265mod recover_guard {
266    use std::{
267        boxed::Box,
268        ops::{Deref, DerefMut, Drop, FnMut},
269    };
270
271    pub(crate) struct RecoverGuard<T, F: FnMut(T)> {
272        data: Option<T>,
273        func: Box<F>,
274    }
275
276    impl<T: Sized, F: FnMut(T)> RecoverGuard<T, F> {
277        pub fn new(data: T, func: F) -> RecoverGuard<T, F> {
278            RecoverGuard {
279                data: Some(data),
280                func: Box::new(func),
281            }
282        }
283    }
284
285    impl<T, F: FnMut(T)> Deref for RecoverGuard<T, F> {
286        type Target = T;
287
288        fn deref(&self) -> &T {
289            &self.data.as_ref().expect("the data should be here")
290        }
291    }
292
293    impl<T, F: FnMut(T)> DerefMut for RecoverGuard<T, F> {
294        fn deref_mut(&mut self) -> &mut T {
295            self.data.as_mut().expect("the data should be here")
296        }
297    }
298
299    impl<T, F: FnMut(T)> Drop for RecoverGuard<T, F> {
300        fn drop(&mut self) {
301            // let mut data: Option<T> = None;
302            // std::mem::swap(&mut data, &mut self.data);
303            let data = self.data.take();
304            let ref mut f = self.func;
305            f(data.expect("the data is here until the drop"));
306        }
307    }
308}