Mentions légales du service

Skip to content
Snippets Groups Projects
config.rs 41.25 KiB
//! liborchestra/ssh/config.rs
//! 
//! This module contains structures to parse openssh profiles. 


//------------------------------------------------------------------------------------------ IMPORTS


use std::error;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::iter::Peekable;
use std::path::PathBuf;
use std::str::CharIndices;
use tracing::{self, trace, instrument};


//------------------------------------------------------------------------------------------- ERRORS


#[derive(Debug, Clone)]
pub enum Error {
    // Leaf Errors
    Lexer(IndexedString, String),
    Parser(IndexedString, String),
    Reader(IndexedString, String),
    GettingProfile(String),
}

impl error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Lexer(ref is, ref r) => write!(
                f,
                "The lexer encountered an unexpected character:\n```\n{}\n```\nHint: {}",
                is, r
            ),
            Error::Parser(ref is, ref r) => write!(
                f,
                "The parser encountered an unexpected token:\n```\n{}\n```\nHint: {}",
                is, r
            ),
            Error::Reader(ref is, ref r) => write!(
                f,
                "The configuration reader encountered an unexpected node:\n```\n{}\n```\nHint; {}",
                is, r
            ),
            Error::GettingProfile(ref s) => {
                write!(f, "An error occurred while getting a profile: {}", s)
            }
        }
    }
}


//------------------------------------------------------------------------------------- SSH-PROFILES


#[derive(Debug, Clone, Hash, PartialEq)]
/// Represents a reduced ssh host configuration.
pub struct SshProfile {
    pub name: String,
    pub hostname: Option<String>,
    pub user: Option<String>,
    pub port: Option<usize>,
    pub proxycommand: Option<String>,
}

impl SshProfile {
    fn from(name: String) -> SshProfile {
        SshProfile {
            name,
            hostname: None,
            user: None,
            port: None,
            proxycommand: None,
        }
    }

    fn set_hostname(mut self, hostname: String) -> SshProfile {
        self.hostname.replace(hostname);
        self
    }

    fn set_user(mut self, user: String) -> SshProfile {
        self.user.replace(user);
        self
    }

    fn set_port(mut self, port: usize) -> SshProfile {
        self.port.replace(port);
        self
    }

    fn set_proxycommand(mut self, proxycommand: String) -> SshProfile {
        self.proxycommand.replace(proxycommand);
        self
    }

    fn complete(mut self) -> SshProfile {
        if self.hostname.is_none() {
            self.hostname = Some(self.name.clone());
        }
        if self.port.is_none() {
            self.port = Some(22);
        }
        if self.user.is_none() {
            let user =
                std::env::var_os("USER").map_or("user".to_owned(), |s| s.into_string().unwrap());
            self.user = Some(user);
        }
        self
    }
}


//---------------------------------------------------------------------------------- INDEXED STRINGS

/// The two following structures allow to work on located string slices. This helps to implement
/// parsing in a non-owning way.

// This structure represents a(n explicitly indexed) slice of a string. Could either represents a
// string span (if the first and second index are different), or a cursor (if the first and second
// index are the same).
#[derive(Clone, Copy)]
struct IndexedSlice<'s>(&'s str, usize, usize);

impl<'s> IndexedSlice<'s> {
   // Constructs an indexed slice which points to the beginning of the string.
    fn beginning(slice: &'s str) -> IndexedSlice<'s> {
        IndexedSlice(slice, 0, 0)
    }

    // Construct an indexed slice which points to the end of the string.
    fn end(slice: &'s str) -> IndexedSlice<'s> {
        IndexedSlice(slice, slice.len(), slice.len())
    }

    // Moves begining to a new index
    fn move_begining(&mut self, idx: usize) {
        self.1 = idx;
    }

    // Moves end to a new index
    fn move_end(&mut self, idx: usize) {
        self.2 = idx;
    }

    // Moves end to a new index
    fn move_end_by(&mut self, idx: isize) {
        match idx {
            i if i < 0 => self.2 = self._before(self.2, -i as usize),
            i if i > 0 => {
                self.2 = self._after(self.2, i as usize + 1)
            }
            _ => {}
        }
    }

    // Moves both ends to a new index
    fn move_both(&mut self, idx: usize) {
        self.1 = idx;
        self.2 = idx;
    }

    // Returns the indexed slice as an &str.
    fn as_str(&self) -> &str {
        &self.0[self.1..self.2]
    }

    // Return an owned IndexedString out of the indexed slice.
    fn to_owned(&self) -> IndexedString {
        IndexedString(self.0.to_owned(), self.1, self.2)
    }

    // Allows to retrieve the index of the character e indexes before the i-th character.
    fn _before(&self, i: usize, e: usize) -> usize {
        match self.0[..i].char_indices().rev().take(e).last() {
            Some(c) => c.0,
            None => i,
        }
    }

    // Allows to retrieve the index of the character e indexes after the i-th character.
    fn _after(&self, i: usize, e: usize) -> usize {
        match self.0[i..].char_indices().take(e).last() {
            Some(c) => c.0 + i,
            None => i,
        }
    }

}

impl<'s> fmt::Debug for IndexedSlice<'s> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let before = match self._before(self.1, 5) {
            0 => format!("{}", &self.0[..self.1]),
            b => format!("...{}", &self.0[b..self.1]),
        };
        let after = match self._after(self.2, 5) {
            i if i == self.0.len() => format!("{}", &self.0[self.2..]),
            e => format!("{}...", &self.0[self.2..e]),
        };
        let middle = match self.1 == self.2 {
            true => format!("↓"),
            false => format!("↳{}↲", &self.0[self.1..self.2]),
        };
        let output = format!("{}{}{}", before, middle, after);
        write!(f, "IndexedSlice({:?})", output)
    }
}

