<commit_before>extern crate regex;
mod lexutils;

use self::lexutils::TokenCollection;
use std::io::prelude::*;
use std::io::BufReader;
use std::io;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Position { pub line: usize, pub pos: usize, }
impl Position {
    pub fn new() -> Self {
        Position { line: 0, pos: 0 }
    }
    pub fn new_line(&mut self) {
        self.line += 1;
        self.pos = 0;
    }
    pub fn push(&mut self, amt: usize) {
        self.pos += amt;
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum LexToken {
    OpenBrace, CloseBrace,
    OpenBracket, CloseBracket,
    Comma, Colon,
    Identifier(String),
    StringLit(String),
    IntegerLit(i64),
    FloatLit(f64),
}

#[derive(Debug)]
pub enum LexError {
    IOError(io::Error),
    UnrecognisedCharError(char),
}

#[derive(Debug, PartialEq, Eq)]
pub struct Token {
    pattern: regex::Regex,
}

impl Token {
    fn new(re: &str) -> Result<Self, regex::Error> {
        Ok(Token {
            pattern: try!(regex::Regex::new(&("^(?:".to_string() + re + ")"))),
        })
    }

    fn get_match(&self, inp: &str) -> Option<usize> {
        if let Some((_, end)) = self.pattern.find(inp) {
            Some(end)
        } else {
            None
        }
    }
}

pub struct Lexer<R: Read> {
    filestream: BufReader<R>,
    buffer: String,
    position: Position,
    ignore: Vec<Token>,
    tokens: TokenCollection,
    errored: bool,
    read_to_end: bool,
}

impl<R: Read> Lexer<R> {

    pub fn lex(file: R) -> Self {
        Lexer {
            filestream: BufReader::new(file),
            buffer: String::new(),
            position: Position::new(),
            errored: false,
            read_to_end: false,
            ignore: vec![
                Token::new(r"\s*").unwrap()
            ],
            tokens: TokenCollection::new(),
        }
    }

    fn consume_buffer(&mut self, len: usize) -> String {
        let mut position = self.position.clone();
        let response = self.buffer.chars().take(len).collect();
        self.buffer = self.buffer
            .chars()
            .skip(len)
            .map(|c| {
                if c == '\n' {
                    position.new_line();
                } else {
                    position.push(1);
                }
                c
            } )
            .collect();
        self.position = position;
        return response;
    }
    fn can_read(&self, len: usize) -> bool {
        return len == self.buffer.len() && !self.read_to_end;
    }
}

impl<R: Read> Iterator for Lexer<R> {
    type Item = Result<LexToken, LexError>;

    fn next(&mut self) -> Option<Self::Item> {
        'mainloop: loop {
            match self.filestream.read_line(&mut self.buffer) {
                Ok(size) => {
                    if size == 0 {
                        self.read_to_end = true;
                        // read to end of file - but have we finished reading the buffer?
                        if self.buffer.len() == 0 {
                            return None;
                        }
                    }
                },
                Err(err) => {
                    return Some(Err(LexError::IOError(err)));
                }
            }

            self.position.new_line();

            for ign in &self.ignore {
                if let Some(len) = ign.get_match(&self.buffer) {
                    if self.can_read(len) { continue 'mainloop; }
                    self.buffer = self.buffer.chars().skip(len).collect();
                }
            }

            if let Some(len) = self.tokens.open_brace.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::OpenBrace));
            } else if let Some(len) = self.tokens.close_brace.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::CloseBrace));
            } else if let Some(len) = self.tokens.open_bracket.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::OpenBracket));
            } else if let Some(len) = self.tokens.close_bracket.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::CloseBracket));
            } else if let Some(len) = self.tokens.comma.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::Comma));
            } else if let Some(len) = self.tokens.colon.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::Colon));
            } else if let Some(len) = self.tokens.identifier.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::Identifier(self.consume_buffer(len))));
            } else if let Some(len) = self.tokens.stringlit.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::StringLit(
                    lexutils::parse_string(self.consume_buffer(len)))));
            } else if let Some(len) = self.tokens.floatlit.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::FloatLit(
                    lexutils::parse_float(self.consume_buffer(len)))));
            } else if let Some(len) = self.tokens.integerlit.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::IntegerLit(
                    lexutils::parse_integer(self.consume_buffer(len)))));
            } else {
                if self.read_to_end {
                    return Some(Err(LexError::UnrecognisedCharError(self.buffer.chars().next().unwrap())));
                } else {
                    continue 'mainloop;
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    pub use super::*;

    mod token {
        use super::*;
        extern crate regex;

        #[test]
        fn test_construction() {
            let tok = Token::new("xyz").unwrap();
            assert_eq!(tok.pattern, regex::Regex::new("^(?:xyz)").unwrap());
        }

        #[test]
        fn test_matching() {
            let tok = Token::new("xyz").unwrap();
            assert_eq!(tok.get_match("xyzdjsd"), Some(3));
            assert_eq!(tok.get_match("abcdjsd"), None);
        }
    }

    mod lexer {
        use super::*;
        use std::io::Cursor;

        #[test]
        fn test_lex_iterable() {
            let file = Cursor::new("hello {}".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("hello".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(), LexToken::OpenBrace);
            assert_eq!(iter.next().unwrap().unwrap(), LexToken::CloseBrace);
            assert!(iter.next().is_none());
        }

        #[test] #[ignore] // Comments not yet implemented
        fn lex_ignored_tokens() {
            let file = Cursor::new(
                "
                token1
                // hello
                token2
                /* one line */
                token3
                /* multiple
                token4
                 * lines */
                 token5".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token1".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token2".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token3".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token5".to_string()));
            assert!(iter.next().is_none());
        }

        #[test]
        fn lex_all_tokens() {
            let file = Cursor::new("ident {}[],: 'string' 54 3.5e5 false".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("ident".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::OpenBrace);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::CloseBrace);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::OpenBracket);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::CloseBracket);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Comma);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Colon);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::StringLit("string".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::IntegerLit(54));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::FloatLit(3.5e5_f64));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("false".to_string()));
            assert!(iter.next().is_none());
        }

        #[test]
        fn test_errors() {
            let file = Cursor::new("ident | <-- invalid character".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("ident".to_string()));
            if let Err(error) = iter.next().unwrap() {
                if let LexError::UnrecognisedCharError(character) = error {
                    assert_eq!(character, '|');
                } else {
                    panic!("Should raise UnrecognisedCharError");
                }
            } else {
                panic!("Should raise an error");
            }
        }
    }
}
<commit_msg>Lexer errors should end iteration<commit_after>extern crate regex;
mod lexutils;

use self::lexutils::TokenCollection;
use std::io::prelude::*;
use std::io::BufReader;
use std::io;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Position { pub line: usize, pub pos: usize, }
impl Position {
    pub fn new() -> Self {
        Position { line: 0, pos: 0 }
    }
    pub fn new_line(&mut self) {
        self.line += 1;
        self.pos = 0;
    }
    pub fn push(&mut self, amt: usize) {
        self.pos += amt;
    }
}

#[derive(Debug, PartialEq, Clone)]
pub enum LexToken {
    OpenBrace, CloseBrace,
    OpenBracket, CloseBracket,
    Comma, Colon,
    Identifier(String),
    StringLit(String),
    IntegerLit(i64),
    FloatLit(f64),
}

#[derive(Debug)]
pub enum LexError {
    IOError(io::Error),
    UnrecognisedCharError(char),
}

#[derive(Debug, PartialEq, Eq)]
pub struct Token {
    pattern: regex::Regex,
}

impl Token {
    fn new(re: &str) -> Result<Self, regex::Error> {
        Ok(Token {
            pattern: try!(regex::Regex::new(&("^(?:".to_string() + re + ")"))),
        })
    }

    fn get_match(&self, inp: &str) -> Option<usize> {
        if let Some((_, end)) = self.pattern.find(inp) {
            Some(end)
        } else {
            None
        }
    }
}

pub struct Lexer<R: Read> {
    filestream: BufReader<R>,
    buffer: String,
    position: Position,
    ignore: Vec<Token>,
    tokens: TokenCollection,
    errored: bool,
    read_to_end: bool,
}

impl<R: Read> Lexer<R> {

    pub fn lex(file: R) -> Self {
        Lexer {
            filestream: BufReader::new(file),
            buffer: String::new(),
            position: Position::new(),
            errored: false,
            read_to_end: false,
            ignore: vec![
                Token::new(r"\s*").unwrap()
            ],
            tokens: TokenCollection::new(),
        }
    }

    fn consume_buffer(&mut self, len: usize) -> String {
        let mut position = self.position.clone();
        let response = self.buffer.chars().take(len).collect();
        self.buffer = self.buffer
            .chars()
            .skip(len)
            .map(|c| {
                if c == '\n' {
                    position.new_line();
                } else {
                    position.push(1);
                }
                c
            } )
            .collect();
        self.position = position;
        return response;
    }

    fn can_read(&self, len: usize) -> bool {
        return len == self.buffer.len() && !self.read_to_end;
    }

    fn make_err(&mut self, err: LexError) -> Option<Result<LexToken, LexError>> {
        self.errored = true;
        Some(Err(err))
    }
}

impl<R: Read> Iterator for Lexer<R> {
    type Item = Result<LexToken, LexError>;

