opter/
lib.rs

1/****************************************************************************
2*
3*   lib.rs
4*   opter
5*
6*   Copyright (c) 2015 Tyler Cole
7*
8*   Details:
9*   - options come in four flavors
10*     - flag is name without value
11*     - ordinal is value without name
12*     - named is value with a name
13*     - value is simply a raw value
14*   - full names start with "--" and short names with "-"
15*   - short name flags can be stacked with "-"
16*     - "-abc" represents the flags a, b, and c
17*   - "-" with no following characters is a value
18*   - "--" signifies end of options and is not emitted
19*     - all values following "--" are simply passed along
20*
21***/
22
23use std::env;
24use std::iter;
25
26
27/****************************************************************************
28*
29*   Opt
30*
31***/
32
33#[derive(Debug)]
34#[derive(PartialEq)]
35pub enum Opt {
36    Flag(String),
37    Named(String, String),
38    Ordinal(String),
39    Raw(String),
40}
41
42
43/****************************************************************************
44*
45*   Opts
46*
47***/
48
49pub struct Opts<I> where I : Iterator<Item = String> {
50    iter  : I,
51    flags : Vec<char>,
52    name  : String,
53    drain : bool,
54    done  : bool,
55}
56
57impl<I> Opts<I> where I : Iterator<Item = String> {
58    fn next_opt (&mut self) -> Option<Opt> {
59        // Check for done
60        if self.done {
61            return None;
62        }
63
64        // Process flags
65        if let Some(c) = self.flags.pop() {
66            return Some(Opt::Flag(c.to_string()));
67        }
68
69        // Get next value
70        let value;
71        match self.iter.next() {
72            Some(a) => value = a,
73            None => {
74                // Iterator is done
75                self.done = true;
76
77                if !self.name.is_empty() {
78                    // Previous value was a name so emit a flag
79                    let temp = self.name.clone();
80                    self.name.clear();
81                    return Some(Opt::Flag(temp));
82                }
83                else {
84                    // All done
85                    return None;
86                }
87            }
88        }
89
90        // If draining just send along the value
91        if self.drain {
92            return Some(Opt::Raw(value));
93        }
94
95        // Handle -- value
96        if value == "--" {
97            self.drain = true;
98            return self.next_opt();
99        }
100
101        // Parse as name or flags
102        let mut name = String::new();
103        if value.starts_with("--") {
104            name.push_str(&value[2..]);
105        }
106        else if value.starts_with("-") {
107            if value.chars().count() > 2 {
108                // Parse flags
109                for c in value.chars().skip(1) {
110                    self.flags.push(c);
111                }
112                self.flags.reverse();
113            }
114            else {
115                // Save single char name
116                name.push_str(&value[1..]);
117            }
118        }
119
120        // Emit option
121        if !self.name.is_empty() {
122            // Named or flag
123            if !name.is_empty() {
124                // Previous was name, current is name, so previous as flag
125                let temp = name;
126                name = self.name.clone();
127                self.name = temp;
128                return Some(Opt::Flag(name));
129            }
130            else if !self.flags.is_empty() {
131                // Previous was name, current is flags, so previous as flag
132                name = self.name.clone();
133                self.name.clear();
134                return Some(Opt::Flag(name));
135            }
136            else {
137                // Previous was name, current is ordinal, so current as named
138                name = self.name.clone();
139                self.name.clear();
140                return Some(Opt::Named(name, value));
141            }
142        }
143        else if !name.is_empty() {
144            // Previous was not name, current is name
145            self.name = name;
146            return self.next_opt();
147        }
148        else if !self.flags.is_empty() {
149            // Previous was not name, current is flags
150            return self.next_opt();
151        }
152
153        // Previous was not name, current is ordinal, so current as ordinal
154        return Some(Opt::Ordinal(value));
155    }
156}
157
158impl<I> Iterator for Opts<I> where I : Iterator<Item = String> {
159    type Item = Opt;
160
161    fn next (&mut self) -> Option<Opt> {
162        return self.next_opt();
163    }
164}
165
166
167/****************************************************************************
168*
169*   Public functions
170*
171***/
172
173pub fn parse<I, II> (values : II) -> Opts<II::IntoIter> where
174    I : Iterator<Item = String>,
175    II : IntoIterator<Item = String, IntoIter = I>
176{
177    return Opts {
178        iter  : values.into_iter(),
179        flags : Vec::new(),
180        name  : String::new(),
181        drain : false,
182        done  : false,
183    };
184}
185
186pub fn parse_env () -> Opts<iter::Skip<env::Args>> {
187    return parse(env::args().skip(1));
188}
189
190
191
192/****************************************************************************
193*
194*   Tests
195*
196***/
197
198#[cfg(test)]
199mod tests {
200    use super::Opt;
201
202    fn args_from_str (s : &str) -> Vec<String> {
203        return s.split(" ").map(|s| s.to_string()).collect();
204    }
205
206    fn options_from_str (s : &str) -> Vec<Opt> {
207        return super::parse(args_from_str(s)).collect();
208    }
209
210    fn flag_from_str (name : &str) -> Opt {
211        return Opt::Flag(name.to_string());
212    }
213
214    fn named_from_str (name : &str, value : &str) -> Opt {
215        return Opt::Named(name.to_string(), value.to_string());
216    }
217
218    fn ordinal_from_str (value : &str) -> Opt {
219        return Opt::Ordinal(value.to_string());
220    }
221
222    fn raw_from_str (value : &str) -> Opt {
223        return Opt::Raw(value.to_string());
224    }
225
226    #[test]
227    fn full () {
228        let actual = options_from_str("a -bc -d e --f g - h -- i -j --k");
229        let spec = vec![
230            ordinal_from_str("a"),
231            flag_from_str("b"),
232            flag_from_str("c"),
233            named_from_str("d", "e"),
234            named_from_str("f", "g"),
235            ordinal_from_str("-"),
236            ordinal_from_str("h"),
237            raw_from_str("i"),
238            raw_from_str("-j"),
239            raw_from_str("--k"),
240        ];
241
242        assert_eq!(actual, spec);
243    }
244
245    #[test]
246    fn trailing_flag () {
247        let actual = options_from_str("a b -c");
248        let spec = vec![
249            ordinal_from_str("a"),
250            ordinal_from_str("b"),
251            flag_from_str("c"),
252        ];
253
254        assert_eq!(actual, spec);
255    }
256}