/// A public and owned counterpart to IndexedSlice. Used to print locations of unexpected characters
/// in errors.
#[derive(Clone)]
pub struct IndexedString(String, usize, usize);

impl IndexedString {
    fn _before(&self, i: usize, e: usize) -> usize {
        match self.0[..i].char_indices().rev().take(e).last() {
            Some(c) => c.0,
            None => i,
        }
    }
    fn _after(&self, i: usize, e: usize) -> usize {
        match self.0[i..].char_indices().take(e).last() {
            Some(c) => c.0 + i,
            None => i,
        }
    }
}

impl fmt::Debug for IndexedString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let before = match self._before(self.1, 5) {
            0 => format!("{}", &self.0[..self.1]),
            b => format!("...{}", &self.0[b..self.1]),
        };
        let after = match self._after(self.2, 5) {
            i if i == self.0.len() => format!("{}", &self.0[self.2..]),
            e => format!("{}...", &self.0[self.2..e]),
        };
        let middle = match self.1 == self.2 {
            true => format!("↓"),
            false => format!("↳{}↲", &self.0[self.1..self.2]),
        };
        let output = format!("{}{}{}", before, middle, after);
        write!(f, "IndexedSlice({:?})", output)
    }
}

impl fmt::Display for IndexedString {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let before = match self._before(self.1, 20) {
            0 => format!("{}", &self.0[..self.1]),
            b => format!("...{}", &self.0[b..self.1]),
        };
        let after = match self._after(self.2, 20) {
            i if i == self.0.len() => format!("{}", &self.0[self.2..]),
            e => format!("{}...", &self.0[self.2..e]),
        };
        let middle = match self.1 == self.2 {
            true => format!("↓"),
            false => format!("↳{}↲", &self.0[self.1..self.2]),
        };
        write!(f, "{}{}{}", before, middle, after)
    }
}


//-------------------------------------------------------------------------------------------- LEXER


/// Represents the different tokens types expected in the config file.
#[derive(Debug, Clone, PartialEq)]
enum TokenType {
    Word,
    Comment,
    NewLine,
    Indent,
}

/// A token is tagged IndexedSlice.
#[derive(Debug)]
struct Token<'s>(TokenType, IndexedSlice<'s>);

//  An iterator that streams tokens out of a string. The `consume_*` methods returns Option<Token>.
//  If the output is None, than no token should be emitted. If the output is Some, then a token
//  should be emitted.
#[derive(Derivative)]
#[derivative(Debug)]
struct Lexer<'s> {
    #[derivative(Debug="ignore")]
    string: &'s str,
    #[derivative(Debug="ignore")]
    iter: Peekable<CharIndices<'s>>,
    #[derivative(Debug="ignore")]
    exhausted: bool,
}

impl<'s> Iterator for Lexer<'s> {

    type Item = Result<Token<'s>, Error>;