    fn next(&mut self) -> Option<Self::Item> {
        'mainloop: loop {
            if self.errored {
                return None;
            }

            match self.filestream.read_line(&mut self.buffer) {
                Ok(size) => {
                    if size == 0 {
                        self.read_to_end = true;
                        // read to end of file - but have we finished reading the buffer?
                        if self.buffer.len() == 0 {
                            return None;
                        }
                    }
                },
                Err(err) => {
                    return Some(Err(LexError::IOError(err)));
                }
            }

            self.position.new_line();

            for ign in &self.ignore {
                if let Some(len) = ign.get_match(&self.buffer) {
                    if self.can_read(len) { continue 'mainloop; }
                    self.buffer = self.buffer.chars().skip(len).collect();
                }
            }

            if let Some(len) = self.tokens.open_brace.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::OpenBrace));
            } else if let Some(len) = self.tokens.close_brace.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::CloseBrace));
            } else if let Some(len) = self.tokens.open_bracket.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::OpenBracket));
            } else if let Some(len) = self.tokens.close_bracket.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::CloseBracket));
            } else if let Some(len) = self.tokens.comma.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::Comma));
            } else if let Some(len) = self.tokens.colon.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                self.consume_buffer(len);
                return Some(Ok(LexToken::Colon));
            } else if let Some(len) = self.tokens.identifier.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::Identifier(self.consume_buffer(len))));
            } else if let Some(len) = self.tokens.stringlit.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::StringLit(
                    lexutils::parse_string(self.consume_buffer(len)))));
            } else if let Some(len) = self.tokens.floatlit.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::FloatLit(
                    lexutils::parse_float(self.consume_buffer(len)))));
            } else if let Some(len) = self.tokens.integerlit.get_match(&self.buffer) {
                if self.can_read(len) { continue 'mainloop; }
                return Some(Ok(LexToken::IntegerLit(
                    lexutils::parse_integer(self.consume_buffer(len)))));
            } else {
                if self.read_to_end {
                    let ch = self.buffer.chars().next().unwrap();
                    return self.make_err(LexError::UnrecognisedCharError(ch));
                } else {
                    continue 'mainloop;
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    pub use super::*;

    mod token {
        use super::*;
        extern crate regex;

        #[test]
        fn test_construction() {
            let tok = Token::new("xyz").unwrap();
            assert_eq!(tok.pattern, regex::Regex::new("^(?:xyz)").unwrap());
        }

        #[test]
        fn test_matching() {
            let tok = Token::new("xyz").unwrap();
            assert_eq!(tok.get_match("xyzdjsd"), Some(3));
            assert_eq!(tok.get_match("abcdjsd"), None);
        }
    }

    mod lexer {
        use super::*;
        use std::io::Cursor;

        #[test]
        fn test_lex_iterable() {
            let file = Cursor::new("hello {}".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("hello".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(), LexToken::OpenBrace);
            assert_eq!(iter.next().unwrap().unwrap(), LexToken::CloseBrace);
            assert!(iter.next().is_none());
        }

        #[test] #[ignore] // Comments not yet implemented
        fn lex_ignored_tokens() {
            let file = Cursor::new(
                "
                token1
                // hello
                token2
                /* one line */
                token3
                /* multiple
                token4
                 * lines */
                 token5".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token1".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token2".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token3".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("token5".to_string()));
            assert!(iter.next().is_none());
        }

        #[test]
        fn lex_all_tokens() {
            let file = Cursor::new("ident {}[],: 'string' 54 3.5e5 false".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("ident".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::OpenBrace);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::CloseBrace);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::OpenBracket);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::CloseBracket);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Comma);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Colon);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::StringLit("string".to_string()));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::IntegerLit(54));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::FloatLit(3.5e5_f64));
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("false".to_string()));
            assert!(iter.next().is_none());
        }

        #[test]
        fn test_errors() {
            let file = Cursor::new("ident | <-- invalid character".as_bytes());
            let mut iter = Lexer::lex(file);
            assert_eq!(iter.next().unwrap().unwrap(),
                LexToken::Identifier("ident".to_string()));
            if let Err(error) = iter.next().unwrap() {
                if let LexError::UnrecognisedCharError(character) = error {
                    assert_eq!(character, '|');
                } else {
                    panic!("Should raise UnrecognisedCharError");
                }
            } else {
                panic!("Should raise an error");
            }
            assert!(iter.next().is_none());
        }
    }
}
<|endoftext|>


<commit_before>//! A map based on a B-Tree.
use std::mem;
use std::borrow::Borrow;
use core;

pub use core::Iter;

/// A map based on a B-Tree.
///
/// TODO
///
/// It is a logic error for a key to be modified in such a way that
/// the key's ordering relative to any other key,
/// as determined by the `Ord` trait, changes while it is in the map.
/// This is normally only possible through `Cell`, `RefCell`, global state, I/O, or unsafe code.
///
/// # Examples
///
/// TODO
///
/// `SplayMap` implements an [Entry API](#method.entry) which allows for
/// more complex methods of getting, setting, updating and removing keys and their values:
///
/// TODO
#[derive(Debug, Clone)]
pub struct SplayMap<K, V> {
    tree: core::Tree<K, V>,
}
impl<K, V> SplayMap<K, V>
    where K: Ord
{
    /// Makes a new empty `SplayMap`.
    ///
    /// # Examples
    ///
    pub fn new() -> Self {
        SplayMap { tree: core::Tree::new() }
    }
    pub fn clear(&mut self) {
        self.tree = core::Tree::new();
    }
    pub fn get<Q: ?Sized>(&mut self, key: &Q) -> Option<&V>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.get_mut(key).map(|v| &*v)
    }
    pub fn get_mut<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut V>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.get(key)
    }
    pub fn find_lower_bound<Q: ?Sized>(&mut self, key: &Q) -> Option<&K>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.find_lower_bound(key)
    }
    pub fn find_upper_bound<Q: ?Sized>(&mut self, key: &Q) -> Option<&K>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.find_upper_bound(key)
    }
    pub fn contains_key<Q: ?Sized>(&mut self, key: &Q) -> bool
        where K: Borrow<Q>,
              Q: Ord
    {
        self.get(key).is_some()
    }
    pub fn insert(&mut self, key: K, value: V) -> Option<V> {
        self.tree.insert(key, value)
    }
    pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.remove(key)
    }
    pub fn entry(&mut self, key: K) -> Entry<K, V> {
        if self.contains_key(&key) {
            Entry::Occupied(OccupiedEntry {
                key: key,
                tree: &mut self.tree,
            })
        } else {
            Entry::Vacant(VacantEntry {
                key: key,
                tree: &mut self.tree,
            })
        }
    }
}
impl<K, V> SplayMap<K, V> {
    pub fn len(&self) -> usize {
        self.tree.len
    }
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
    pub fn iter(&self) -> Iter<K, V> {
        self.tree.iter()
    }
    // TODO: iterator
}
impl<K, V> Default for SplayMap<K, V>
    where K: Ord
{
    fn default() -> Self {
        SplayMap::new()
    }
}

// TODO: move to core
pub enum Entry<'a, K: 'a, V: 'a> {
    Occupied(OccupiedEntry<'a, K, V>),
    Vacant(VacantEntry<'a, K, V>),
}
impl<'a, K: 'a, V: 'a> Entry<'a, K, V>
    where K: Ord
{
    pub fn key(&self) -> &K {
        match *self {
            Entry::Occupied(ref e) => &e.key,
            Entry::Vacant(ref e) => &e.key,
        }
    }
    pub fn or_insert(self, default: V) -> &'a mut V {
        match self {
            Entry::Occupied(e) => e.into_mut(),
            Entry::Vacant(e) => e.insert(default),
        }
    }
    pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
        match self {
            Entry::Occupied(e) => e.into_mut(),
            Entry::Vacant(e) => e.insert(default()),
        }
    }
}

pub struct OccupiedEntry<'a, K: 'a, V: 'a> {
    key: K,
    tree: &'a mut core::Tree<K, V>,
}
impl<'a, K: 'a, V: 'a> OccupiedEntry<'a, K, V>
    where K: Ord
{
    pub fn key(&self) -> &K {
        &self.key
    }
    pub fn get(&self) -> &V {
        &self.tree.root.as_ref().unwrap().val
    }
    pub fn get_mut(&mut self) -> &mut V {
        &mut self.tree.root.as_mut().unwrap().val
    }
    pub fn into_mut(self) -> &'a mut V {
        &mut self.tree.root.as_mut().unwrap().val
    }
    pub fn insert(&mut self, value: V) -> V {
        mem::replace(self.tree.get(&self.key).unwrap(), value)
    }
    pub fn remove(self) -> V {
        self.tree.remove(&self.key).unwrap()
    }
}

pub struct VacantEntry<'a, K: 'a, V: 'a> {
    key: K,
    tree: &'a mut core::Tree<K, V>,
}
impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V>
    where K: Ord
{
    pub fn key(&self) -> &K {
        &self.key
    }
    pub fn insert(self, value: V) -> &'a mut V {
        self.tree.insert(self.key, value);
        &mut self.tree.root.as_mut().unwrap().val
    }
}
<commit_msg>[WIP] Add documentaions for SplayMap<commit_after>//! A map based on a B-Tree.
use std::mem;
use std::borrow::Borrow;
use core;

pub use core::Iter;

/// A map based on a B-Tree.
///
/// TODO
///
/// It is a logic error for a key to be modified in such a way that
/// the key's ordering relative to any other key,
/// as determined by the `Ord` trait, changes while it is in the map.
/// This is normally only possible through `Cell`, `RefCell`, global state, I/O, or unsafe code.
///
/// # Examples
///
/// TODO
///
/// `SplayMap` implements an [Entry API](#method.entry) which allows for
/// more complex methods of getting, setting, updating and removing keys and their values:
///
/// TODO
#[derive(Debug, Clone)]
pub struct SplayMap<K, V> {
    tree: core::Tree<K, V>,
}
impl<K, V> SplayMap<K, V>
    where K: Ord
{
    /// Makes a new empty `SplayMap`.
    ///
    /// # Examples
    /// ```
    /// use splay_tree::SplayMap;
    ///
    /// let mut map = SplayMap::new();
    /// map.insert("foo", 1);
    /// assert_eq!(map.len(), 1);
    /// ```
    pub fn new() -> Self {
        SplayMap { tree: core::Tree::new() }
    }

    /// Clears the map, removing all values.
    ///
    /// # Examples
    /// ```
    /// use splay_tree::SplayMap;
    ///
    /// let mut map = SplayMap::new();
    /// map.insert("foo", 1);
    /// map.clear();
    /// assert!(map.is_empty());
    /// ```
    pub fn clear(&mut self) {
        self.tree = core::Tree::new();
    }
    pub fn get<Q: ?Sized>(&mut self, key: &Q) -> Option<&V>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.get_mut(key).map(|v| &*v)
    }
    pub fn get_mut<Q: ?Sized>(&mut self, key: &Q) -> Option<&mut V>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.get(key)
    }
    pub fn find_lower_bound<Q: ?Sized>(&mut self, key: &Q) -> Option<&K>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.find_lower_bound(key)
    }
    pub fn find_upper_bound<Q: ?Sized>(&mut self, key: &Q) -> Option<&K>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.find_upper_bound(key)
    }
    pub fn contains_key<Q: ?Sized>(&mut self, key: &Q) -> bool
        where K: Borrow<Q>,
              Q: Ord
    {
        self.get(key).is_some()
    }
    pub fn insert(&mut self, key: K, value: V) -> Option<V> {
        self.tree.insert(key, value)
    }
    pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
        where K: Borrow<Q>,
              Q: Ord
    {
        self.tree.remove(key)
    }
    pub fn entry(&mut self, key: K) -> Entry<K, V> {
        if self.contains_key(&key) {
            Entry::Occupied(OccupiedEntry {
                key: key,
                tree: &mut self.tree,
            })
        } else {
            Entry::Vacant(VacantEntry {
                key: key,
                tree: &mut self.tree,
            })
        }
    }
}
impl<K, V> SplayMap<K, V> {
    pub fn len(&self) -> usize {
        self.tree.len
    }
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
    pub fn iter(&self) -> Iter<K, V> {
        self.tree.iter()
    }
    // TODO: iterator
}
impl<K, V> Default for SplayMap<K, V>
    where K: Ord
{
    fn default() -> Self {
        SplayMap::new()
    }
}

