ng_clp/
lib.rs

1use std::result;
2
3use thiserror::Error;
4
5pub fn is_invalid(a: &str) -> bool {
6    a.starts_with("---") || a.starts_with("--=")
7}
8
9pub fn is_argument(a: &str) -> bool {
10    !is_invalid(a) && (a == "-" || !a.starts_with('-'))
11}
12
13pub fn is_separator(a: &str) -> bool {
14    a == "--"
15}
16
17pub fn is_flag_or_option(a: &str) -> bool {
18    !is_invalid(a) && !is_argument(a) && !is_separator(a)
19}
20
21#[derive(Error, Debug, PartialEq)]
22pub enum ParseError {
23    #[error("internal, argument can not have argument: {}", arg)]
24    InternalArgumentCanNotHaveArgument { arg: String },
25
26    #[error("internal, separator can not have argument")]
27    InternalSeparatorCanNotHaveArgument,
28
29    #[error("internal, invalid eat count: {}", .eat)]
30    InternalInvalidEatCount { eat: usize },
31
32    #[error("internal, index out of range: {}", .index)]
33    InternalIndexOutOfRange { index: usize },
34
35    #[error("flag can not take argument: {}", .name)]
36    FlagWithArgument { name: String },
37
38    #[error("option missing argument: {}", .name)]
39    OptionWithoutArgument { name: String },
40
41    #[error("invalid argument: {}", .value)]
42    InvalidArgument { value: String },
43
44    #[error("unexpected separator: {}", .value)]
45    UnexpectedSeparator { value: String },
46
47    #[error("unknown flag or option: {}", .name)]
48    UnknownFlagOrOption { name: String },
49
50    #[error("invalid string: {}", .s)]
51    InvalidString { s: String },
52}
53
54fn _s(s: &str) -> String {
55    s.to_string()
56}
57
58pub fn parse<'s, 'a>(
59    argv: &'a [&'s str],
60    index: usize,
61) -> result::Result<(&'s str, Option<&'s str>), ParseError> {
62    if index >= argv.len() {
63        return Err(ParseError::InternalIndexOutOfRange { index: index });
64    }
65
66    let a = argv[index];
67    if is_invalid(a) {
68        Err(ParseError::InvalidString { s: _s(a) })
69    } else if is_argument(a) || is_separator(a) {
70        Ok((a, None))
71    } else if a.starts_with("--") {
72        if let Some(i) = a.find('=') {
73            Ok((&a[..i], Some(&a[i + 1..])))
74        } else {
75            if index + 1 < argv.len() && is_argument(argv[index + 1]) {
76                Ok((a, Some(argv[index + 1])))
77            } else {
78                Ok((a, None))
79            }
80        }
81    } else {
82        assert!(a.starts_with('-'));
83        if a.len() > 2 {
84            Ok((&a[..2], Some(&a[2..])))
85        } else {
86            if index + 1 < argv.len() && is_argument(argv[index + 1]) {
87                Ok((a, Some(argv[index + 1])))
88            } else {
89                Ok((a, None))
90            }
91        }
92    }
93}
94
95pub fn next_index(argv: &[&str], index: usize, eat: usize) -> result::Result<usize, ParseError> {
96    if index >= argv.len() {
97        return Err(ParseError::InternalIndexOutOfRange { index });
98    }
99
100    let a = argv[index];
101    if eat == 0 {
102        if is_invalid(a) {
103            return Err(ParseError::InvalidString { s: _s(a) });
104        } else if is_argument(a) {
105            return Err(ParseError::InvalidArgument { value: _s(a) });
106        } else if is_separator(a) {
107            return Err(ParseError::UnexpectedSeparator { value: _s(a) });
108        } else if is_flag_or_option(a) {
109            if let Some(i) = a.find('=') {
110                return Err(ParseError::UnknownFlagOrOption { name: _s(&a[..i]) });
111            } else {
112                return Err(ParseError::UnknownFlagOrOption { name: _s(a) });
113            }
114        } else {
115        }
116    } else if !(eat == 1 || eat == 2) {
117        return Err(ParseError::InternalInvalidEatCount { eat });
118    }
119
120    let ni = if is_invalid(a) {
121        return Err(ParseError::InvalidString { s: _s(a) });
122    } else if is_argument(a) {
123        if eat == 2 {
124            return Err(ParseError::InternalArgumentCanNotHaveArgument { arg: _s(a) });
125        }
126        index + 1
127    } else if is_separator(a) {
128        if eat == 2 {
129            return Err(ParseError::InternalSeparatorCanNotHaveArgument);
130        } else {
131            assert_eq!(eat, 1);
132            index + 1
133        }
134    } else {
135        assert!(is_flag_or_option(a));
136        if a.starts_with("--") {
137            if let Some(i) = a.find('=') {
138                if eat == 1 {
139                    return Err(ParseError::FlagWithArgument { name: _s(&a[..i]) });
140                } else {
141                    assert_eq!(eat, 2);
142                    index + 1
143                }
144            } else {
145                if eat == 2 {
146                    if index + 1 < argv.len() && is_argument(argv[index + 1]) {
147                        index + eat
148                    } else {
149                        return Err(ParseError::OptionWithoutArgument { name: _s(a) });
150                    }
151                } else {
152                    index + eat
153                }
154            }
155        } else if a.len() > 2 {
156            assert!(a.starts_with('-') && &a[1..2] != "-");
157            if eat == 1 {
158                return Err(ParseError::FlagWithArgument { name: _s(&a[..2]) });
159            } else {
160                assert_eq!(eat, 2);
161                index + 1
162            }
163        } else {
164            if index + 1 < argv.len() && is_argument(argv[index + 1]) {
165                index + eat
166            } else if eat == 2 {
167                return Err(ParseError::OptionWithoutArgument { name: _s(a) });
168            } else {
169                assert_eq!(eat, 1);
170                index + 1
171            }
172        }
173    };
174
175    Ok(ni)
176}
177
178pub fn unwrap_argument<'s>(
179    parse_result: (&'s str, Option<&'s str>),
180) -> result::Result<&'s str, ParseError> {
181    if let Some(a) = parse_result.1 {
182        Ok(a)
183    } else {
184        Err(ParseError::OptionWithoutArgument { name: _s(parse_result.0) })
185    }
186}