    #[instrument(name="Lexer::next")]
    fn next(&mut self) -> Option<Result<Token<'s>, Error>> {
        trace!("Getting next token");
        // We loop to keep consuming when no token was emitted.
        loop {
            trace!(next_char=?self.iter.peek(), "Next character");
            // We perform a 1-lookahead to decide which consumer to use.
            let ret = match self.iter.peek() {
                Some((_, '\n')) => self.consume_newline(),
                Some((_, '\t')) => self.consume_indent(),
                Some((_, '#')) => self.consume_comment(),
                Some((_, ' ')) => self.consume_indent(),
                Some((_, _)) => self.consume_word(),
                None => return None, // If a None is seen, then the lexer iterator is consumed.
            };
            // If a token was emitted by the consumer then we return it
            if let Some(t) = ret {
                trace!(token=?t, "Token emitted");
                return Some(t);
            }
        }
    }
}

impl<'s> Lexer<'s> {
    /// Creates a lexer from a string.
    #[instrument(name="Lexer::from")]
    fn from(string: &str) -> Lexer {
        trace!("Creating Lexer instance");
        let iter = string.char_indices().peekable();
        Lexer {
            string,
            iter,
            exhausted: false,
        }
    }

    /// Consumes a newline token
    #[instrument(name="Lexer::consume_newline")]
    fn consume_newline(&mut self) -> Option<Result<Token<'s>, Error>> {
        trace!("Consuming newline");
        let mut ret = Token(TokenType::NewLine, IndexedSlice::beginning(self.string));
        // We consume the first character which should match our expectations if the dispatch is
        // working
        if let Some((b, '\n')) = self.iter.next() {
            (ret.1).move_begining(b);
            (ret.1).move_end(b);
        } else {
            panic!("Consume newline called on wrong character")
        }
        // We look ahead to retrieve the next character index
        match self.iter.peek() {
            Some((e, _)) => (ret.1).move_end(*e),
            None => (ret.1).move_end(self.string.len()),
        }
        // While next lookahead is a newline, we consume, since multiple newlines have no particular
        // meaning
        while let Some((_, '\n')) = self.iter.peek() {
            self.iter.next();
        }
        Some(Ok(ret))
    }

    /// Consumes an indent token
    #[instrument(name="Lexer::consume_indent")]
    fn consume_indent(&mut self) -> Option<Result<Token<'s>, Error>> {
        trace!("Consuming indent");
        let mut ret = Token(TokenType::Indent, IndexedSlice::beginning(self.string));
        // We consume the first character which should match our expectations if the dispatch is
        // working
        match self.iter.next() {
            Some((b, '\t')) | Some((b, ' '))=> {
                (ret.1).move_begining(b);
                (ret.1).move_end(b);
            }
            _ =>{
                panic!("Consume indent called on wrong character.")
            }
        }
        // While whitespace or tabs are encountered, we keep consuming characters.
        loop {
            match self.iter.peek() {
                Some((_, ' ')) | Some((_, '\t')) => {
                    self.iter.next();
                }
                None => {
                    (ret.1).move_end(self.string.len());
                    break;
                }
                Some((e, _)) => {
                    (ret.1).move_end(*e);
                    break;
                }
            }
        }
        Some(Ok(ret))   
    }

    /// Consumes a comment token
    #[instrument(name="Lexer::consume_comment")]
    fn consume_comment(&mut self) -> Option<Result<Token<'s>, Error>> {
        trace!("Consuming comment");
        let mut ret = Token(TokenType::Comment, IndexedSlice::beginning(self.string));
        // We consume the first character which should match our expectations if the dispatch is
        // working
        if let Some((b, '#')) = self.iter.next() {
            (ret.1).move_begining(b);
            (ret.1).move_end(b);
        } else {
            panic!("Consume Comment called on wrong character.")
        }
        // While no newline or none (eof) is encountered, we keep consuming characters.
        loop {
            match self.iter.peek() {
                Some((e, '\n')) => {
                    (ret.1).move_end(*e);
                    break;
                }
                None => {
                    (ret.1).move_end(self.string.len());
                    break;
                }
                _ => {
                    self.iter.next();
                }
            }
        }
        Some(Ok(ret))
    }

    /// Consumes a word token
    #[instrument(name="Lexer::consume_word")]
    fn consume_word(&mut self) -> Option<Result<Token<'s>, Error>> {
        trace!("Consuming word");
        let mut ret = Token(TokenType::Word, IndexedSlice::beginning(self.string));
        // We consume the first character which should match our expectations if the dispatch is
        // working
        match self.iter.next() {
            Some((_, '\n')) | Some((_, '\t')) | Some((_, '#')) | None => {
                panic!("Consume word called on wrong character");
            }
            Some((_, ' ')) => {
                panic!("Consume word called on wrong character");
            }
            Some((b, _)) => {
                (ret.1).move_begining(b);
                (ret.1).move_end(b);
            }
        }
        // While the characters are good, we consume them.
        loop {
            match self.iter.peek() {
                Some((e, '\n')) | Some((e, '\t')) | Some((e, '#')) => {
                    (ret.1).move_end(*e);
                    return Some(Ok(ret));
                }
                None => {
                    (ret.1).move_end(self.string.len());
                    return Some(Ok(ret));
                }
                Some((e, chr)) if !chr.is_ascii() => {
                    (ret.1).move_both(*e);
                    (ret.1).move_end_by(1);
                    return Some(Err(Error::Lexer(
                        ret.1.to_owned(),
                        "Character is not ascii.".to_owned(),
                    )));
                }
                Some((e, ' ')) => {
                    (ret.1).move_end(*e);
                    break;
                }
                _ => {
                    self.iter.next();
                }
            }
        }
        // While characters are whitespaces, we consume.
        loop {
            match self.iter.peek() {
                Some((_, ' ')) => {
                    self.iter.next();
                }
                _ => return Some(Ok(ret)),
            }
        }
    }

    /// Consume whitespaces
    #[instrument(name="Lexer::consume_whitespace")]
    fn consume_whitespace(&mut self) -> Option<Result<Token<'s>, Error>> {
        trace!("Consuming whitespace");
        // We consume the first whitespace
        match self.iter.next() {
            Some((_, ' ')) => {}
            _ => panic!("Consume whitespace called on wrong character"),
        }
        // And all following whitespaces
        loop {
            match self.iter.peek() {
                Some((_, ' ')) => {
                    self.iter.next();
                }
                _ => return None,
            }
        }
    }
}