// TODO: move to core
pub enum Entry<'a, K: 'a, V: 'a> {
    Occupied(OccupiedEntry<'a, K, V>),
    Vacant(VacantEntry<'a, K, V>),
}
impl<'a, K: 'a, V: 'a> Entry<'a, K, V>
    where K: Ord
{
    pub fn key(&self) -> &K {
        match *self {
            Entry::Occupied(ref e) => &e.key,
            Entry::Vacant(ref e) => &e.key,
        }
    }
    pub fn or_insert(self, default: V) -> &'a mut V {
        match self {
            Entry::Occupied(e) => e.into_mut(),
            Entry::Vacant(e) => e.insert(default),
        }
    }
    pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
        match self {
            Entry::Occupied(e) => e.into_mut(),
            Entry::Vacant(e) => e.insert(default()),
        }
    }
}

pub struct OccupiedEntry<'a, K: 'a, V: 'a> {
    key: K,
    tree: &'a mut core::Tree<K, V>,
}
impl<'a, K: 'a, V: 'a> OccupiedEntry<'a, K, V>
    where K: Ord
{
    pub fn key(&self) -> &K {
        &self.key
    }
    pub fn get(&self) -> &V {
        &self.tree.root.as_ref().unwrap().val
    }
    pub fn get_mut(&mut self) -> &mut V {
        &mut self.tree.root.as_mut().unwrap().val
    }
    pub fn into_mut(self) -> &'a mut V {
        &mut self.tree.root.as_mut().unwrap().val
    }
    pub fn insert(&mut self, value: V) -> V {
        mem::replace(self.tree.get(&self.key).unwrap(), value)
    }
    pub fn remove(self) -> V {
        self.tree.remove(&self.key).unwrap()
    }
}

pub struct VacantEntry<'a, K: 'a, V: 'a> {
    key: K,
    tree: &'a mut core::Tree<K, V>,
}
impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V>
    where K: Ord
{
    pub fn key(&self) -> &K {
        &self.key
    }
    pub fn insert(self, value: V) -> &'a mut V {
        self.tree.insert(self.key, value);
        &mut self.tree.root.as_mut().unwrap().val
    }
}
<|endoftext|>


<commit_before>use bytes::Rope;
use bytes::traits::*;
use super::gen_bytes;

const TEST_BYTES_1: &'static [u8] =
    b"dblm4ng7jp4v9rdn1w6hhssmluoqrrrqj59rccl9
      nkv2tm1t2da4jyku51ge7f8hv581gkki8lekmf5f
      1l44whp4aiwbvhkziw02292on4noyvuwjzsloqyc
      5n0iyn4l6o6tgjhlek00mynfzb1wgcwj4mqp6zdr
      3625yy7rj7xuisal7b1a7xgq271abvt5ssxuj39v
      njtetokxxrgxzp7ik9adnypkmmcn4270yv9l46m7
      9mu2zmqmkxdmgia210vkdytb7ywfcyt2bvcsg9eq
      5yqizxl6888zrksvaxhzs2v355jxu8gr21m33t83
      qvoian1ra7c6pvxabshgngldxa408p18l1fdet2h";

const TEST_BYTES_2: &'static [u8] =
    b"jmh14t79mllzj1ohxfj6fun7idwbks8oh35f83g6
      ryaowe86mmou5t1xa91uyg8e95wcu5mje1mswien
      tt4clgj029cw0pyuvfbvsgzdg1x7sr9qsjkf2b1t
      h43smgp1ea22lph17f78cel0cc2kjoht5281xuy8
      0ex9uaqwj4330jrp30stsk15j9bpqezu3w78ktit
      ev5g6xsngr35q7pemdm9hihf0ebrw5fbwhm530lo
      e0zyj1bm7yfyk7f2i45jhr3wu3bvb4hj8jve6db0
      iewmr9weecaon9vdnqo5hen9iaiox5vsaxuo461m
      8336ugp20u4sfky3kfawr0ome1tiqyx8chkerrjh
      a95s0gypcsgo9jqxasqkoj08t4uq5moxmay5plg5
      tlh6f9omhn0ezvi0w2n8hx7n6qk7rn1s3mjpnpl6
      hvilp8awaa4tvsis66q4e5b3xwy2z1h2klpa87h7";

#[test]
pub fn test_rope_round_trip() {
    let rope = Rope::from_slice(b"zomg");

    assert_eq!(4, rope.len());

    let mut dst = vec![];
    rope.buf().read(&mut dst).unwrap();

    assert_eq!(b"zomg", &dst[..]);
}

#[test]
pub fn test_rope_slice() {
    let mut dst = vec![];

    let bytes = Rope::from_slice(TEST_BYTES_1);
    assert_eq!(TEST_BYTES_1.len(), bytes.len());

    bytes.buf().read(&mut dst).unwrap();
    assert_eq!(dst, TEST_BYTES_1);

    let left = bytes.slice_to(250);
    assert_eq!(250, left.len());

    left.buf().read(&mut dst).unwrap();
    assert_eq!(dst, &TEST_BYTES_1[..250]);

    let right = bytes.slice_from(250);
    assert_eq!(TEST_BYTES_1.len() - 250, right.len());

    right.buf().read(&mut dst).unwrap();
    assert_eq!(dst, &TEST_BYTES_1[250..]);
}

#[test]
pub fn test_rope_concat_two_byte_str() {
    let mut dst = vec![];

    let left = Rope::from_slice(TEST_BYTES_1);
    let right = Rope::from_slice(TEST_BYTES_2);

    let both = left.concat(&right);

    assert_eq!(both.len(), TEST_BYTES_1.len() + TEST_BYTES_2.len());

    both.buf().read(&mut dst).unwrap();
    assert_eq!(dst, TEST_BYTES_1.to_vec() + TEST_BYTES_2);
}

#[test]
#[ignore]
pub fn test_slice_parity() {
    let bytes = gen_bytes(2048 * 1024);
    let start = 512 * 1024 - 3333;
    let end = 512 * 1024 + 7777;

    let _ = Rope::from_slice(&bytes).slice(start, end);

    // stuff
}

#[test]
pub fn test_rope_equality() {
    let a = &b"Mary had a little lamb, its fleece was white as snow; ".to_bytes()
        .concat(&b"And everywhere that Mary went, the lamb was sure to go.".to_bytes());

    let b = &b"Mary had a little lamb, ".to_bytes()
        .concat(&b"its fleece was white as snow; ".to_bytes())
        .concat(
            &b"And everywhere that Mary went, ".to_bytes()
                .concat(&b"the lamb was sure to go.".to_bytes()));

    assert_eq!(a, b);
}
<commit_msg>+ isn't implemented on Vec any more<commit_after>use bytes::Rope;
use bytes::traits::*;
use super::gen_bytes;

const TEST_BYTES_1: &'static [u8] =
    b"dblm4ng7jp4v9rdn1w6hhssmluoqrrrqj59rccl9
      nkv2tm1t2da4jyku51ge7f8hv581gkki8lekmf5f
      1l44whp4aiwbvhkziw02292on4noyvuwjzsloqyc
      5n0iyn4l6o6tgjhlek00mynfzb1wgcwj4mqp6zdr
      3625yy7rj7xuisal7b1a7xgq271abvt5ssxuj39v
      njtetokxxrgxzp7ik9adnypkmmcn4270yv9l46m7
      9mu2zmqmkxdmgia210vkdytb7ywfcyt2bvcsg9eq
      5yqizxl6888zrksvaxhzs2v355jxu8gr21m33t83
      qvoian1ra7c6pvxabshgngldxa408p18l1fdet2h";

const TEST_BYTES_2: &'static [u8] =
    b"jmh14t79mllzj1ohxfj6fun7idwbks8oh35f83g6
      ryaowe86mmou5t1xa91uyg8e95wcu5mje1mswien
      tt4clgj029cw0pyuvfbvsgzdg1x7sr9qsjkf2b1t
      h43smgp1ea22lph17f78cel0cc2kjoht5281xuy8
      0ex9uaqwj4330jrp30stsk15j9bpqezu3w78ktit
      ev5g6xsngr35q7pemdm9hihf0ebrw5fbwhm530lo
      e0zyj1bm7yfyk7f2i45jhr3wu3bvb4hj8jve6db0
      iewmr9weecaon9vdnqo5hen9iaiox5vsaxuo461m
      8336ugp20u4sfky3kfawr0ome1tiqyx8chkerrjh
      a95s0gypcsgo9jqxasqkoj08t4uq5moxmay5plg5
      tlh6f9omhn0ezvi0w2n8hx7n6qk7rn1s3mjpnpl6
      hvilp8awaa4tvsis66q4e5b3xwy2z1h2klpa87h7";

#[test]
pub fn test_rope_round_trip() {
    let rope = Rope::from_slice(b"zomg");

    assert_eq!(4, rope.len());

    let mut dst = vec![];
    rope.buf().read(&mut dst).unwrap();

    assert_eq!(b"zomg", &dst[..]);
}

