hostlist/
lib.rs

1/*
2MIT License
3
4Copyright (c) 2016-2020 Janne Blomqvist
5
6Permission is hereby granted, free of charge, to any person obtaining
7a copy of this software and associated documentation files (the
8"Software"), to deal in the Software without restriction, including
9without limitation the rights to use, copy, modify, merge, publish,
10distribute, sublicense, and/or sell copies of the Software, and to
11permit persons to whom the Software is furnished to do so, subject to
12the following conditions:
13
14The above copyright notice and this permission notice shall be
15included in all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25 */
26
27use nom::bytes::complete::tag;
28use nom::bytes::complete::{take_while, take_while1};
29use nom::character::is_digit;
30use nom::combinator::opt;
31use nom::multi::many0;
32use nom::multi::separated_list1;
33use nom::sequence::delimited;
34use nom::sequence::pair;
35use nom::sequence::preceded;
36use nom::sequence::tuple;
37use nom::*;
38use std::str;
39
40struct DigitInfo {
41    value: u32,
42    leading_zeros: usize,
43    num_digits: usize,
44}
45
46#[derive(Debug)]
47struct RangeList {
48    ranges: Vec<(u32, u32)>,
49    num_digits: usize,
50}
51
52// A name component part of a hostlist, before the hostlist syntax begins
53fn hostname_part(input: &[u8]) -> IResult<&[u8], &[u8]> {
54    let hpart = take_while1(|ch| (ch != b'[' && ch != b','));
55    hpart(input)
56}
57
58// take_digits taken from https://github.com/badboy/iso8601 (MIT
59// licensed)
60fn take_digits(i: &[u8]) -> IResult<&[u8], DigitInfo> {
61    let (i, digits) = take_while(is_digit)(i)?;
62
63    if digits.is_empty() {
64        return Err(nom::Err::Error(nom::error::Error {
65            input: i,
66            code: nom::error::ErrorKind::Eof,
67        }));
68    }
69
70    let s = str::from_utf8(digits).expect("Invalid data, expected UTF-8 string");
71    let res = s
72        .parse()
73        .expect("Invalid string, expected ASCII representation of a number");
74
75    let mut clz = 0;
76    for &c in digits {
77        if c == b'0' {
78            clz += 1;
79        } else {
80            break;
81        }
82    }
83    let di = DigitInfo {
84        value: res,
85        leading_zeros: clz,
86        num_digits: digits.len(),
87    };
88    Ok((i, di))
89}
90
91// A hostlist list expressions, the stuff within []. E.g. 1,2,5-6,9
92fn listexpr(input: &[u8]) -> IResult<&[u8], RangeList> {
93    let digits = take_digits;
94    let range = tuple((&digits, opt(preceded(tag("-"), &digits))));
95    let mut snl = separated_list1(tag(","), range);
96    let (i, les) = snl(input)?;
97    let mut ri = RangeList {
98        ranges: Vec::new(),
99        num_digits: 0,
100    };
101    let mut max_lz = 0;
102    for le in les {
103        if le.0.leading_zeros > max_lz {
104            max_lz = le.0.leading_zeros;
105            ri.num_digits = le.0.num_digits;
106        }
107        let mut vals = (le.0.value, le.0.value);
108        match le.1 {
109            Some(u) => {
110                if u.value >= le.0.value {
111                    vals.1 = u.value;
112                } else {
113                    vals = (u.value, le.0.value);
114                }
115                if u.leading_zeros > max_lz {
116                    max_lz = u.leading_zeros;
117                    ri.num_digits = u.num_digits;
118                }
119            }
120            None => {}
121        }
122        ri.ranges.push(vals);
123    }
124    Ok((i, ri))
125}
126
127// A range (something enclosed with [])
128fn range(input: &[u8]) -> IResult<&[u8], RangeList> {
129    let mut r = delimited(tag("["), listexpr, tag("]"));
130    r(input)
131}
132
133// hostname-ranges pair, e.g. foo[N-M][NN-MM]
134fn hnrangepair(input: &[u8]) -> IResult<&[u8], (&[u8], Vec<RangeList>)> {
135    let mut t = pair(hostname_part, many0(range));
136    t(input)
137}
138
139// A complete hostlist, e.g. foo[1-3]bar[4-5][5-6],baz[1-3]
140fn hostlist(input: &[u8]) -> IResult<&[u8], Vec<Vec<(&[u8], Vec<RangeList>)>>> {
141    let m = many0(hnrangepair);
142    let mut snl = separated_list1(tag(","), m);
143    snl(input)
144}
145
146// Cartesian multiplication of strings
147fn cartesian<T: AsRef<str> + ToString>(v1: &[T], v2: &[T]) -> Vec<String> {
148    let oldsz = v1.len();
149    let mut res = Vec::with_capacity(oldsz * v2.len());
150    for e1 in v1 {
151        for e2 in v2 {
152            // TODO: Is this dance really needed to concatenate two &[T]'s?
153            let mut t: String = e1.to_string();
154            t.push_str(e2.to_string().as_str());
155            res.push(t);
156        }
157    }
158    res
159}
160
161/// Expand a hostlist to a vector of hostnames
162///
163/// # Examples
164///
165/// ```
166/// extern crate hostlist;
167/// assert_eq!(hostlist::expand("foo[1-3]").unwrap(),
168///            vec!["foo1", "foo2", "foo3"]);
169/// ```
170pub fn expand(a_str: &str) -> Result<Vec<String>, &'static str> {
171    let p = hostlist(a_str.as_bytes());
172    let parsed = match p {
173        Ok((_, o)) => o,
174        _ => return Err("Invalid hostlist"),
175    };
176    let mut allres = Vec::new();
177    for e in &parsed {
178        let mut res: Vec<String> = vec!["".to_string()];
179        for rangepair in e {
180            let base = str::from_utf8(&rangepair.0).unwrap();
181            let mut res2 = vec![base.to_string()];
182            for range in &rangepair.1 {
183                let mut res3: Vec<String> = Vec::new();
184                for r2 in &range.ranges {
185                    for i in r2.0..(r2.1 + 1) {
186                        // {:08} - field width 8, pad with zeros at front
187                        res3.push(format!("{:0width$}", i, width = range.num_digits));
188                    }
189                }
190                res2 = cartesian(&res2, &res3);
191            }
192            res = cartesian(&res, &res2);
193        }
194        for host in res {
195            allres.push(host);
196        }
197    }
198    Ok(allres)
199}
200
201// Tests of private functions
202#[test]
203fn check_base() {
204    let hostlist = b"foo[1-3]";
205    let res = hostname_part(hostlist);
206    let out = match res {
207        Ok((_, o)) => str::from_utf8(&o).unwrap(),
208        _ => panic!(),
209    };
210    assert_eq!(out, "foo");
211}
212
213#[test]
214fn listexpr_1() {
215    let le = b"1";
216    let res = listexpr(le);
217    let out = match res {
218        Ok((_, o)) => o.ranges[0].0,
219        _ => panic!(),
220    };
221    assert_eq!(out, 1);
222}
223
224#[test]
225fn listexpr_2() {
226    let le = b"1,2,3-5";
227    let res = listexpr(le);
228    let out = match res {
229        Ok((_, o)) => o,
230        _ => panic!(),
231    };
232    assert_eq!(out.ranges[0].0, 1);
233    assert_eq!(out.ranges[1].0, 2);
234    assert_eq!(out.ranges[2].0, 3);
235    assert_eq!(out.ranges[2].1, 5);
236}
237
238#[test]
239fn hostrange() {
240    let hostlist = b"[1,2,3-5]";
241    let res = range(hostlist);
242    let out = match res {
243        Ok((_, o)) => o,
244        _ => {
245            println!("{:?}", res);
246            panic!();
247        }
248    };
249    assert_eq!(out.ranges[0].0, 1);
250    assert_eq!(out.ranges[1].0, 2);
251    assert_eq!(out.ranges[2].0, 3);
252    assert_eq!(out.ranges[2].1, 5);
253}
254
255/*
256#[test]
257fn hnrangepair_empty() {
258    let hostlist = b"";
259    let res = hnrangepair(hostlist);
260    let out = match res {
261        Ok((_, o)) => o,
262        _ => {
263            println!("{:?}", res);
264            panic!();
265        }
266    };
267    assert_eq!(str::from_utf8(&out.0).unwrap(), "");
268}
269*/
270
271#[test]
272fn hnrangepair_1() {
273    let hostlist = b"foo[1,2,3-5]";
274    let res = hnrangepair(hostlist);
275    let out = match res {
276        Ok((_, o)) => o,
277        _ => {
278            println!("{:?}", res);
279            panic!();
280        }
281    };
282    assert_eq!(str::from_utf8(&out.0).unwrap(), "foo");
283    let r = &out.1[0];
284    assert_eq!(r.ranges[0].0, 1);
285    assert_eq!(r.ranges[1].0, 2);
286    assert_eq!(r.ranges[2].0, 3);
287    assert_eq!(r.ranges[2].1, 5);
288}
289
290#[test]
291fn hnrangepair_hostonly() {
292    let hostlist = b"foo";
293    let res = hnrangepair(hostlist);
294    let out = match res {
295        Ok((_, o)) => str::from_utf8(&o.0).unwrap(),
296        _ => {
297            println!("{:?}", res);
298            panic!();
299        }
300    };
301    assert_eq!(out, "foo");
302}
303
304/*
305#[test]
306fn hnrangepair_rangeonly() {
307    let hostlist = b"[1,2,3-5]";
308    let res = hnrangepair(hostlist);
309    let out = match res {
310        Ok((_, o)) => o,
311        _ => {
312            println!("{:?}", res);
313            panic!();
314        }
315    };
316    let r = &out.1;
317    assert_eq!(r[0].ranges[0].0, 1);
318    //assert_eq!(r[1].0, 2);
319    //assert_eq!(r[2].0, 3);
320    //assert_eq!(r[2].1.unwrap(), 5);
321}
322*/
323
324#[test]
325fn hostlist_1() {
326    let myhl = b"foo[1,2,3-5]";
327    let res = hostlist(myhl);
328    let out = match res {
329        Ok((_, o)) => o,
330        _ => {
331            println!("{:?}", res);
332            panic!();
333        }
334    };
335    assert_eq!(str::from_utf8(&out[0][0].0).unwrap(), "foo");
336    let r = &out[0][0].1[0];
337    assert_eq!(r.ranges[0].0, 1);
338    assert_eq!(r.ranges[1].0, 2);
339    assert_eq!(r.ranges[2].0, 3);
340    assert_eq!(r.ranges[2].1, 5);
341}
342
343/*
344#[test]
345fn hostlist_empty() {
346    let res = hostlist(b"");
347    let out = match res {
348        Ok((_, o)) => o,
349        _ => {
350            println!("{:?}", res);
351            panic!();
352        }
353    };
354    assert_eq!(out[0][0].0, b"");
355}
356*/
357
358#[test]
359fn test_cartesian() {
360    let a = vec!["ab", "c"];
361    let b = vec!["1", "23"];
362    let r = cartesian(&a, &b);
363    assert_eq!("ab1", r[0]);
364    assert_eq!("ab23", r[1]);
365    assert_eq!("c1", r[2]);
366    assert_eq!("c23", r[3]);
367    assert_eq!(4, r.len());
368}
369
370// Tests of public functions
371#[cfg(test)]
372mod tests {
373    use super::*;
374
375    #[test]
376    fn it_works() {}
377
378    #[test]
379    fn test_expand() {
380        assert_eq!(expand("foo[1,2,3]").unwrap(), vec!["foo1", "foo2", "foo3"]);
381        assert_eq!(expand("foo[1-3]").unwrap(), vec!["foo1", "foo2", "foo3"]);
382        assert_eq!(expand("foo").unwrap(), vec!["foo"]);
383    }
384
385    #[test]
386    fn test_full_name_with_comma_works() {
387        assert_eq!(
388            expand("hostname1.foo.com,hostname2.foo.com").unwrap(),
389            vec!["hostname1.foo.com", "hostname2.foo.com"]
390        );
391    }
392
393    #[test]
394    fn test_trailing_parts() {
395        assert_eq!(
396            expand("hostname1.foo.com").unwrap(),
397            vec!["hostname1.foo.com"]
398        );
399    }
400
401    #[test]
402    fn test_single_host_expansion() {
403        assert_eq!(
404            expand("hostname[6].foo.com").unwrap(),
405            vec!["hostname6.foo.com"]
406        )
407    }
408
409    #[test]
410    fn test_prefix_expansion() {
411        assert_eq!(
412            expand("hostname[009-011]").unwrap(),
413            vec!["hostname009", "hostname010", "hostname011"]
414        );
415    }
416
417    #[test]
418    fn test_reverse_order() {
419        assert_eq!(
420            expand("hostname[7-5]").unwrap(),
421            vec!["hostname5", "hostname6", "hostname7"],
422        );
423    }
424
425    #[test]
426    fn test_single_item_two_ranges() {
427        assert_eq!(
428            expand("hostname[6,7]-[9-11].foo.com").unwrap(),
429            vec![
430                "hostname6-9.foo.com",
431                "hostname6-10.foo.com",
432                "hostname6-11.foo.com",
433                "hostname7-9.foo.com",
434                "hostname7-10.foo.com",
435                "hostname7-11.foo.com"
436            ]
437        );
438    }
439}