//------------------------------------------------------------------------------------------- PARSER


/// Represents the different types a parsed node can be.
#[derive(Debug, PartialEq)]
enum NodeType {
    Host,
    HostNameClause,
    UserClause,
    PortClause,
    ProxyCommandClause,
}

/// A node is a tagged indexed slice.
#[derive(Debug)]
struct Node<'s>(NodeType, IndexedSlice<'s>);

/// An iterator that yields a stream of nodes out of a lexer iterator.
#[derive(Derivative)]
#[derivative(Debug)]
struct Parser<'s> {
    #[derivative(Debug="ignore")]
    string: &'s str,
    #[derivative(Debug="ignore")]
    iter: Peekable<Lexer<'s>>,
    #[derivative(Debug="ignore")]
    exhausted: bool,
}

impl<'s> Iterator for Parser<'s> {
    type Item = Result<Node<'s>, Error>;

    #[instrument(name="Parser::next")]
    fn next(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Getting next node");
        if !self.exhausted {
            loop {
                trace!(next_token=?self.iter.peek(), "Consuming a new token");
                let ret = match self.iter.peek() {
                    Some(Ok(Token(TokenType::Word, _))) => self.consume_host(),
                    Some(Ok(Token(TokenType::Indent, _))) => self.consume_clause(),
                    Some(Ok(Token(TokenType::NewLine, _))) => self.consume_newline(),
                    Some(Ok(Token(TokenType::Comment, _))) => self.consume_comment(),
                    Some(Err(_)) => return Some(Err(self.iter.next().unwrap().unwrap_err())),
                    None => return None,
                };
                if let Some(n) = ret {
                    trace!(node=?n, "Node emitted");
                    return Some(n);
                }
            }
        } else {
            None
        }
    }
}

impl<'s> Parser<'s> {

    /// Creates a parser out of a lexer.
    #[instrument(name="Parser::from_lexer")]
    fn from_lexer(lexer: Lexer) -> Parser {
        trace!("Creating parser from lexer");
        let string = lexer.string;
        let iter = lexer.peekable();
        Parser {
            string,
            iter,
            exhausted: false,
        }
    }