#[test]
pub fn test_rope_slice() {
    let mut dst = vec![];

    let bytes = Rope::from_slice(TEST_BYTES_1);
    assert_eq!(TEST_BYTES_1.len(), bytes.len());

    bytes.buf().read(&mut dst).unwrap();
    assert_eq!(dst, TEST_BYTES_1);

    let left = bytes.slice_to(250);
    assert_eq!(250, left.len());

    left.buf().read(&mut dst).unwrap();
    assert_eq!(dst, &TEST_BYTES_1[..250]);

    let right = bytes.slice_from(250);
    assert_eq!(TEST_BYTES_1.len() - 250, right.len());

    right.buf().read(&mut dst).unwrap();
    assert_eq!(dst, &TEST_BYTES_1[250..]);
}

#[test]
pub fn test_rope_concat_two_byte_str() {
    let mut dst = vec![];

    let left = Rope::from_slice(TEST_BYTES_1);
    let right = Rope::from_slice(TEST_BYTES_2);

    let both = left.concat(&right);

    assert_eq!(both.len(), TEST_BYTES_1.len() + TEST_BYTES_2.len());

    both.buf().read(&mut dst).unwrap();
    let mut expected = Vec::new();
    expected.extend(TEST_BYTES_1.iter().cloned());
    expected.extend(TEST_BYTES_2.iter().cloned());
    assert_eq!(dst, expected);
}

#[test]
#[ignore]
pub fn test_slice_parity() {
    let bytes = gen_bytes(2048 * 1024);
    let start = 512 * 1024 - 3333;
    let end = 512 * 1024 + 7777;

    let _ = Rope::from_slice(&bytes).slice(start, end);

    // stuff
}

#[test]
pub fn test_rope_equality() {
    let a = &b"Mary had a little lamb, its fleece was white as snow; ".to_bytes()
        .concat(&b"And everywhere that Mary went, the lamb was sure to go.".to_bytes());

    let b = &b"Mary had a little lamb, ".to_bytes()
        .concat(&b"its fleece was white as snow; ".to_bytes())
        .concat(
            &b"And everywhere that Mary went, ".to_bytes()
                .concat(&b"the lamb was sure to go.".to_bytes()));

    assert_eq!(a, b);
}
<|endoftext|>


<commit_before>// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! VerifierCache trait for batch verifying outputs and kernels.
//! We pass a "caching verifier" into the block validation processing with this.

use lru_cache::LruCache;

use crate::core::hash::{Hash, Hashed};
use crate::core::{Output, TxKernel};

/// Verifier cache for caching expensive verification results.
/// Specifically the following -
///   * kernel signature verification
///   * output rangeproof verification
pub trait VerifierCache: Sync + Send {
	/// Takes a vec of tx kernels and returns those kernels
	/// that have not yet been verified.
	fn filter_kernel_sig_unverified(&mut self, kernels: &[TxKernel]) -> Vec<TxKernel>;
	/// Takes a vec of tx outputs and returns those outputs
	/// that have not yet had their rangeproofs verified.
	fn filter_rangeproof_unverified(&mut self, outputs: &[Output]) -> Vec<Output>;
	/// Adds a vec of tx kernels to the cache (used in conjunction with the the filter above).
	fn add_kernel_sig_verified(&mut self, kernels: Vec<TxKernel>);
	/// Adds a vec of outputs to the cache (used in conjunction with the the filter above).
	fn add_rangeproof_verified(&mut self, outputs: Vec<Output>);
}

/// An implementation of verifier_cache using lru_cache.
/// Caches tx kernels by kernel hash.
/// Caches outputs by output rangeproof hash (rangeproofs are committed to separately).
pub struct LruVerifierCache {
	kernel_sig_verification_cache: LruCache<Hash, bool>,
	rangeproof_verification_cache: LruCache<Hash, bool>,
}

impl LruVerifierCache {
	/// TODO how big should these caches be?
	/// They need to be *at least* large enough to cover a maxed out block.
	pub fn new() -> LruVerifierCache {
		LruVerifierCache {
			kernel_sig_verification_cache: LruCache::new(50_000),
			rangeproof_verification_cache: LruCache::new(50_000),
		}
	}
}

impl VerifierCache for LruVerifierCache {
	fn filter_kernel_sig_unverified(&mut self, kernels: &[TxKernel]) -> Vec<TxKernel> {
		let res = kernels
			.iter()
			.filter(|x| {
				!*self
					.kernel_sig_verification_cache
					.get_mut(&x.hash())
					.unwrap_or(&mut false)
			})
			.cloned()
			.collect::<Vec<_>>();
		trace!(
			"lru_verifier_cache: kernel sigs: {}, not cached (must verify): {}",
			kernels.len(),
			res.len()
		);
		res
	}

	fn filter_rangeproof_unverified(&mut self, outputs: &[Output]) -> Vec<Output> {
		let res = outputs
			.iter()
			.filter(|x| {
				!*self
					.rangeproof_verification_cache
					.get_mut(&x.proof.hash())
					.unwrap_or(&mut false)
			})
			.cloned()
			.collect::<Vec<_>>();
		trace!(
			"lru_verifier_cache: rangeproofs: {}, not cached (must verify): {}",
			outputs.len(),
			res.len()
		);
		res
	}

	fn add_kernel_sig_verified(&mut self, kernels: Vec<TxKernel>) {
		for k in kernels {
			self.kernel_sig_verification_cache.insert(k.hash(), true);
		}
	}

	fn add_rangeproof_verified(&mut self, outputs: Vec<Output>) {
		for o in outputs {
			self.rangeproof_verification_cache
				.insert(o.proof.hash(), true);
		}
	}
}
<commit_msg>refactor: use less memory in verifier_cache by storing zero-sized type instead of bool<commit_after>// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! VerifierCache trait for batch verifying outputs and kernels.
//! We pass a "caching verifier" into the block validation processing with this.

use lru_cache::LruCache;

use crate::core::hash::{Hash, Hashed};
use crate::core::{Output, TxKernel};

/// Verifier cache for caching expensive verification results.
/// Specifically the following -
///   * kernel signature verification
///   * output rangeproof verification
pub trait VerifierCache: Sync + Send {
	/// Takes a vec of tx kernels and returns those kernels
	/// that have not yet been verified.
	fn filter_kernel_sig_unverified(&mut self, kernels: &[TxKernel]) -> Vec<TxKernel>;
	/// Takes a vec of tx outputs and returns those outputs
	/// that have not yet had their rangeproofs verified.
	fn filter_rangeproof_unverified(&mut self, outputs: &[Output]) -> Vec<Output>;
	/// Adds a vec of tx kernels to the cache (used in conjunction with the the filter above).
	fn add_kernel_sig_verified(&mut self, kernels: Vec<TxKernel>);
	/// Adds a vec of outputs to the cache (used in conjunction with the the filter above).
	fn add_rangeproof_verified(&mut self, outputs: Vec<Output>);
}

/// An implementation of verifier_cache using lru_cache.
/// Caches tx kernels by kernel hash.
/// Caches outputs by output rangeproof hash (rangeproofs are committed to separately).
pub struct LruVerifierCache {
	kernel_sig_verification_cache: LruCache<Hash, ()>,
	rangeproof_verification_cache: LruCache<Hash, ()>,
}

impl LruVerifierCache {
	/// TODO how big should these caches be?
	/// They need to be *at least* large enough to cover a maxed out block.
	pub fn new() -> LruVerifierCache {
		LruVerifierCache {
			kernel_sig_verification_cache: LruCache::new(50_000),
			rangeproof_verification_cache: LruCache::new(50_000),
		}
	}
}

impl VerifierCache for LruVerifierCache {
	fn filter_kernel_sig_unverified(&mut self, kernels: &[TxKernel]) -> Vec<TxKernel> {
		let res = kernels
			.iter()
			.filter(|x| !self.kernel_sig_verification_cache.contains_key(&x.hash()))
			.cloned()
			.collect::<Vec<_>>();
		trace!(
			"lru_verifier_cache: kernel sigs: {}, not cached (must verify): {}",
			kernels.len(),
			res.len()
		);
		res
	}

	fn filter_rangeproof_unverified(&mut self, outputs: &[Output]) -> Vec<Output> {
		let res = outputs
			.iter()
			.filter(|x| {
				!self
					.rangeproof_verification_cache
					.contains_key(&x.proof.hash())
			})
			.cloned()
			.collect::<Vec<_>>();
		trace!(
			"lru_verifier_cache: rangeproofs: {}, not cached (must verify): {}",
			outputs.len(),
			res.len()
		);
		res
	}

	fn add_kernel_sig_verified(&mut self, kernels: Vec<TxKernel>) {
		for k in kernels {
			self.kernel_sig_verification_cache.insert(k.hash(), ());
		}
	}

	fn add_rangeproof_verified(&mut self, outputs: Vec<Output>) {
		for o in outputs {
			self.rangeproof_verification_cache
				.insert(o.proof.hash(), ());
		}
	}
}
<|endoftext|>


<commit_before>//! Flycheck provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.

use std::{
    fmt, io,
    process::{ChildStderr, ChildStdout, Command, Stdio},
    time::Duration,
};

use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf;
use serde::Deserialize;
use stdx::{process::streaming_output, JodChild};

pub use cargo_metadata::diagnostic::{
    Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
    DiagnosticSpanMacroExpansion,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig {
    CargoCommand {
        command: String,
        target_triple: Option<String>,
        all_targets: bool,
        no_default_features: bool,
        all_features: bool,
        features: Vec<String>,
        extra_args: Vec<String>,
    },
    CustomCommand {
        command: String,
        args: Vec<String>,
    },
}

impl fmt::Display for FlycheckConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
            FlycheckConfig::CustomCommand { command, args } => {
                write!(f, "{} {}", command, args.join(" "))
            }
        }
    }
}

/// Flycheck wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct FlycheckHandle {
    // XXX: drop order is significant
    sender: Sender<Restart>,
    _thread: jod_thread::JoinHandle,
}

impl FlycheckHandle {
    pub fn spawn(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckHandle {
        let actor = FlycheckActor::new(id, sender, config, workspace_root);
        let (sender, receiver) = unbounded::<Restart>();
        let thread = jod_thread::Builder::new()
            .name("Flycheck".to_owned())
            .spawn(move || actor.run(receiver))
            .expect("failed to spawn thread");
        FlycheckHandle { sender, _thread: thread }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        self.sender.send(Restart).unwrap();
    }
}

pub enum Message {
    /// Request adding a diagnostic with fixes included to a file
    AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic },

