bulk 0.4.2

A simple tool for making deb packages, repositories, and update version numbers.
use std::io::{self, BufRead};
use std::sync::Arc;

use re;
use config::VersionHolder;


#[derive(Clone)]
pub struct Scanner {
    pub block_start: Option<Arc<re::Regex>>,
    pub block_end: Option<Arc<re::Regex>>,
    pub multiple_blocks: bool,
    pub regex: Arc<re::Regex>,
    pub partial: Option<Arc<re::Regex>>,
}

pub struct Lines<B: BufRead>(usize, B);

impl<B: BufRead> Iterator for Lines<B> {
    type Item = io::Result<(usize, String)>;
    fn next(&mut self) -> Option<io::Result<(usize, String)>> {
        let Lines(ref mut lineno, ref mut file) = *self;
        let mut s = String::with_capacity(200);
        match file.read_line(&mut s) {
            Ok(0) => None,
            Ok(_) => {
                *lineno += 1;
                Some(Ok((*lineno, s)))
            }
            Err(e) => Some(Err(e)),
        }
    }
}

impl<B:BufRead> Lines<B> {
    pub fn iter(file: B) -> Lines<B> {
        Lines(0, file)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
    Prefix,
    Body,
    Suffix,
}

pub struct Iter<'a> {
    scanner: &'a Scanner,
    state: State,
    error: Option<Error>,
}

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        EmptyVersion(line_no: usize) {
            display("{}: empty version string captured", line_no)
            description("empty version")
        }
        NoCapture(line_no: usize) {
            display("{}: no capture in regex, probably bad regex", line_no)
            description("no capture in regex, probably bad regex")
        }
    }
}

impl<'a> Iter<'a> {

    pub fn error(self) -> Result<(), Error> {
        self.error.map(Err).unwrap_or(Ok(()))
    }

    pub fn scanner(&self) -> &Scanner {
        self.scanner
    }

    pub fn line(&mut self, line_no: usize, line: &str)
        -> Option<(usize, usize)>
    {
        use self::State::*;
        use self::Error::*;
        if self.error.is_some() {
            return None;
        }
        match self.state {
            Prefix => {
                if self.scanner.block_start.as_ref().unwrap().is_match(line) {
                    self.state = Body;
                }
                None
            }
            Body => match self.scanner.regex.captures(line) {
                Some(cpt) => match cpt.pos(1) {
                    Some((x, y)) if x == y => {
                        self.error = Some(EmptyVersion(line_no));
                        return None;
                    }
                    Some(x) => Some(x),
                    None => {
                        self.error = Some(NoCapture(line_no));
                        return None;
                    }
                },
                None => {
                    match self.scanner.block_end {
                        Some(ref end_re) if end_re.is_match(line) => {
                            if self.scanner.multiple_blocks {
                                self.state = Prefix;
                            } else {
                                self.state = Suffix;
                            }
                        }
                        _ => {}
                    }
                    None
                }
            },
            Suffix => None,
        }
    }
}

impl Scanner {
    pub fn new(cfg: &VersionHolder) -> Result<Scanner, re::Error> {
        Ok(Scanner {
            block_start: if let Some(ref regex) = cfg.block_start {
                             Some(try!(re::compile(regex)))
                         } else { None },
            block_end: if let Some(ref regex) = cfg.block_end {
                           Some(try!(re::compile(regex)))
                       } else { None },
            regex: try!(re::compile(&cfg.regex)),
            multiple_blocks: cfg.multiple_blocks,
            partial: if let Some(ref regex) = cfg.partial_version {
                           Some(try!(re::compile(regex)))
                       } else { None },
        })
    }
    pub fn start(&self) -> Iter {
        use self::State::*;
        Iter {
            scanner: self,
            state: if self.block_start.is_some()
                { Prefix } else { Body },
            error: None,
        }
    }
}