    /// Consumes a host line, e.g. "Host myhost\n"
    #[instrument(name="Parser::consume_host")]
    fn consume_host(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming host");
        let mut ret = Node(NodeType::Host, IndexedSlice::beginning(self.string));
        // We consume the first keyword word token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "Host" => {}
            _ => panic!("Consume host called on wrong token"),
        }
        // We consume the value token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) => {
                ret.1 = ib;
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a nickname here".to_owned(),
                )));
            }
            Some(Err(e)) => return Some(Err(e)),
            None => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    IndexedSlice::end(self.string).to_owned(),
                    "Expected a nickname here".to_owned(),
                )));
            }
        }
        // We consume extra tokens
        loop {
            match self.iter.peek() {
                Some(Ok(Token(TokenType::Word, ib))) => {
                    self.exhausted = true;
                    return Some(Err(Error::Parser(
                        ib.to_owned(),
                        "The nickname must be a single word".to_owned(),
                    )));
                }
                Some(Ok(Token(TokenType::NewLine, _))) => {
                    self.iter.next();
                    return Some(Ok(ret));
                }
                Some(Ok(Token(TokenType::Indent, _))) => {
                    self.iter.next();
                }
                Some(Ok(Token(TokenType::Comment, _))) => {
                    self.iter.next();
                    return Some(Ok(ret));
                }
                Some(Err(_)) => return None,
                None => {
                    self.iter.next();
                    return Some(Ok(ret));
                }
            }
        }
    }

    /// Consume a clause, e.g. "\tClauseKeyword ClauseValue\n"
    #[instrument(name="Parser::consume_clause")]
    fn consume_clause(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming clause");
        // We consume indent token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Indent, _))) => {}
            _ => panic!("Consume host called on wrong token"),
        }
        // We dispatch with next keyword
        match self.iter.peek() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "HostName" => {
                self.consume_hostname_clause()
            }
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "User" => {
                self.consume_user_clause()
            }
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "Port" => {
                self.consume_port_clause()
            }
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "ProxyCommand" => {
                self.consume_proxycommand_clause()
            }
            Some(Ok(Token(TokenType::NewLine, _ib))) => {
                None
            }
            Some(Ok(Token(TokenType::Word, ib))) => {
                self.exhausted = true;
                Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Only HostName, User, Port and ProxyCommand \
                     are supported."
                        .to_owned(),
                )))
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a clause name here.".to_owned(),
                )))
            }
            None => {
                self.exhausted = true;
                Some(Err(Error::Parser(
                    IndexedSlice::end(self.string).to_owned(),
                    "Expected a clause name here.".to_owned(),
                )))
            }
            Some(Err(_)) => None,
        }
    }

    /// Consumes a hostname clause, e.g. "\tHostName locahost"
    #[instrument(name="Parser::consume_hostname_clause")]
    fn consume_hostname_clause(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming hostname clause");
        let mut ret = Node(
            NodeType::HostNameClause,
            IndexedSlice::beginning(self.string),
        );
        // We consume the keyword token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "HostName" => {}
            _ => panic!("Consume hostname clause called on wrong token"),
        }
        // We consume value token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) => {
                ret.1 = ib;
            }
            Some(Ok(Token(_, ib))) => {
                ret.1 = ib;
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a word here.".to_owned(),
                )));
            }
            None => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    IndexedSlice::end(self.string).to_owned(),
                    "Expected a word here.".to_owned(),
                )));
            }
            Some(Err(e)) => return Some(Err(e)),
        }
        // We consume until newline
        match self.iter.peek() {
            Some(Ok(Token(TokenType::NewLine, _))) | None => {
                self.iter.next();
                Some(Ok(ret))
            }
            Some(Ok(Token(TokenType::Comment, _))) => {
                self.iter.next();
                Some(Ok(ret))
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a newline here".to_owned(),
                )))
            }
            Some(Err(_)) => None,
        }
    }

    /// Consumes a user clause, e.g. "\tUser me\n"
    #[instrument(name="Parser::consume_user_clause")]
    fn consume_user_clause(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming user clause");
        let mut ret = Node(NodeType::UserClause, IndexedSlice::beginning(self.string));
        // We consume the keyword token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "User" => {}
            _ => panic!("Consume user clause called on wrong token"),
        }
        // We consume value token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) => {
                ret.1 = ib;
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a word here.".to_owned(),
                )));
            }
            None => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    IndexedSlice::end(self.string).to_owned(),
                    "Expected a word here.".to_owned(),
                )));
            }
            Some(Err(e)) => return Some(Err(e)),
        }
        // We consume until newline
        match self.iter.peek() {
            Some(Ok(Token(TokenType::NewLine, _))) | None => {
                self.iter.next();
                Some(Ok(ret))
            }
            Some(Ok(Token(TokenType::Comment, _))) => {
                self.iter.next();
                Some(Ok(ret))
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a newline here.".to_owned(),
                )))
            }
            Some(Err(_)) => None,
        }
    }

    /// Consumes a port clause, e.g. "\tPort 22\n"
    #[instrument(name="Parser::consume_port_clause")]
    fn consume_port_clause(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming port clause");
        let mut ret = Node(NodeType::PortClause, IndexedSlice::beginning(self.string));
        // We consume the keyword token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "Port" => {}
            _ => panic!("Consume port clause called on wrong token"),
        }
        // We consume the value token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str().parse::<u32>().is_ok() => {
                ret.1 = ib;
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a port number here".to_owned(),
                )));
            }
            Some(Err(e)) => return Some(Err(e)),
            None => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    IndexedSlice::end(self.string).to_owned(),
                    "Expected a port number here".to_owned(),
                )));
            }
        }
        // We consume until newline
        match self.iter.peek() {
            Some(Ok(Token(TokenType::NewLine, _))) | None => {
                self.iter.next();
                Some(Ok(ret))
            }
            Some(Ok(Token(TokenType::Comment, _))) => {
                self.iter.next();
                Some(Ok(ret))
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a newline here".to_owned(),
                )))
            }
            Some(Err(_)) => None,
        }
    }

    /// Cosumes a proxycommand clause e.g. "\t\ProxyCommand ssh...\n"
    #[instrument(name="Parser::consume_proxycommand_clause")]
    fn consume_proxycommand_clause(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming proxycommand clause");
        let mut ret = Node(
            NodeType::ProxyCommandClause,
            IndexedSlice(self.string, 0, 0),
        );
        // We consume the keyword token
        match self.iter.next() {
            Some(Ok(Token(TokenType::Word, ib))) if ib.as_str() == "ProxyCommand" => {
                ret.1 = ib;
            }
            _ => panic!("Consume proxycommand clause called on wrong token"),
        }
        // We consume the first proxycommand word
        match self.iter.peek() {
            Some(Ok(Token(TokenType::Word, ib))) => {
                (ret.1).1 = ib.1;
                self.iter.next();
            }
            Some(Ok(Token(_, ib))) => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    ib.to_owned(),
                    "Expected a command here".to_owned(),
                )));
            }
            None => {
                self.exhausted = true;
                return Some(Err(Error::Parser(
                    IndexedSlice::end(self.string).to_owned(),
                    "Expected a command here".to_owned(),
                )));
            }
            Some(Err(_)) => return None,
        }
        // We consume the upcoming proxycommand words
        loop {
            match self.iter.peek() {
                Some(Ok(Token(TokenType::Word, ib))) => {
                    (ret.1).2 = ib.2;
                    self.iter.next();
                }
                Some(Ok(Token(TokenType::NewLine, _))) | None => {
                    self.iter.next();
                    return Some(Ok(ret));
                }
                Some(Ok(Token(TokenType::Comment, _))) => {
                    self.iter.next();
                    return Some(Ok(ret));
                }
                Some(Ok(Token(_, ib))) => {
                    self.exhausted = true;
                    return Some(Err(Error::Parser(
                        ib.to_owned(),
                        "Expected word, comment or newline here".to_owned(),
                    )));
                }
                Some(Err(_)) => return None,
            }
        }
    }

    /// Consumes unnecessary newlines
    #[instrument(name="Parser::consume_newline")]
    fn consume_newline(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consuming newline");
        match self.iter.next() {
            Some(Ok(Token(TokenType::NewLine, _))) => None,
            _ => panic!("Consume newline called on wrong character"),
        }
    }

    /// Consumes unnecessary commentsS
    #[instrument(name="Parser::consume_comment")]
    fn consume_comment(&mut self) -> Option<Result<Node<'s>, Error>> {
        trace!("Consumming comment");
        match self.iter.next() {
            Some(Ok(Token(TokenType::Comment, _))) => None,
            _ => panic!("Consume comment called on wrong token"),
        }
    }

}