    /// Request check progress notification to client
    Progress {
        /// Flycheck instance ID
        id: usize,
        progress: Progress,
    },
}

impl fmt::Debug for Message {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Message::AddDiagnostic { workspace_root, diagnostic } => f
                .debug_struct("AddDiagnostic")
                .field("workspace_root", workspace_root)
                .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
                .finish(),
            Message::Progress { id, progress } => {
                f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
            }
        }
    }
}

#[derive(Debug)]
pub enum Progress {
    DidStart,
    DidCheckCrate(String),
    DidFinish(io::Result<()>),
    DidCancel,
}

struct Restart;

struct FlycheckActor {
    id: usize,
    sender: Box<dyn Fn(Message) + Send>,
    config: FlycheckConfig,
    workspace_root: AbsPathBuf,
    /// CargoHandle exists to wrap around the communication needed to be able to
    /// run `cargo check` without blocking. Currently the Rust standard library
    /// doesn't provide a way to read sub-process output without blocking, so we
    /// have to wrap sub-processes output handling in a thread and pass messages
    /// back over a channel.
    cargo_handle: Option<CargoHandle>,
}

enum Event {
    Restart(Restart),
    CheckEvent(Option<CargoMessage>),
}

impl FlycheckActor {
    fn new(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckActor {
        FlycheckActor { id, sender, config, workspace_root, cargo_handle: None }
    }
    fn progress(&self, progress: Progress) {
        self.send(Message::Progress { id: self.id, progress });
    }
    fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
        let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
        select! {
            recv(inbox) -> msg => msg.ok().map(Event::Restart),
            recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
        }
    }
    fn run(mut self, inbox: Receiver<Restart>) {
        while let Some(event) = self.next_event(&inbox) {
            match event {
                Event::Restart(Restart) => {
                    if let Some(cargo_handle) = self.cargo_handle.take() {
                        // Cancel the previously spawned process
                        cargo_handle.cancel();
                    }
                    while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
                    self.progress(Progress::DidCancel);

                    let command = self.check_command();
                    tracing::debug!(?command, "will restart flycheck");
                    match CargoHandle::spawn(command) {
                        Ok(cargo_handle) => {
                            tracing::debug!(
                                command = ?self.check_command(),
                                "did  restart flycheck"
                            );
                            self.cargo_handle = Some(cargo_handle);
                            self.progress(Progress::DidStart);
                        }
                        Err(error) => {
                            tracing::error!(
                                command = ?self.check_command(),
                                %error, "failed to restart flycheck"
                            );
                        }
                    }
                }
                Event::CheckEvent(None) => {
                    tracing::debug!("flycheck finished");

                    // Watcher finished
                    let cargo_handle = self.cargo_handle.take().unwrap();
                    let res = cargo_handle.join();
                    if res.is_err() {
                        tracing::error!(
                            "Flycheck failed to run the following command: {:?}",
                            self.check_command()
                        );
                    }
                    self.progress(Progress::DidFinish(res));
                }
                Event::CheckEvent(Some(message)) => match message {
                    CargoMessage::CompilerArtifact(msg) => {
                        self.progress(Progress::DidCheckCrate(msg.target.name));
                    }

                    CargoMessage::Diagnostic(msg) => {
                        self.send(Message::AddDiagnostic {
                            workspace_root: self.workspace_root.clone(),
                            diagnostic: msg,
                        });
                    }
                },
            }
        }
        // If we rerun the thread, we need to discard the previous check results first
        self.cancel_check_process();
    }

    fn cancel_check_process(&mut self) {
        if let Some(cargo_handle) = self.cargo_handle.take() {
            cargo_handle.cancel();
            self.progress(Progress::DidCancel);
        }
    }
    fn check_command(&self) -> Command {
        let mut cmd = match &self.config {
            FlycheckConfig::CargoCommand {
                command,
                target_triple,
                no_default_features,
                all_targets,
                all_features,
                extra_args,
                features,
            } => {
                let mut cmd = Command::new(toolchain::cargo());
                cmd.arg(command);
                cmd.current_dir(&self.workspace_root);
                cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
                    .arg(self.workspace_root.join("Cargo.toml").as_os_str());

                if let Some(target) = target_triple {
                    cmd.args(&["--target", target.as_str()]);
                }
                if *all_targets {
                    cmd.arg("--all-targets");
                }
                if *all_features {
                    cmd.arg("--all-features");
                } else {
                    if *no_default_features {
                        cmd.arg("--no-default-features");
                    }
                    if !features.is_empty() {
                        cmd.arg("--features");
                        cmd.arg(features.join(" "));
                    }
                }
                cmd.args(extra_args);
                cmd
            }
            FlycheckConfig::CustomCommand { command, args } => {
                let mut cmd = Command::new(command);
                cmd.args(args);
                cmd
            }
        };
        cmd.current_dir(&self.workspace_root);
        cmd
    }

    fn send(&self, check_task: Message) {
        (self.sender)(check_task);
    }
}

/// A handle to a cargo process used for fly-checking.
struct CargoHandle {
    /// The handle to the actual cargo process. As we cannot cancel directly from with
    /// a read syscall dropping and therefor terminating the process is our best option.
    child: JodChild,
    thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
    receiver: Receiver<CargoMessage>,
}

impl CargoHandle {
    fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
        let mut child = JodChild::spawn(command)?;

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        let (sender, receiver) = unbounded();
        let actor = CargoActor::new(sender, stdout, stderr);
        let thread = jod_thread::Builder::new()
            .name("CargoHandle".to_owned())
            .spawn(move || actor.run())
            .expect("failed to spawn thread");
        Ok(CargoHandle { child, thread, receiver })
    }

    fn cancel(mut self) {
        let _ = self.child.kill();
        let _ = self.child.wait();
    }

    fn join(mut self) -> io::Result<()> {
        let _ = self.child.kill();
        let exit_status = self.child.wait()?;
        let (read_at_least_one_message, error) = self.thread.join()?;
        if read_at_least_one_message || exit_status.success() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other, format!(
                "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}",
                exit_status, error
            )))
        }
    }
}

struct CargoActor {
    sender: Sender<CargoMessage>,
    stdout: ChildStdout,
    stderr: ChildStderr,
}

impl CargoActor {
    fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor {
        CargoActor { sender, stdout, stderr }
    }

    fn run(self) -> io::Result<(bool, String)> {
        // We manually read a line at a time, instead of using serde's
        // stream deserializers, because the deserializer cannot recover
        // from an error, resulting in it getting stuck, because we try to
        // be resilient against failures.
        //
        // Because cargo only outputs one JSON object per line, we can
        // simply skip a line if it doesn't parse, which just ignores any
        // erroneus output.

        let mut error = String::new();
        let mut read_at_least_one_message = false;
        let output = streaming_output(
            self.stdout,
            self.stderr,
            &mut |line| {
                read_at_least_one_message = true;

                // Try to deserialize a message from Cargo or Rustc.
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
                    match message {
                        // Skip certain kinds of messages to only spend time on what's useful
                        JsonMessage::Cargo(message) => match message {
                            cargo_metadata::Message::CompilerArtifact(artifact)
                                if !artifact.fresh =>
                            {
                                self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
                            }
                            cargo_metadata::Message::CompilerMessage(msg) => {
                                self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
                            }
                            _ => (),
                        },
                        JsonMessage::Rustc(message) => {
                            self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
                        }
                    }
                }
            },
            &mut |line| {
                error.push_str(line);
                error.push('\n');
            },
        );
        match output {
            Ok(_) => Ok((read_at_least_one_message, error)),
            Err(e) => Err(io::Error::new(e.kind(), format!("{:?}: {}", e, error))),
        }
    }
}

enum CargoMessage {
    CompilerArtifact(cargo_metadata::Artifact),
    Diagnostic(Diagnostic),
}

#[derive(Deserialize)]
#[serde(untagged)]
enum JsonMessage {
    Cargo(cargo_metadata::Message),
    Rustc(Diagnostic),
}
<commit_msg>Auto merge of #12660 - Veykril:flycheck, r=Veykril<commit_after>//! Flycheck provides the functionality needed to run `cargo check` or
//! another compatible command (f.x. clippy) in a background thread and provide
//! LSP diagnostics based on the output of the command.

use std::{
    fmt, io,
    process::{ChildStderr, ChildStdout, Command, Stdio},
    time::Duration,
};

use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
use paths::AbsPathBuf;
use serde::Deserialize;
use stdx::{process::streaming_output, JodChild};

pub use cargo_metadata::diagnostic::{
    Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan,
    DiagnosticSpanMacroExpansion,
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig {
    CargoCommand {
        command: String,
        target_triple: Option<String>,
        all_targets: bool,
        no_default_features: bool,
        all_features: bool,
        features: Vec<String>,
        extra_args: Vec<String>,
    },
    CustomCommand {
        command: String,
        args: Vec<String>,
    },
}

impl fmt::Display for FlycheckConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command),
            FlycheckConfig::CustomCommand { command, args } => {
                write!(f, "{} {}", command, args.join(" "))
            }
        }
    }
}

/// Flycheck wraps the shared state and communication machinery used for
/// running `cargo check` (or other compatible command) and providing
/// diagnostics based on the output.
/// The spawned thread is shut down when this struct is dropped.
#[derive(Debug)]
pub struct FlycheckHandle {
    // XXX: drop order is significant
    sender: Sender<Restart>,
    _thread: jod_thread::JoinHandle,
}

impl FlycheckHandle {
    pub fn spawn(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckHandle {
        let actor = FlycheckActor::new(id, sender, config, workspace_root);
        let (sender, receiver) = unbounded::<Restart>();
        let thread = jod_thread::Builder::new()
            .name("Flycheck".to_owned())
            .spawn(move || actor.run(receiver))
            .expect("failed to spawn thread");
        FlycheckHandle { sender, _thread: thread }
    }

    /// Schedule a re-start of the cargo check worker.
    pub fn update(&self) {
        self.sender.send(Restart).unwrap();
    }
}

pub enum Message {
    /// Request adding a diagnostic with fixes included to a file
    AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic },

    /// Request check progress notification to client
    Progress {
        /// Flycheck instance ID
        id: usize,
        progress: Progress,
    },
}

