Skip to main content

unlab_gpu/
getopts.rs

1//
2// Copyright (c) 2025-2026 Ɓukasz Szpakowski
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7//
8//!A module of getopts.
9use std::collections::BTreeMap;
10use std::sync::Arc;
11use std::sync::RwLock;
12use getopts::HasArg;
13use getopts::Occur;
14use getopts::Options;
15use crate::env::*;
16use crate::error::*;
17use crate::interp::*;
18use crate::utils::*;
19use crate::value::*;
20
21fn create_options<F>(value: &Value, err_msg: &str, mut f: F) -> Result<Options>
22    where F: FnMut(String)
23{
24    match value {
25        Value::Ref(object) => {
26            let object_g = rw_lock_read(object)?;
27            match &*object_g {
28                MutObject::Array(opt_values) => {
29                    let mut opts = Options::new();
30                    for opt_value in opt_values {
31                        match opt_value {
32                            Value::Ref(opt_object) => {
33                                let opt_object_g = rw_lock_read(opt_object)?;
34                                match &*opt_object_g {
35                                    MutObject::Array(opt_arg_values) => {
36                                        if opt_arg_values.len() < 3 || opt_arg_values.len() > 6 {
37                                            return Err(Error::Interp(String::from("invalid number of elements for option")))
38                                        }
39                                        let short_name = match opt_arg_values.get(0) {
40                                            Some(opt_arg_value) => {
41                                                let mut s = String::new();
42                                                match format!("{}", opt_arg_value).chars().next() {
43                                                    Some(c) => s.push(c),
44                                                    None => (),                                                    
45                                                }
46                                                s
47                                            },
48                                            None => return Err(Error::Interp(String::from("no short name for option"))),
49                                        };
50                                        let long_name = match opt_arg_values.get(1) {
51                                            Some(opt_arg_value) => format!("{}", opt_arg_value),
52                                            None => return Err(Error::Interp(String::from("no long name for option"))),
53                                        };
54                                        let desc = match opt_arg_values.get(2) {
55                                            Some(opt_arg_value) => format!("{}", opt_arg_value),
56                                            None => return Err(Error::Interp(String::from("no description for option"))),
57                                        };
58                                        let hint = match opt_arg_values.get(3) {
59                                            Some(opt_arg_value) => format!("{}", opt_arg_value),
60                                            None => String::new(),
61                                        };
62                                        let hasarg = match opt_arg_values.get(4) {
63                                            Some(opt_arg_value) => {
64                                                let s = format!("{}", opt_arg_value);
65                                                if s == String::from("yes") {
66                                                    HasArg::Yes
67                                                } else if s == String::from("no") {
68                                                    HasArg::No
69                                                } else if s == String::from("maybe") {
70                                                    HasArg::Maybe
71                                                } else {
72                                                    return Err(Error::Interp(String::from("invalid has argument for option")));
73                                                }
74                                            },
75                                            None => {
76                                                if !hint.is_empty() {
77                                                    HasArg::Yes
78                                                } else {
79                                                    HasArg::No
80                                                }
81                                            },
82                                        };
83                                        let occur = match opt_arg_values.get(5) {
84                                            Some(opt_arg_value) => {
85                                                let s = format!("{}", opt_arg_value);
86                                                if s == String::from("req") {
87                                                    Occur::Req
88                                                } else if s == String::from("optional") {
89                                                    Occur::Optional
90                                                } else if s == String::from("multi") {
91                                                    Occur::Multi
92                                                } else {
93                                                    return Err(Error::Interp(String::from("invalid has argument for option")));
94                                                }
95                                            },
96                                            None => Occur::Optional,
97                                        };
98                                        let name = if !long_name.is_empty() {
99                                            long_name.clone()
100                                        } else {
101                                            short_name.clone()
102                                        };
103                                        if name.is_empty() {
104                                            return Err(Error::Interp(String::from("no name for option")))
105                                        }
106                                        f(name);
107                                        opts.opt(short_name.as_str(), long_name.as_str(), desc.as_str(), hint.as_str(), hasarg, occur);
108                                    },
109                                    _ => return Err(Error::Interp(String::from("invalid option"))),
110                                }
111                            },
112                            _ => return Err(Error::Interp(String::from("invalid option"))),
113                        }
114                    }
115                    Ok(opts)
116                },
117                _ => Err(Error::Interp(String::from(err_msg))),
118            }
119        },
120        _ => Err(Error::Interp(String::from(err_msg))),
121    }
122}
123
124pub fn create_args(value: &Value, err_msg: &str) -> Result<Vec<String>>
125{
126    match value {
127        Value::Ref(object) => {
128            let object_g = rw_lock_read(object)?;
129            match &*object_g {
130                MutObject::Array(arg_values) => Ok(arg_values.iter().map(|v| format!("{}", v)).collect()),
131                _ => Err(Error::Interp(String::from(err_msg))),
132            }
133        },
134        _ => Err(Error::Interp(String::from(err_msg))),
135    }
136}
137
138/// A `getopts` built-in function. 
139pub fn getopts(_interp: &mut Interp, env: &mut Env, arg_values: &[Value]) -> Result<Value>
140{
141    if arg_values.len() < 1 || arg_values.len() > 2 {
142        return Err(Error::Interp(String::from("invalid number of arguments")));
143    }
144    let mut names: Vec<String> = Vec::new();
145    let (opts, args) = match (arg_values.get(0), arg_values.get(1)) {
146        (Some(opt_value), None) => {
147            let tmp_opts = create_options(&opt_value, "unsupported types for getopts", |s| names.push(s))?;
148            let shared_env_g = rw_lock_read(env.shared_env())?;
149            (tmp_opts, shared_env_g.args().to_vec())
150        },
151        (Some(opt_value), Some(arg_value)) => {
152            let tmp_opts = create_options(&opt_value, "unsupported types for getopts", |s| names.push(s))?;
153            let tmp_args = create_args(&arg_value, "unsupported types for getopts")?;
154            (tmp_opts, tmp_args)
155        },
156        (_, _) => return Err(Error::Interp(String::from("no argument"))),
157    };
158    let matches = match opts.parse(args.as_slice()) {
159        Ok(tmp_matches) => tmp_matches,
160        Err(err) => return Ok(Value::Object(Arc::new(Object::Error(String::from("getopts"), format!("{}", err))))),
161    };
162    let mut opt_fields: BTreeMap<String, Value> = BTreeMap::new();
163    for name in &names {
164        let ident = name.replace("-", "_");
165        if matches.opt_present(name.as_str()) {
166            opt_fields.insert(ident, Value::Ref(Arc::new(RwLock::new(MutObject::Array(matches.opt_strs(name.as_str()).iter().map(|s| Value::Object(Arc::new(Object::String(s.clone())))).collect())))));
167        } else {
168            opt_fields.insert(ident, Value::None);
169        }
170    }
171    let mut fields: BTreeMap<String, Value> = BTreeMap::new();
172    fields.insert(String::from("opts"), Value::Ref(Arc::new(RwLock::new(MutObject::Struct(opt_fields)))));
173    fields.insert(String::from("free"), Value::Ref(Arc::new(RwLock::new(MutObject::Array(matches.free.iter().map(|s| Value::Object(Arc::new(Object::String(s.clone())))).collect())))));
174    Ok(Value::Ref(Arc::new(RwLock::new(MutObject::Struct(fields)))))
175}
176
177/// A `getoptsusage` built-in function. 
178pub fn getoptsusage(_interp: &mut Interp, _env: &mut Env, arg_values: &[Value]) -> Result<Value>
179{
180    if arg_values.len() != 2 {
181        return Err(Error::Interp(String::from("invalid number of arguments")));
182    }
183    let (opts, brief) = match (arg_values.get(0), arg_values.get(1)) {
184        (Some(opt_value), Some(brief_value)) => {
185            let tmp_opts = create_options(&opt_value, "unsupported type for getoptsusage", |_| ())?;
186            (tmp_opts, format!("{}", brief_value))
187        },
188        (_, _) => return Err(Error::Interp(String::from("no argument"))),
189    };
190    Ok(Value::Object(Arc::new(Object::String(opts.usage(brief.as_str())))))
191}
192
193#[cfg(test)]
194mod tests;