//------------------------------------------------------------------------------------ CONFIG READER


/// This structure is an iterator over SshProfiles defined in a string following the openssh format.
/// The following clauses are supported:
/// + HostName
/// + User
/// + Port
/// + ProxyCommand
///
/// The string may contain comments.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct ConfigReader<'s> {
    #[derivative(Debug="ignore")]
    iter: Peekable<Parser<'s>>,
}

impl<'s> Iterator for ConfigReader<'s> {

    type Item = Result<SshProfile, Error>;

    #[instrument(name="ConfigReader::next")]
    fn next(&mut self) -> Option<Result<SshProfile, Error>> {
        trace!("Getting next configuration");
        loop {
            trace!(next_node=?self.iter.peek(), "Consuming a new node");
            let ret = match self.iter.peek() {
                Some(Ok(Node(NodeType::Host, _))) => self._consume_host(),
                Some(Ok(Node(_, ib))) => {
                    return Some(Err(Error::Reader(
                        ib.to_owned(),
                        "Expected Host".to_owned(),
                    )));
                }
                Some(Err(_)) => return Some(Err(self.iter.next().unwrap().unwrap_err())),
                None => return None,
            };
            if let Some(n) = ret {
                trace!(profile=?n, "Profile emitted");
                return Some(n);
            }
        }
    }
}