impl fmt::Debug for Message {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Message::AddDiagnostic { workspace_root, diagnostic } => f
                .debug_struct("AddDiagnostic")
                .field("workspace_root", workspace_root)
                .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
                .finish(),
            Message::Progress { id, progress } => {
                f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
            }
        }
    }
}

#[derive(Debug)]
pub enum Progress {
    DidStart,
    DidCheckCrate(String),
    DidFinish(io::Result<()>),
    DidCancel,
}

struct Restart;

struct FlycheckActor {
    id: usize,
    sender: Box<dyn Fn(Message) + Send>,
    config: FlycheckConfig,
    workspace_root: AbsPathBuf,
    /// CargoHandle exists to wrap around the communication needed to be able to
    /// run `cargo check` without blocking. Currently the Rust standard library
    /// doesn't provide a way to read sub-process output without blocking, so we
    /// have to wrap sub-processes output handling in a thread and pass messages
    /// back over a channel.
    cargo_handle: Option<CargoHandle>,
}

enum Event {
    Restart(Restart),
    CheckEvent(Option<CargoMessage>),
}

impl FlycheckActor {
    fn new(
        id: usize,
        sender: Box<dyn Fn(Message) + Send>,
        config: FlycheckConfig,
        workspace_root: AbsPathBuf,
    ) -> FlycheckActor {
        FlycheckActor { id, sender, config, workspace_root, cargo_handle: None }
    }
    fn progress(&self, progress: Progress) {
        self.send(Message::Progress { id: self.id, progress });
    }
    fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
        let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver);
        select! {
            recv(inbox) -> msg => msg.ok().map(Event::Restart),
            recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())),
        }
    }
    fn run(mut self, inbox: Receiver<Restart>) {
        while let Some(event) = self.next_event(&inbox) {
            match event {
                Event::Restart(Restart) => {
                    // Cancel the previously spawned process
                    self.cancel_check_process();
                    while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}

                    let command = self.check_command();
                    tracing::debug!(?command, "will restart flycheck");
                    match CargoHandle::spawn(command) {
                        Ok(cargo_handle) => {
                            tracing::debug!(
                                command = ?self.check_command(),
                                "did  restart flycheck"
                            );
                            self.cargo_handle = Some(cargo_handle);
                            self.progress(Progress::DidStart);
                        }
                        Err(error) => {
                            tracing::error!(
                                command = ?self.check_command(),
                                %error, "failed to restart flycheck"
                            );
                        }
                    }
                }
                Event::CheckEvent(None) => {
                    tracing::debug!("flycheck finished");

                    // Watcher finished
                    let cargo_handle = self.cargo_handle.take().unwrap();
                    let res = cargo_handle.join();
                    if res.is_err() {
                        tracing::error!(
                            "Flycheck failed to run the following command: {:?}",
                            self.check_command()
                        );
                    }
                    self.progress(Progress::DidFinish(res));
                }
                Event::CheckEvent(Some(message)) => match message {
                    CargoMessage::CompilerArtifact(msg) => {
                        self.progress(Progress::DidCheckCrate(msg.target.name));
                    }

                    CargoMessage::Diagnostic(msg) => {
                        self.send(Message::AddDiagnostic {
                            workspace_root: self.workspace_root.clone(),
                            diagnostic: msg,
                        });
                    }
                },
            }
        }
        // If we rerun the thread, we need to discard the previous check results first
        self.cancel_check_process();
    }

    fn cancel_check_process(&mut self) {
        if let Some(cargo_handle) = self.cargo_handle.take() {
            cargo_handle.cancel();
            self.progress(Progress::DidCancel);
        }
    }

    fn check_command(&self) -> Command {
        let mut cmd = match &self.config {
            FlycheckConfig::CargoCommand {
                command,
                target_triple,
                no_default_features,
                all_targets,
                all_features,
                extra_args,
                features,
            } => {
                let mut cmd = Command::new(toolchain::cargo());
                cmd.arg(command);
                cmd.current_dir(&self.workspace_root);
                cmd.args(&["--workspace", "--message-format=json", "--manifest-path"])
                    .arg(self.workspace_root.join("Cargo.toml").as_os_str());

                if let Some(target) = target_triple {
                    cmd.args(&["--target", target.as_str()]);
                }
                if *all_targets {
                    cmd.arg("--all-targets");
                }
                if *all_features {
                    cmd.arg("--all-features");
                } else {
                    if *no_default_features {
                        cmd.arg("--no-default-features");
                    }
                    if !features.is_empty() {
                        cmd.arg("--features");
                        cmd.arg(features.join(" "));
                    }
                }
                cmd.args(extra_args);
                cmd
            }
            FlycheckConfig::CustomCommand { command, args } => {
                let mut cmd = Command::new(command);
                cmd.args(args);
                cmd
            }
        };
        cmd.current_dir(&self.workspace_root);
        cmd
    }

    fn send(&self, check_task: Message) {
        (self.sender)(check_task);
    }
}

/// A handle to a cargo process used for fly-checking.
struct CargoHandle {
    /// The handle to the actual cargo process. As we cannot cancel directly from with
    /// a read syscall dropping and therefor terminating the process is our best option.
    child: JodChild,
    thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
    receiver: Receiver<CargoMessage>,
}

impl CargoHandle {
    fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
        let mut child = JodChild::spawn(command)?;

        let stdout = child.stdout.take().unwrap();
        let stderr = child.stderr.take().unwrap();

        let (sender, receiver) = unbounded();
        let actor = CargoActor::new(sender, stdout, stderr);
        let thread = jod_thread::Builder::new()
            .name("CargoHandle".to_owned())
            .spawn(move || actor.run())
            .expect("failed to spawn thread");
        Ok(CargoHandle { child, thread, receiver })
    }

    fn cancel(mut self) {
        let _ = self.child.kill();
        let _ = self.child.wait();
    }

    fn join(mut self) -> io::Result<()> {
        let _ = self.child.kill();
        let exit_status = self.child.wait()?;
        let (read_at_least_one_message, error) = self.thread.join()?;
        if read_at_least_one_message || exit_status.success() {
            Ok(())
        } else {
            Err(io::Error::new(io::ErrorKind::Other, format!(
                "Cargo watcher failed, the command produced no valid metadata (exit code: {:?}):\n{}",
                exit_status, error
            )))
        }
    }
}

struct CargoActor {
    sender: Sender<CargoMessage>,
    stdout: ChildStdout,
    stderr: ChildStderr,
}

impl CargoActor {
    fn new(sender: Sender<CargoMessage>, stdout: ChildStdout, stderr: ChildStderr) -> CargoActor {
        CargoActor { sender, stdout, stderr }
    }

    fn run(self) -> io::Result<(bool, String)> {
        // We manually read a line at a time, instead of using serde's
        // stream deserializers, because the deserializer cannot recover
        // from an error, resulting in it getting stuck, because we try to
        // be resilient against failures.
        //
        // Because cargo only outputs one JSON object per line, we can
        // simply skip a line if it doesn't parse, which just ignores any
        // erroneus output.

        let mut error = String::new();
        let mut read_at_least_one_message = false;
        let output = streaming_output(
            self.stdout,
            self.stderr,
            &mut |line| {
                read_at_least_one_message = true;

                // Try to deserialize a message from Cargo or Rustc.
                let mut deserializer = serde_json::Deserializer::from_str(line);
                deserializer.disable_recursion_limit();
                if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
                    match message {
                        // Skip certain kinds of messages to only spend time on what's useful
                        JsonMessage::Cargo(message) => match message {
                            cargo_metadata::Message::CompilerArtifact(artifact)
                                if !artifact.fresh =>
                            {
                                self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap();
                            }
                            cargo_metadata::Message::CompilerMessage(msg) => {
                                self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap();
                            }
                            _ => (),
                        },
                        JsonMessage::Rustc(message) => {
                            self.sender.send(CargoMessage::Diagnostic(message)).unwrap();
                        }
                    }
                }
            },
            &mut |line| {
                error.push_str(line);
                error.push('\n');
            },
        );
        match output {
            Ok(_) => Ok((read_at_least_one_message, error)),
            Err(e) => Err(io::Error::new(e.kind(), format!("{:?}: {}", e, error))),
        }
    }
}

enum CargoMessage {
    CompilerArtifact(cargo_metadata::Artifact),
    Diagnostic(Diagnostic),
}

#[derive(Deserialize)]
#[serde(untagged)]
enum JsonMessage {
    Cargo(cargo_metadata::Message),
    Rustc(Diagnostic),
}
<|endoftext|>


<commit_before>use getopts::{optflag,optopt,getopts,usage};
use std::os;
use std::collections::HashMap;
use std::io;
use std::io::fs::PathExtensions;
use std::borrow::ToOwned;
use std::str;

