1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use errors::*;

use std::fs;
use std::io;
use std::path;

use std::io::BufRead;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Entry {
    pub src: bool,
    pub url: String,
    pub suite_codename: String,
    pub components: Vec<String>,
    pub arch: Option<String>,
}

fn read_single_line(line: &str) -> Result<Vec<Entry>> {
    let line = match line.find('#') {
        Some(comment) => &line[..comment],
        None => line,
    }.trim();

    if line.is_empty() {
        return Ok(Vec::new());
    }

    let mut parts = line.split_whitespace().peekable();

    let src = parts.next().ok_or("deb{,s,-src} section required")?;
    let arch = match parts.peek() {
        Some(&val) if val.starts_with("[") => {
            parts.next();
            Some(val)
        }
        Some(_) => None,
        None => bail!("unexpected end of line looking for arch or url"),
    };

    let url = parts.next().ok_or("url section required")?;
    let suite = parts.next().ok_or("suite section required")?;

    let components: Vec<&str> = parts.collect();

    let srcs: &[bool] = match src {
        "deb" => &[false],
        "deb-src" => &[true],
        "debs" => &[false, true],
        other => bail!("unsupported deb-src tag: {:?}", other),
    };

    let mut ret = Vec::with_capacity(srcs.len());

    for src in srcs {
        ret.push(Entry {
            src: *src,
            url: if url.ends_with('/') {
                url.to_string()
            } else {
                format!("{}/", url)
            },
            suite_codename: suite.to_string(),
            components: components.iter().map(|x| x.to_string()).collect(),
            arch: arch.map(|arch| arch.to_string()),
        });
    }

    Ok(ret)
}

fn read_single_line_number(line: &str, no: usize) -> Result<Vec<Entry>> {
    read_single_line(line).chain_err(|| format!("parsing line {}", no + 1))
}

pub fn read(from: &str) -> Result<Vec<Entry>> {
    from.lines()
        .enumerate()
        .map(|(no, line)| read_single_line_number(line, no))
        .collect::<Result<Vec<Vec<Entry>>>>()
        .map(|vec_vec| vec_vec.into_iter().flat_map(|x| x).collect())
}

pub fn load<P: AsRef<path::Path>>(path: P) -> Result<Vec<Entry>> {
    io::BufReader::new(fs::File::open(path)?)
        .lines()
        .enumerate()
        .map(|(no, line)| match line {
            Ok(line) => read_single_line_number(&line, no),
            Err(e) => Err(Error::with_chain(e, format!("reading around line {}", no))),
        })
        .collect::<Result<Vec<Vec<Entry>>>>()
        .map(|vec_vec| vec_vec.into_iter().flat_map(|x| x).collect())
}

#[cfg(test)]
mod tests {
    use super::read;
    use super::Entry;

    #[test]
    fn simple() {
        assert_eq!(
            vec![
                Entry {
                    src: false,
                    arch: None,
                    url: "http://foo/".to_string(),
                    suite_codename: "bar".to_string(),
                    components: vec!["baz".to_string(), "quux".to_string()],
                },
                Entry {
                    src: true,
                    arch: None,
                    url: "http://foo/".to_string(),
                    suite_codename: "bar".to_string(),
                    components: vec!["baz".to_string(), "quux".to_string()],
                },
            ],
            read(
                r"
deb     http://foo  bar  baz quux
deb-src http://foo  bar  baz quux
",
            ).unwrap()
        );
    }
}