impl<'s> ConfigReader<'s> {
    /// Instantiates a new configuration reader out of a string.
    #[instrument(name="ConfigReader::from_str")]
    pub fn from_str(string: &'s str) -> ConfigReader<'s> {
        trace!("Creating config reader from string");
        let lexer = Lexer::from(string);
        let parser = Parser::from_lexer(lexer).peekable();
        ConfigReader { iter: parser }
    }

    /// Consumes a host, e.g. a complete host declaration with starting line and clauses.
    #[instrument(name="ConfigReader::_consume_host")]
    fn _consume_host(&mut self) -> Option<Result<SshProfile, Error>> {
        trace!("Consuming host");
        // We consume the name
        let mut profile = match self.iter.next() {
            Some(Ok(Node(NodeType::Host, ib))) => SshProfile::from(ib.as_str().to_owned()),
            _ => panic!("Consume host called on wrong node"),
        };
        // We consume clause until new host
        loop {
            match self.iter.peek() {
                Some(Ok(Node(NodeType::HostNameClause, ib))) => {
                    profile = profile.set_hostname(ib.as_str().to_owned());
                    self.iter.next();
                }
                Some(Ok(Node(NodeType::UserClause, ib))) => {
                    profile = profile.set_user(ib.as_str().to_owned());
                    self.iter.next();
                }
                Some(Ok(Node(NodeType::PortClause, ib))) => {
                    profile = profile.set_port(ib.as_str().parse::<usize>().unwrap());
                    self.iter.next();
                }
                Some(Ok(Node(NodeType::ProxyCommandClause, ib))) => {
                    profile = profile.set_proxycommand(ib.as_str().to_owned());
                    self.iter.next();
                }
                Some(Ok(Node(NodeType::Host, _))) | None => {
                    return Some(Ok(profile));
                }
                Some(Err(_)) => return None,
            }
        }
    }
}

/// This convenient function allows to parse a config file and retrieve a profile if it exists.
#[instrument(name="ConfigReader::get_profile")]
pub fn get_profile(config_path: &PathBuf, name: &str) -> Result<SshProfile, Error> {
    trace!("Getting profile");
    let mut profiles = File::open(config_path).map_err(|_| {
        Error::GettingProfile(format!(
            "Failed to open the file {}",
            config_path.to_str().unwrap()
        ))
    })?;
    let mut profiles_string = String::new();
    profiles.read_to_string(&mut profiles_string).map_err(|_| {
        Error::GettingProfile(format!(
            "Failed to read file {}",
            config_path.to_str().unwrap()
        ))
    })?;
    let initial_err = Err(Error::GettingProfile(format!(
        "There is no {} profile in {}",
        name,
        config_path.to_str().unwrap()
    )));
    let profile = ConfigReader::from_str(&profiles_string).fold(initial_err, |res, r| {
        if r.is_err() {
            r
        } else if r.as_ref().unwrap().name == name {
            Ok(r.unwrap())
        } else {
            res
        }
    })?;
    Ok(profile.complete())
}


//--------------------------------------------------------------------------------------------- TEST


#[cfg(test)]
mod tests {
    use super::*;
    use tracing_subscriber::fmt::Subscriber;
    use tracing::Level;

    fn init(){
        let subscriber = Subscriber::builder()
            //.compact()
            .with_max_level(Level::TRACE)
            .without_time()
            .with_target(false)
            .finish();
        tracing::subscriber::set_global_default(subscriber).unwrap();
    }

    #[test]
    fn test_lexer() {

        init();

        let a = "\tHost \t plafrim   #kalbfezjk \t jjja -p  \n\n\n   ".to_owned();
        let mut lexer = Lexer::from(&a);
        let n = lexer.next().unwrap().unwrap();
        println!("n1: {:?}", n);
        assert_eq!(n.0, TokenType::Indent);
        assert_eq!(n.1.as_str(), "\t");
        let n = lexer.next().unwrap().unwrap();
        println!("n2: {:?}", n);
        assert_eq!(n.0, TokenType::Word);
        assert_eq!(n.1.as_str(), "Host");
        let n = lexer.next().unwrap().unwrap();
        println!("n3: {:?}", n);
        assert_eq!(n.0, TokenType::Indent);
        assert_eq!(n.1.as_str(), "\t ");
        let n = lexer.next().unwrap().unwrap();
        println!("n4: {:?}", n);
        assert_eq!(n.0, TokenType::Word);
        assert_eq!(n.1.as_str(), "plafrim");
        let n = lexer.next().unwrap().unwrap();
        println!("n5: {:?}", n);
        assert_eq!(n.0, TokenType::Comment);
        assert_eq!(n.1.as_str(), "#kalbfezjk \t jjja -p  ");
        let n = lexer.next().unwrap().unwrap();
        println!("n6: {:?}", n);
        assert_eq!(n.0, TokenType::NewLine);
        assert_eq!(n.1.as_str(), "\n");
        let n = lexer.next().unwrap().unwrap();
        println!("n7: {:?}", n);
        assert_eq!(n.0, TokenType::Indent);
        assert_eq!(n.1.as_str(), "   ");
        assert!(lexer.next().is_none());
    }