use vobject::parse_component;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                println!("{}: {}", $errmsg, e);
                os::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        key.as_slice().starts_with("MATES_") && value.len() > 0
    }));
    env
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    env.get(key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

fn build_index(outfile: &Path, dir: &Path) -> io::IoResult<()> {
    if !dir.is_dir() {
        return Err(io::IoError {
            kind: io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let mut outf = io::File::create(outfile);
    let entries = try!(io::fs::readdir(dir));
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }

        print!("Processing {}\n", entry.display());

        let itemstr = try!(io::File::open(entry).read_to_string());
        let item = match parse_component(&itemstr) {
            Ok(item) => item,
            Err(e) => {
                println!("Error: Failed to parse item {}: {}\n", entry.display(), e);
                os::set_exit_status(1);
                continue;
            }
        };

        let name = match item.single_prop("FN") {
            Some(name) => name.get_raw_value(),
            None => {
                print!("Warning: No name in {}, skipping.\n", entry.display());
                continue;
            }
        };

        let emails = item.all_props("EMAIL");
        for email in emails.iter() {
            try!(outf.write_str(
                format!("{}\t{}\t{}\n", email.get_raw_value(), name, entry.display()).as_slice()
            ))
        };
    };
    return Ok(());
}

pub fn cli_main() {
    let env = get_env();

    let mut args = os::args();
    let program = args.pop().unwrap();

    let help = format!("Usage: {} COMMAND
Commands:
    index:
        Rewrite/create the index.
    mutt-query <query>:
        Search for contact, output is usable for mutt's query_command.
    file-query <query>:
        Search for contact, return just the filename.
    email-query <query>:
        Search for contact, return \"name <email>\".
    add:
        Take mail from stdin, add sender to contacts. Print filename.
    edit <file-or-query>:
        Open contact (given by filepath or search-string) in $MATES_EDITOR. If
        the file is cleared, the contact is removed.", program);

    let print_help = |&:| {
        println!("{}", help);
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    let command = args.pop().unwrap_or("".to_string());

    match command.as_slice() {
        "index" => {
            let index_file = expect_env(&env, "MATES_INDEX");
            let mates_dir = expect_env(&env, "MATES_DIR");
            println!("Rebuilding index file \"{}\"...", index_file);
            main_try!(build_index(
                &Path::new(index_file.as_slice()),
                &Path::new(mates_dir.as_slice())
            ), "Failed to build index");
        },
        "mutt-query" => {
            let query = args.pop().unwrap_or("".to_string());
            main_try!(mutt_query(env, query), "Failed to execute grep");
        },
        "file-query" => {
            let query = args.pop().unwrap_or("".to_string());
            main_try!(file_query(env, query), "Failed to execute grep");
        },
        "email-query" => {
            let query = args.pop().unwrap_or("".to_string());
            main_try!(email_query(env, query), "Failed to execute grep");
        }
        _ => {
            print_help();
            if(command != "help" && command != "--help" && command != "-h") {
                os::set_exit_status(1);
            }
        }
    };
}

fn mutt_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(index_query(env, query)) {
        if(item.email.len() > 0 && item.name.len() > 0) {
            println!("{}\t{}\t{}", item.email, item.name, item.filepath);
        };
    };
    Ok(())
}

fn file_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if(item.filepath.len() > 0) {
            println!("{}", item.filepath)
        };
    };
    Ok(())
}

fn email_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if(item.name.len() > 0 && item.email.len() > 0) {
            println!("{} <{}>", item.name, item.email)
        };
    };
    Ok(())
}

fn index_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<IndexIterator<'a>> {
    let index_file = expect_env(&env, "MATES_INDEX");
    let default_grep = "grep".to_owned();
    let grep_cmd = match env.get("MATES_GREP") {
        Some(x) => x,
        None => &default_grep
    };

    let mut process = try!(io::Command::new(grep_cmd.as_slice())
                           .arg(query.as_slice())
                           .arg(index_file.as_slice())
                           .stderr(io::process::InheritFd(2))
                           .spawn());

    let stream = match process.stdout.as_mut() {
        Some(x) => x,
        None => return Err(io::IoError {
            kind: io::IoUnavailable,
            desc: "Failed to get stdout from grep process.",
            detail: None
        })
    };

    let output = try!(stream.read_to_string());
    Ok(IndexIterator::new(&output))
}

struct IndexItem<'a> {
    pub email: String,
    pub name: String,
    pub filepath: String
}

impl<'a> IndexItem<'a> {
    fn new(line: String) -> IndexItem<'a> {
        let mut parts = line.split('\t');

        IndexItem {
            email: parts.next().unwrap_or("").to_string(),
            name: parts.next().unwrap_or("").to_string(),
            filepath: parts.next().unwrap_or("").to_string()
        }
    }
}

struct IndexIterator<'a> {
    linebuffer: Vec<String>
}

impl<'a> IndexIterator<'a> {
    fn new(output: &String) -> IndexIterator<'a> {

        let rv = output.split('\n').map(|x: &str| x.to_string()).collect();
        IndexIterator {
            linebuffer: rv
        }
    }
}

impl<'a> Iterator for IndexIterator<'a> {
    type Item = IndexItem<'a>;

    fn next(&mut self) -> Option<IndexItem<'a>> {
        match self.linebuffer.pop() {
            Some(x) => Some(IndexItem::new(x)),
            None => None
        }
    }
}
<commit_msg>Introduce useless-use-of-cat<commit_after>use getopts::{optflag,optopt,getopts,usage};
use std::os;
use std::collections::HashMap;
use std::io;
use std::io::fs::PathExtensions;
use std::borrow::ToOwned;
use std::str;

use vobject::parse_component;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                println!("{}: {}", $errmsg, e);
                os::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        key.as_slice().starts_with("MATES_") && value.len() > 0
    }));
    env
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    env.get(key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

fn build_index(outfile: &Path, dir: &Path) -> io::IoResult<()> {
    if !dir.is_dir() {
        return Err(io::IoError {
            kind: io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let mut outf = io::File::create(outfile);
    let entries = try!(io::fs::readdir(dir));
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }

        print!("Processing {}\n", entry.display());

        let itemstr = try!(io::File::open(entry).read_to_string());
        let item = match parse_component(&itemstr) {
            Ok(item) => item,
            Err(e) => {
                println!("Error: Failed to parse item {}: {}\n", entry.display(), e);
                os::set_exit_status(1);
                continue;
            }
        };

        let name = match item.single_prop("FN") {
            Some(name) => name.get_raw_value(),
            None => {
                print!("Warning: No name in {}, skipping.\n", entry.display());
                continue;
            }
        };

        let emails = item.all_props("EMAIL");
        for email in emails.iter() {
            try!(outf.write_str(
                format!("{}\t{}\t{}\n", email.get_raw_value(), name, entry.display()).as_slice()
            ))
        };
    };
    return Ok(());
}

pub fn cli_main() {
    let env = get_env();
    let mut args = os::args().into_iter();
    let program = args.next().unwrap_or("mates".to_string());

    let help = "Usage: mates COMMAND
Commands:
    index:
        Rewrite/create the index.
    mutt-query <query>:
        Search for contact, output is usable for mutt's query_command.
    file-query <query>:
        Search for contact, return just the filename.
    email-query <query>:
        Search for contact, return \"name <email>\".
    add:
        Take mail from stdin, add sender to contacts. Print filename.
    edit <file-or-query>:
        Open contact (given by filepath or search-string) in $MATES_EDITOR. If
        the file is cleared, the contact is removed.";

    let print_help = |&:| {
        println!("{}", help);
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    let command = args.next().unwrap_or("".to_string());

    match command.as_slice() {
        "index" => {
            let index_file = expect_env(&env, "MATES_INDEX");
            let mates_dir = expect_env(&env, "MATES_DIR");
            println!("Rebuilding index file \"{}\"...", index_file);
            main_try!(build_index(
                &Path::new(index_file.as_slice()),
                &Path::new(mates_dir.as_slice())
            ), "Failed to build index");
        },
        "mutt-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(mutt_query(env, query), "Failed to execute grep");
        },
        "file-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(file_query(env, query), "Failed to execute grep");
        },
        "email-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(email_query(env, query), "Failed to execute grep");
        }
        _ => {
            print_help();
            if(command != "help" && command != "--help" && command != "-h") {
                os::set_exit_status(1);
            }
        }
    };
}

fn mutt_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(index_query(env, query)) {
        if(item.email.len() > 0 && item.name.len() > 0) {
            println!("{}\t{}\t{}", item.email, item.name, item.filepath);
        };
    };
    Ok(())
}

fn file_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if(item.filepath.len() > 0) {
            println!("{}", item.filepath)
        };
    };
    Ok(())
}

fn email_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<()> {
    for item in try!(index_query(env, query)) {
        if(item.name.len() > 0 && item.email.len() > 0) {
            println!("{} <{}>", item.name, item.email)
        };
    };
    Ok(())
}

fn index_query<'a>(env: HashMap<String, String>, query: String) -> io::IoResult<IndexIterator<'a>> {
    let default_grep = "grep".to_owned();
    let grep_cmd = match env.get("MATES_GREP") {
        Some(x) => x,
        None => &default_grep
    };

    let index_path = Path::new(expect_env(&env, "MATES_INDEX"));
    let mut process = try!(io::Command::new(grep_cmd.as_slice())
        .arg(query.as_slice())
        .stderr(io::process::InheritFd(2))
        .spawn());

    {
        let mut index_fp = try!(io::File::open(&index_path));
        let mut stdin = process.stdin.take().unwrap();
        try!(stdin.write_str(try!(index_fp.read_to_string()).as_slice()));
    }

    let stream = match process.stdout.as_mut() {
        Some(x) => x,
        None => return Err(io::IoError {
            kind: io::IoUnavailable,
            desc: "Failed to get stdout from grep process.",
            detail: None
        })
    };

    let output = try!(stream.read_to_string());
    Ok(IndexIterator::new(&output))
}

struct IndexItem<'a> {
    pub email: String,
    pub name: String,
    pub filepath: String
}

impl<'a> IndexItem<'a> {
    fn new(line: String) -> IndexItem<'a> {
        let mut parts = line.split('\t');

        IndexItem {
            email: parts.next().unwrap_or("").to_string(),
            name: parts.next().unwrap_or("").to_string(),
            filepath: parts.next().unwrap_or("").to_string()
        }
    }
}

struct IndexIterator<'a> {
    linebuffer: Vec<String>
}

impl<'a> IndexIterator<'a> {
    fn new(output: &String) -> IndexIterator<'a> {

        let rv = output.split('\n').map(|x: &str| x.to_string()).collect();
        IndexIterator {
            linebuffer: rv
        }
    }
}

impl<'a> Iterator for IndexIterator<'a> {
    type Item = IndexItem<'a>;

    fn next(&mut self) -> Option<IndexItem<'a>> {
        match self.linebuffer.pop() {
            Some(x) => Some(IndexItem::new(x)),
            None => None
        }
    }
}
<|endoftext|>


<commit_before>//! Support library for `cargo xtask` command.
//!
//! See https://github.com/matklad/cargo-xtask/

pub mod not_bash;
pub mod install;
pub mod dist;
pub mod pre_commit;

pub mod codegen;
mod ast_src;

use anyhow::Context;
use std::{
    env,
    io::Write,
    path::{Path, PathBuf},
    process::{Command, Stdio},
};
use walkdir::{DirEntry, WalkDir};

use crate::{
    codegen::Mode,
    not_bash::{fs2, pushd, rm_rf, run},
};

pub use anyhow::Result;

const TOOLCHAIN: &str = "stable";

pub fn project_root() -> PathBuf {
    Path::new(
        &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
    )
    .ancestors()
    .nth(1)
    .unwrap()
    .to_path_buf()
}

pub fn rust_files(path: &Path) -> impl Iterator<Item = PathBuf> {
    let iter = WalkDir::new(path);
    return iter
        .into_iter()
        .filter_entry(|e| !is_hidden(e))
        .map(|e| e.unwrap())
        .filter(|e| !e.file_type().is_dir())
        .map(|e| e.into_path())
        .filter(|path| path.extension().map(|it| it == "rs").unwrap_or(false));

    fn is_hidden(entry: &DirEntry) -> bool {
        entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false)
    }
}

pub fn run_rustfmt(mode: Mode) -> Result<()> {
    let _dir = pushd(project_root());
    ensure_rustfmt()?;

    let check = if mode == Mode::Verify { "--check" } else { "" };
    run!("rustup run {} -- cargo fmt -- {}", TOOLCHAIN, check)?;
    Ok(())
}

fn reformat(text: impl std::fmt::Display) -> Result<String> {
    ensure_rustfmt()?;
    let mut rustfmt = Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "rustfmt", "--config-path"])
        .arg(project_root().join("rustfmt.toml"))
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;
    write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
    let output = rustfmt.wait_with_output()?;
    let stdout = String::from_utf8(output.stdout)?;
    let preamble = "Generated file, do not edit by hand, see `xtask/src/codegen`";
    Ok(format!("//! {}\n\n{}", preamble, stdout))
}

fn ensure_rustfmt() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => return Ok(()),
        _ => (),
    };
    run!("rustup toolchain install {}", TOOLCHAIN)?;
    run!("rustup component add rustfmt --toolchain {}", TOOLCHAIN)?;
    Ok(())
}

pub fn run_clippy() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_clippy().context("install clippy")?,
    };

    let allowed_lints = [
        "clippy::collapsible_if",
        "clippy::needless_pass_by_value",
        "clippy::nonminimal_bool",
        "clippy::redundant_pattern_matching",
    ];
    run!(
        "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
        TOOLCHAIN,
        allowed_lints.join(" -A ")
    )?;
    Ok(())
}

fn install_clippy() -> Result<()> {
    run!("rustup toolchain install {}", TOOLCHAIN)?;
    run!("rustup component add clippy --toolchain {}", TOOLCHAIN)?;
    Ok(())
}

pub fn run_fuzzer() -> Result<()> {
    let _d = pushd("./crates/ra_syntax");
    if run!("cargo fuzz --help").is_err() {
        run!("cargo install cargo-fuzz")?;
    };

    run!("rustup run nightly -- cargo fuzz run parser")?;
    Ok(())
}

/// Cleans the `./target` dir after the build such that only
/// dependencies are cached on CI.
pub fn run_pre_cache() -> Result<()> {
    let slow_tests_cookie = Path::new("./target/.slow_tests_cookie");
    if !slow_tests_cookie.exists() {
        panic!("slow tests were skipped on CI!")
    }
    rm_rf(slow_tests_cookie)?;

    for entry in Path::new("./target/debug").read_dir()? {
        let entry = entry?;
        if entry.file_type().map(|it| it.is_file()).ok() == Some(true) {
            // Can't delete yourself on windows :-(
            if !entry.path().ends_with("xtask.exe") {
                rm_rf(&entry.path())?
            }
        }
    }

    fs2::remove_file("./target/.rustc_info.json")?;
    let to_delete = ["ra_", "heavy_test", "xtask"];
    for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
        for entry in Path::new(dir).read_dir()? {
            let entry = entry?;
            if to_delete.iter().any(|&it| entry.path().display().to_string().contains(it)) {
                // Can't delete yourself on windows :-(
                if !entry.path().ends_with("xtask.exe") {
                    rm_rf(&entry.path())?
                }
            }
        }
    }

    Ok(())
}

pub fn run_release(dry_run: bool) -> Result<()> {
    if !dry_run {
        run!("git switch release")?;
        run!("git fetch upstream")?;
        run!("git reset --hard upstream/master")?;
        run!("git push")?;
    }

    let website_root = project_root().join("../rust-analyzer.github.io");
    let changelog_dir = website_root.join("./thisweek/_posts");

    let today = run!("date --iso")?;
    let commit = run!("git rev-parse HEAD")?;
    let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();

    let contents = format!(
        "\
= Changelog #{}
:sectanchors:
:page-layout: post

Commit: commit:{}[] +
Release: release:{}[]

== New Features

* pr:[] .

== Fixes

== Internal Improvements
",
        changelog_n, commit, today
    );

    let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
    fs2::write(&path, &contents)?;

    fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?;

    let tags = run!("git tag --list"; echo = false)?;
    let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();

    println!("\n    git log {}..HEAD --merges --reverse", prev_tag);

    Ok(())
}

fn is_release_tag(tag: &str) -> bool {
    tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
}
<commit_msg>When making a release, just promote the latest nightly<commit_after>//! Support library for `cargo xtask` command.
//!
//! See https://github.com/matklad/cargo-xtask/

pub mod not_bash;
pub mod install;
pub mod dist;
pub mod pre_commit;

pub mod codegen;
mod ast_src;

use anyhow::Context;
use std::{
    env,
    io::Write,
    path::{Path, PathBuf},
    process::{Command, Stdio},
};
use walkdir::{DirEntry, WalkDir};

use crate::{
    codegen::Mode,
    not_bash::{fs2, pushd, rm_rf, run},
};

pub use anyhow::Result;

const TOOLCHAIN: &str = "stable";

pub fn project_root() -> PathBuf {
    Path::new(
        &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()),
    )
    .ancestors()
    .nth(1)
    .unwrap()
    .to_path_buf()
}

pub fn rust_files(path: &Path) -> impl Iterator<Item = PathBuf> {
    let iter = WalkDir::new(path);
    return iter
        .into_iter()
        .filter_entry(|e| !is_hidden(e))
        .map(|e| e.unwrap())
        .filter(|e| !e.file_type().is_dir())
        .map(|e| e.into_path())
        .filter(|path| path.extension().map(|it| it == "rs").unwrap_or(false));

    fn is_hidden(entry: &DirEntry) -> bool {
        entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false)
    }
}

pub fn run_rustfmt(mode: Mode) -> Result<()> {
    let _dir = pushd(project_root());
    ensure_rustfmt()?;

    let check = if mode == Mode::Verify { "--check" } else { "" };
    run!("rustup run {} -- cargo fmt -- {}", TOOLCHAIN, check)?;
    Ok(())
}

fn reformat(text: impl std::fmt::Display) -> Result<String> {
    ensure_rustfmt()?;
    let mut rustfmt = Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "rustfmt", "--config-path"])
        .arg(project_root().join("rustfmt.toml"))
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;
    write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
    let output = rustfmt.wait_with_output()?;
    let stdout = String::from_utf8(output.stdout)?;
    let preamble = "Generated file, do not edit by hand, see `xtask/src/codegen`";
    Ok(format!("//! {}\n\n{}", preamble, stdout))
}

fn ensure_rustfmt() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => return Ok(()),
        _ => (),
    };
    run!("rustup toolchain install {}", TOOLCHAIN)?;
    run!("rustup component add rustfmt --toolchain {}", TOOLCHAIN)?;
    Ok(())
}

pub fn run_clippy() -> Result<()> {
    match Command::new("rustup")
        .args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
        .stderr(Stdio::null())
        .stdout(Stdio::null())
        .status()
    {
        Ok(status) if status.success() => (),
        _ => install_clippy().context("install clippy")?,
    };

    let allowed_lints = [
        "clippy::collapsible_if",
        "clippy::needless_pass_by_value",
        "clippy::nonminimal_bool",
        "clippy::redundant_pattern_matching",
    ];
    run!(
        "rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
        TOOLCHAIN,
        allowed_lints.join(" -A ")
    )?;
    Ok(())
}

fn install_clippy() -> Result<()> {
    run!("rustup toolchain install {}", TOOLCHAIN)?;
    run!("rustup component add clippy --toolchain {}", TOOLCHAIN)?;
    Ok(())
}

pub fn run_fuzzer() -> Result<()> {
    let _d = pushd("./crates/ra_syntax");
    if run!("cargo fuzz --help").is_err() {
        run!("cargo install cargo-fuzz")?;
    };

    run!("rustup run nightly -- cargo fuzz run parser")?;
    Ok(())
}

/// Cleans the `./target` dir after the build such that only
/// dependencies are cached on CI.
pub fn run_pre_cache() -> Result<()> {
    let slow_tests_cookie = Path::new("./target/.slow_tests_cookie");
    if !slow_tests_cookie.exists() {
        panic!("slow tests were skipped on CI!")
    }
    rm_rf(slow_tests_cookie)?;

    for entry in Path::new("./target/debug").read_dir()? {
        let entry = entry?;
        if entry.file_type().map(|it| it.is_file()).ok() == Some(true) {
            // Can't delete yourself on windows :-(
            if !entry.path().ends_with("xtask.exe") {
                rm_rf(&entry.path())?
            }
        }
    }

    fs2::remove_file("./target/.rustc_info.json")?;
    let to_delete = ["ra_", "heavy_test", "xtask"];
    for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
        for entry in Path::new(dir).read_dir()? {
            let entry = entry?;
            if to_delete.iter().any(|&it| entry.path().display().to_string().contains(it)) {
                // Can't delete yourself on windows :-(
                if !entry.path().ends_with("xtask.exe") {
                    rm_rf(&entry.path())?
                }
            }
        }
    }

    Ok(())
}

pub fn run_release(dry_run: bool) -> Result<()> {
    if !dry_run {
        run!("git switch release")?;
        run!("git fetch upstream --tags --force")?;
        run!("git reset --hard tags/nightly")?;
        run!("git push")?;
    }

    let website_root = project_root().join("../rust-analyzer.github.io");
    let changelog_dir = website_root.join("./thisweek/_posts");

    let today = run!("date --iso")?;
    let commit = run!("git rev-parse HEAD")?;
    let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count();

    let contents = format!(
        "\
= Changelog #{}
:sectanchors:
:page-layout: post

Commit: commit:{}[] +
Release: release:{}[]

== New Features

* pr:[] .

== Fixes

== Internal Improvements
",
        changelog_n, commit, today
    );

    let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n));
    fs2::write(&path, &contents)?;

    fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?;

    let tags = run!("git tag --list"; echo = false)?;
    let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap();

    println!("\n    git log {}..HEAD --merges --reverse", prev_tag);

    Ok(())
}

fn is_release_tag(tag: &str) -> bool {
    tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
}
<|endoftext|>