    #[test]
    fn test_lexing_error() {
        init();
        let a = "Höst pla\n\tHostName plafrim".to_owned();
        let mut lexer = Lexer::from(&a);
        let n = lexer.next().unwrap().unwrap_err();
        println!("n: {}", n);
    }


    #[test]
    fn test_parser() {
        init();
        let a = "# my configurations\n\
                 Host localhost #Kikou \n HostName localhost # comments \t \n\
                 # Some comments\n\n\
                 \t User apere #some comments \n\
                 \t\tPort 222 #not classic\n\
                 \tProxyCommand ssh -A -l apere localhost -W localhost:22 # some comments"
            .to_owned();
        let lexer = Lexer::from(&a);
        let mut parser = Parser::from_lexer(lexer);
        let n = parser.next().unwrap().unwrap();
        println!("n1: {:?}", n);
        assert_eq!(n.0, NodeType::Host);
        assert_eq!(n.1.as_str(), "localhost");
        let n = parser.next().unwrap().unwrap();
        println!("n2: {:?}", n);
        assert_eq!(n.0, NodeType::HostNameClause);
        assert_eq!(n.1.as_str(), "localhost");
        let n = parser.next().unwrap().unwrap();
        println!("n3: {:?}", n);
        assert_eq!(n.0, NodeType::UserClause);
        assert_eq!(n.1.as_str(), "apere");
        let n = parser.next().unwrap().unwrap();
        println!("n4: {:?}", n);
        assert_eq!(n.0, NodeType::PortClause);
        assert_eq!(n.1.as_str(), "222");
        let n = parser.next().unwrap().unwrap();
        println!("n5: {:?}", n);
        assert_eq!(n.0, NodeType::ProxyCommandClause);
        assert_eq!(n.1.as_str(), "ssh -A -l apere localhost -W localhost:22");
        assert!(parser.next().is_none());
    }

    #[test]
    fn test_parsing_error() {
        let a = "\tPort 222a".to_owned();
        let lexer = Lexer::from(&a);
        let mut parser = Parser::from_lexer(lexer);
        let n = parser.next().unwrap().unwrap_err();
        println!("n: {}", n);
    }

    #[test]
    fn test_config_reader() {
        init();
        let a = "# My configurations\n\
            \t \n\
            Host test # first profile for testing purpose\n\
            \tHostName localhost\n\
            \tPort 222 # not the usual port \n\
            \n\
            \tUser apere\n\
            \tProxyCommand ssh -a -l blahblah bhal \n\
            \n\
            # The second profile\n\
            Host test2\n\
            \tUser apere\n\
            \n\
            \n\
            #The last one which is wrong\n\
            Host blahblah\n
            \tPort 222a\n"
            .to_owned();
        let mut reader = ConfigReader::from_str(&a);
        let n = reader.next().unwrap().unwrap();
        println!("n: {:?}", n);
        assert_eq!(n.name, "test");
        assert_eq!(n.hostname.as_ref().unwrap(), "localhost");
        assert_eq!(n.user.as_ref().unwrap(), "apere");
        assert_eq!(*n.port.as_ref().unwrap(), 222 as usize);
        assert_eq!(n.proxycommand.as_ref().unwrap(), "ssh -a -l blahblah bhal");
        let n = reader.next().unwrap().unwrap();
        println!("n: {:?}", n);
        assert_eq!(n.name, "test2");
        assert!(n.hostname.is_none());
        assert_eq!(n.user.as_ref().unwrap(), "apere");
        assert!(n.port.is_none());
        assert!(n.proxycommand.is_none());
        let n = reader.next().unwrap().unwrap_err();
        println!("error: {}", n);
        assert!(reader.next().is_none());
    }


}