<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

use clap::{App, Arg, SubCommand};
use time;
use self::InputType::{F32, I16};
use self::Mode::{ConstMode, TrackMode};

use std::fmt;
use std::process::exit;

pub enum Mode {
    ConstMode,
    TrackMode,
}

#[derive(Copy)]
#[derive(Clone)]
pub enum InputType {
    F32,
    I16,
}

impl fmt::Display for InputType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            InputType::F32 => {write!(f, "f32")},
            InputType::I16 => {write!(f, "i16")},
        }
    }
}

#[derive(Debug)]
#[derive(Copy)]
#[derive(Clone)]
pub struct Location {
    pub lat: f64,
    pub lon: f64,
    pub alt: i32,
}

pub struct ConstModeArgs {
    pub shift: Option<i32>,
}

pub struct TrackModeArgs {
    pub tlefile: Option<String>,
    pub tlename: Option<String>,
    pub location: Option<Location>,
    pub time: Option<time::Tm>,
    pub frequency: Option<u32>,
    pub offset: Option<i32>,
}

pub struct CommandArgs {
    pub mode: Option<Mode>,

    pub samplerate: Option<u32>,
    pub inputtype: Option<InputType>,

    pub constargs: ConstModeArgs,
    pub trackargs: TrackModeArgs,
}

fn parse_location(location: &str) -> Result<Location, String> {
    if location.contains("lat") && location.contains("lon") && location.contains("alt"){
        let split = location.split(",");

        let mut lat: Option<f64> = None;
        let mut lon: Option<f64> = None;
        let mut alt: Option<i32> = None;

        for s in split {
            if s.contains("lat") && s.contains("=") {
                lat = s.split("=").nth(1).unwrap().parse::<f64>().ok();
            }
            else if s.contains("lon") && s.contains("=") {
                lon = s.split("=").nth(1).unwrap().parse::<f64>().ok();
            }
            else if s.contains("alt") && s.contains("=") {
                alt = s.split("=").nth(1).unwrap().parse::<i32>().ok();
            }
        }

        if lat.is_some() && lon.is_some() && alt.is_some() {
            Ok(Location{lat: lat.unwrap(), lon: lon.unwrap(), alt: alt.unwrap()})
        }
        else {
            Err(format!("{} isn't a valid value for --location\n\t[use as: lat=58.64560,lon=23.15163,alt=8]", location).to_string())
        }
    }
    else {
        Err("--location should be defined as: lat=58.64560,lon=23.15163,alt=8".to_string())
    }
}

pub fn args() -> CommandArgs {
    let matches = App::new("doppler")
                .author("Andres Vahter <andres.vahter@gmail.com>")
                .version(env!("CARGO_PKG_VERSION"))
                .about("Compensates IQ data stream doppler shift based on TLE information and constant shift for IQ data is also possible.")

                .subcommand(SubCommand::new("const")
                    .about("Constant shift mode")

                    .arg(Arg::with_name("SAMPLERATE")
                       .long("samplerate")
                       .short("s")
                       .help("IQ data samplerate")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("INTYPE")
                       .long("intype")
                       .short("i")
                       .help("IQ data type")
                       .required(true)
                       .possible_values(vec!["i16", "f32"])
                       .takes_value(true))

                    .arg(Arg::with_name("SHIFT")
                       .long("shift")
                       .help("frequency shift in Hz")
                       .required(true)
                       .takes_value(true)))

                .subcommand(SubCommand::new("track")
                    .about("Doppler tracking mode")

                    .arg(Arg::with_name("SAMPLERATE")
                       .long("samplerate")
                       .short("s")
                       .help("IQ data samplerate")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("INTYPE")
                       .long("intype")
                       .short("i")
                       .help("IQ data type")
                       .required(true)
                       .possible_values(vec!["i16", "f32"])
                       .takes_value(true))

                    .arg(Arg::with_name("TLEFILE")
                       .long("tlefile")
                       .help("TLE file: eg. http://www.celestrak.com/NORAD/elements/cubesat.txt")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("TLENAME")
                       .long("tlename")
                       .help("TLE name in TLE file: eg. ESTCUBE 1")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("LOCATION")
                       .long("location")
                       .help("Observer location: eg. lat=58.64560,lon=23.15163,alt=8")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("TIME")
                       .long("time")
                       .help("Observation start time in UTC Y-m-dTH:M:S: eg. 2015-05-13T14:28:48. If not specified current time is used")
                       .required(false)
                       .takes_value(true))

                    .arg(Arg::with_name("FREQUENCY")
                       .long("frequency")
                       .help("Satellite transmitter frequency in Hz")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("OFFSET")
                       .long("offset")
                       .help("Constant frequency shift in Hz. Can be used to compensate constant offset")
                       .required(false)
                       .takes_value(true)))

                .get_matches();

    let mut args = CommandArgs {
                    mode : None,

                    samplerate : None,
                    inputtype : None,

                    constargs : ConstModeArgs {
                        shift: None,
                    },

                    trackargs : TrackModeArgs {
                        tlefile : None,
                        tlename : None,
                        location: None,
                        time : None,
                        frequency : None,
                        offset : None,
                    },
                };

    match matches.subcommand_name() {
        Some("const")   => {
            args.mode = Some(ConstMode);
            let submatches = matches.subcommand_matches("const").unwrap();
            args.samplerate = Some(value_t_or_exit!(submatches.value_of("SAMPLERATE"), u32));

            match submatches.value_of("INTYPE").unwrap() {
                "f32" => {args.inputtype = Some(F32);},
                "i16" => {args.inputtype = Some(I16);},
                _ => unreachable!()
            }

            args.constargs.shift = Some(value_t_or_exit!(submatches.value_of("SHIFT"), i32));
        },

        Some("track") => {
            args.mode = Some(TrackMode);
            let submatches = matches.subcommand_matches("track").unwrap();
            args.samplerate = Some(value_t_or_exit!(submatches.value_of("SAMPLERATE"), u32));

            match submatches.value_of("INTYPE").unwrap() {
                "f32" => {args.inputtype = Some(F32);},
                "i16" => {args.inputtype = Some(I16);},
                _ => unreachable!()
            }

            if submatches.is_present("OFFSET") {
                args.trackargs.offset = Some(value_t_or_exit!(submatches.value_of("OFFSET"), i32));
            }

            if submatches.is_present("TIME") {
                let tm = time::strptime(submatches.value_of("TIME").unwrap(), "%Y-%m-%dT%H:%M:%S");
                match tm {
                    Ok(_) => {},
                    Err(e) => {
                        println!("{}.", e);
                        println!("--time should be defined in Y-m-dTH:M:S format: eg. 2015-05-13T14:28:48");
                        exit(1);
                    },
                };

                args.trackargs.time = Some(tm.unwrap());
            }

            args.trackargs.tlefile = Some(submatches.value_of("TLEFILE").unwrap().to_string());
            args.trackargs.tlename = Some(submatches.value_of("TLENAME").unwrap().to_string());
            args.trackargs.frequency = Some(value_t_or_exit!(submatches.value_of("FREQUENCY"), u32));

            let location = parse_location(&submatches.value_of("LOCATION").unwrap().to_string());
            match location {
                Ok(loc) => { args.trackargs.location = Some(loc);},
                Err(e) => {
                    println!("{}.", e);
                    exit(1);
                }
            }
        },

        _ => unreachable!()
    }

    args
}

<commit_msg>replace separate Clone Copy with oneliner #[derive(Clone, Copy)]<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

use clap::{App, Arg, SubCommand};
use time;
use self::InputType::{F32, I16};
use self::Mode::{ConstMode, TrackMode};

use std::fmt;
use std::process::exit;

pub enum Mode {
    ConstMode,
    TrackMode,
}

#[derive(Clone, Copy)]
pub enum InputType {
    F32,
    I16,
}

impl fmt::Display for InputType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            InputType::F32 => {write!(f, "f32")},
            InputType::I16 => {write!(f, "i16")},
        }
    }
}

#[derive(Debug)]
#[derive(Clone, Copy)]
pub struct Location {
    pub lat: f64,
    pub lon: f64,
    pub alt: i32,
}

pub struct ConstModeArgs {
    pub shift: Option<i32>,
}

pub struct TrackModeArgs {
    pub tlefile: Option<String>,
    pub tlename: Option<String>,
    pub location: Option<Location>,
    pub time: Option<time::Tm>,
    pub frequency: Option<u32>,
    pub offset: Option<i32>,
}

pub struct CommandArgs {
    pub mode: Option<Mode>,

    pub samplerate: Option<u32>,
    pub inputtype: Option<InputType>,

    pub constargs: ConstModeArgs,
    pub trackargs: TrackModeArgs,
}

fn parse_location(location: &str) -> Result<Location, String> {
    if location.contains("lat") && location.contains("lon") && location.contains("alt"){
        let split = location.split(",");

        let mut lat: Option<f64> = None;
        let mut lon: Option<f64> = None;
        let mut alt: Option<i32> = None;

        for s in split {
            if s.contains("lat") && s.contains("=") {
                lat = s.split("=").nth(1).unwrap().parse::<f64>().ok();
            }
            else if s.contains("lon") && s.contains("=") {
                lon = s.split("=").nth(1).unwrap().parse::<f64>().ok();
            }
            else if s.contains("alt") && s.contains("=") {
                alt = s.split("=").nth(1).unwrap().parse::<i32>().ok();
            }
        }

        if lat.is_some() && lon.is_some() && alt.is_some() {
            Ok(Location{lat: lat.unwrap(), lon: lon.unwrap(), alt: alt.unwrap()})
        }
        else {
            Err(format!("{} isn't a valid value for --location\n\t[use as: lat=58.64560,lon=23.15163,alt=8]", location).to_string())
        }
    }
    else {
        Err("--location should be defined as: lat=58.64560,lon=23.15163,alt=8".to_string())
    }
}

pub fn args() -> CommandArgs {
    let matches = App::new("doppler")
                .author("Andres Vahter <andres.vahter@gmail.com>")
                .version(env!("CARGO_PKG_VERSION"))
                .about("Compensates IQ data stream doppler shift based on TLE information and constant shift for IQ data is also possible.")

                .subcommand(SubCommand::new("const")
                    .about("Constant shift mode")

                    .arg(Arg::with_name("SAMPLERATE")
                       .long("samplerate")
                       .short("s")
                       .help("IQ data samplerate")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("INTYPE")
                       .long("intype")
                       .short("i")
                       .help("IQ data type")
                       .required(true)
                       .possible_values(vec!["i16", "f32"])
                       .takes_value(true))

                    .arg(Arg::with_name("SHIFT")
                       .long("shift")
                       .help("frequency shift in Hz")
                       .required(true)
                       .takes_value(true)))

                .subcommand(SubCommand::new("track")
                    .about("Doppler tracking mode")

                    .arg(Arg::with_name("SAMPLERATE")
                       .long("samplerate")
                       .short("s")
                       .help("IQ data samplerate")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("INTYPE")
                       .long("intype")
                       .short("i")
                       .help("IQ data type")
                       .required(true)
                       .possible_values(vec!["i16", "f32"])
                       .takes_value(true))

                    .arg(Arg::with_name("TLEFILE")
                       .long("tlefile")
                       .help("TLE file: eg. http://www.celestrak.com/NORAD/elements/cubesat.txt")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("TLENAME")
                       .long("tlename")
                       .help("TLE name in TLE file: eg. ESTCUBE 1")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("LOCATION")
                       .long("location")
                       .help("Observer location: eg. lat=58.64560,lon=23.15163,alt=8")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("TIME")
                       .long("time")
                       .help("Observation start time in UTC Y-m-dTH:M:S: eg. 2015-05-13T14:28:48. If not specified current time is used")
                       .required(false)
                       .takes_value(true))

                    .arg(Arg::with_name("FREQUENCY")
                       .long("frequency")
                       .help("Satellite transmitter frequency in Hz")
                       .required(true)
                       .takes_value(true))

                    .arg(Arg::with_name("OFFSET")
                       .long("offset")
                       .help("Constant frequency shift in Hz. Can be used to compensate constant offset")
                       .required(false)
                       .takes_value(true)))

                .get_matches();

    let mut args = CommandArgs {
                    mode : None,

                    samplerate : None,
                    inputtype : None,

                    constargs : ConstModeArgs {
                        shift: None,
                    },

                    trackargs : TrackModeArgs {
                        tlefile : None,
                        tlename : None,
                        location: None,
                        time : None,
                        frequency : None,
                        offset : None,
                    },
                };

    match matches.subcommand_name() {
        Some("const")   => {
            args.mode = Some(ConstMode);
            let submatches = matches.subcommand_matches("const").unwrap();
            args.samplerate = Some(value_t_or_exit!(submatches.value_of("SAMPLERATE"), u32));

            match submatches.value_of("INTYPE").unwrap() {
                "f32" => {args.inputtype = Some(F32);},
                "i16" => {args.inputtype = Some(I16);},
                _ => unreachable!()
            }

            args.constargs.shift = Some(value_t_or_exit!(submatches.value_of("SHIFT"), i32));
        },

        Some("track") => {
            args.mode = Some(TrackMode);
            let submatches = matches.subcommand_matches("track").unwrap();
            args.samplerate = Some(value_t_or_exit!(submatches.value_of("SAMPLERATE"), u32));

            match submatches.value_of("INTYPE").unwrap() {
                "f32" => {args.inputtype = Some(F32);},
                "i16" => {args.inputtype = Some(I16);},
                _ => unreachable!()
            }

            if submatches.is_present("OFFSET") {
                args.trackargs.offset = Some(value_t_or_exit!(submatches.value_of("OFFSET"), i32));
            }

            if submatches.is_present("TIME") {
                let tm = time::strptime(submatches.value_of("TIME").unwrap(), "%Y-%m-%dT%H:%M:%S");
                match tm {
                    Ok(_) => {},
                    Err(e) => {
                        println!("{}.", e);
                        println!("--time should be defined in Y-m-dTH:M:S format: eg. 2015-05-13T14:28:48");
                        exit(1);
                    },
                };

                args.trackargs.time = Some(tm.unwrap());
            }

            args.trackargs.tlefile = Some(submatches.value_of("TLEFILE").unwrap().to_string());
            args.trackargs.tlename = Some(submatches.value_of("TLENAME").unwrap().to_string());
            args.trackargs.frequency = Some(value_t_or_exit!(submatches.value_of("FREQUENCY"), u32));

            let location = parse_location(&submatches.value_of("LOCATION").unwrap().to_string());
            match location {
                Ok(loc) => { args.trackargs.location = Some(loc);},
                Err(e) => {
                    println!("{}.", e);
                    exit(1);
                }
            }
        },

        _ => unreachable!()
    }

    args
}

<|endoftext|>


<commit_before>use std::error;
use std::fmt::{self, Display, Formatter};
use std::io::{self, ErrorKind};

use rmp::decode::{MarkerReadError, ValueReadError};

pub mod value;
pub mod value_ref;

pub use self::value::read_value;
pub use self::value_ref::read_value_ref;

/// This type represents all possible errors that can occur when deserializing a value.
#[derive(Debug)]
pub enum Error {
    /// Error while reading marker byte.
    InvalidMarkerRead(io::Error),
    /// Error while reading data.
    InvalidDataRead(io::Error),
}

impl Error {
    pub fn kind(&self) -> ErrorKind {
        match *self {
            Error::InvalidMarkerRead(ref err) => err.kind(),
            Error::InvalidDataRead(ref err) => err.kind(),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::InvalidMarkerRead(..) => "I/O error while reading marker byte",
            Error::InvalidDataRead(..) => "I/O error while reading non-marker bytes",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::InvalidMarkerRead(ref err) => Some(err),
            Error::InvalidDataRead(ref err) => Some(err),
        }
    }
}

impl Display for Error {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::InvalidMarkerRead(ref err) => {
                write!(fmt, "I/O error while reading marker byte: {}", err)
            }
            Error::InvalidDataRead(ref err) => {
                write!(fmt, "I/O error while reading non-marker bytes: {}", err)
            }
        }
    }
}

impl From<MarkerReadError> for Error {
    fn from(err: MarkerReadError) -> Error {
        Error::InvalidMarkerRead(err.0)
    }
}

impl From<ValueReadError> for Error {
    fn from(err: ValueReadError) -> Error {
        match err {
            ValueReadError::InvalidMarkerRead(err) => Error::InvalidMarkerRead(err),
            ValueReadError::InvalidDataRead(err) => Error::InvalidDataRead(err),
            ValueReadError::TypeMismatch(..) => {
                Error::InvalidMarkerRead(io::Error::new(ErrorKind::Other, "type mismatch"))
            }
        }
    }
}

impl From<Error> for io::Error {
    fn from(err: Error) -> io::Error {
        match err {
            Error::InvalidMarkerRead(err) |
            Error::InvalidDataRead(err) => err,
        }
    }
}
<commit_msg>refactor: swap into symmetric conversion trait<commit_after>use std::error;
use std::fmt::{self, Display, Formatter};
use std::io::{self, ErrorKind};

use rmp::decode::{MarkerReadError, ValueReadError};

pub mod value;
pub mod value_ref;

pub use self::value::read_value;
pub use self::value_ref::read_value_ref;

/// This type represents all possible errors that can occur when deserializing a value.
#[derive(Debug)]
pub enum Error {
    /// Error while reading marker byte.
    InvalidMarkerRead(io::Error),
    /// Error while reading data.
    InvalidDataRead(io::Error),
}

impl Error {
    pub fn kind(&self) -> ErrorKind {
        match *self {
            Error::InvalidMarkerRead(ref err) => err.kind(),
            Error::InvalidDataRead(ref err) => err.kind(),
        }
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::InvalidMarkerRead(..) => "I/O error while reading marker byte",
            Error::InvalidDataRead(..) => "I/O error while reading non-marker bytes",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            Error::InvalidMarkerRead(ref err) => Some(err),
            Error::InvalidDataRead(ref err) => Some(err),
        }
    }
}

impl Display for Error {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::InvalidMarkerRead(ref err) => {
                write!(fmt, "I/O error while reading marker byte: {}", err)
            }
            Error::InvalidDataRead(ref err) => {
                write!(fmt, "I/O error while reading non-marker bytes: {}", err)
            }
        }
    }
}

impl From<MarkerReadError> for Error {
    fn from(err: MarkerReadError) -> Error {
        Error::InvalidMarkerRead(err.0)
    }
}

impl From<ValueReadError> for Error {
    fn from(err: ValueReadError) -> Error {
        match err {
            ValueReadError::InvalidMarkerRead(err) => Error::InvalidMarkerRead(err),
            ValueReadError::InvalidDataRead(err) => Error::InvalidDataRead(err),
            ValueReadError::TypeMismatch(..) => {
                Error::InvalidMarkerRead(io::Error::new(ErrorKind::Other, "type mismatch"))
            }
        }
    }
}

impl Into<io::Error> for Error {
    fn into(self) -> io::Error {
        match self {
            Error::InvalidMarkerRead(err) |
            Error::InvalidDataRead(err) => err,
        }
    }
}
<|endoftext|>


<commit_before>//! The parser is based on the following grammar
//!
//! Equation ==> Product { "+" Product }
//!           |  Product { "-" Product }
//!
//! Product  ==> Factor { "*" Factor }
//!           |  Factor { "/" Factor }
//!
//! Factor   ==> "-" Factor
//!           |  Exponent { "^" Factor }
//!
//! Exponent ==> Number { "!" }
//!
//! Number   ==> Function "(" Equation ")"
//!           |  Constant
//!           |  "(" Equation ")"
//!           |  "|" Equation "|"
//!           |  Digit { Digit } | Digit { Digit } "." { Digit }
//!
//! Function ==> "sin" | "cos" | "tan" | "asin" | "acos" | "atan" | "sqrt" | "abs" | "exp"
//!           |  "ln" | "log"
//!
//! Constant ==> "pi" | "e" | "phi"
//!
//! Digit    ==> "0".."9"

use std::vec::IntoIter;
use std::iter::Peekable;
use errors::{CalcrResult, CalcrError};
use ast::Ast;
use ast::AstVal;
use ast::AstBranch::*;
use ast::FuncKind::*;
use ast::ConstKind::*;
use ast::OpKind::*;
use token::Token;
use token::TokVal;
use token::TokVal::*;

pub fn parse_equation(tokens: Vec<Token>) -> CalcrResult<Ast> {
    let end_pos = tokens.last().and_then(|tok| Some(tok.span.1)).unwrap_or(0);
    let mut parser = Parser {
        iter: tokens.into_iter().peekable(),
        paren_level: 0,
        abs_level: 0,
        end_pos: end_pos,
    };
    parser.parse_equation()
}

pub struct Parser {
    iter: Peekable<IntoIter<Token>>,
    paren_level: u32,
    abs_level: u32,
    end_pos: usize,
}

impl Parser {
    fn parse_equation(&mut self) -> CalcrResult<Ast> {
        let mut lhs = try!(self.parse_product());
        while !self.next_tok_matches(|val| *val == Op(Plus) || *val == Op(Minus)) {
            let Token { val: tok_val, span: tok_span } = self.consume_tok();
            let rhs = try!(self.parse_product());
            lhs = Ast {
                val: AstVal::Op(tok_val.op().unwrap()),
                span: tok_span,
                branches: Binary(Box::new(lhs), Box::new(rhs)),
            }
        }
        if self.next_tok_is(ParenClose) && self.paren_level < 1 {
            let Token { val: _, span: tok_span } = self.consume_tok();
            Err(CalcrError {
                desc: format!("Missing opening parentheses"),
                span: Some(tok_span),
            })
        } else if self.next_tok_is(AbsDelim) && self.abs_level < 1 {
            let Token { val: _, span: tok_span } = self.consume_tok();
            Err(CalcrError {
                desc: format!("Missing opening abs delimiter"),
                span: Some(tok_span),
            })
        } else {
            Ok(lhs)
        }
    }

    fn parse_product(&mut self) -> CalcrResult<Ast> {
        let mut lhs = try!(self.parse_factor());
        while self.next_tok_matches(|val| *val == Op(Mult) || *val == Op(Div)) {
            let Token { val: tok_val, span: tok_span } = self.consume_tok();
            let rhs = try!(self.parse_factor());
            lhs = Ast {
                val: AstVal::Op(tok_val.op().unwrap()),
                span: tok_span,
                branches: Binary(Box::new(lhs), Box::new(rhs)),
            };
        }
        Ok(lhs)
    }

    fn parse_factor(&mut self) -> CalcrResult<Ast> {
        // when we lex we only store `Minus`s since we do not have any context there,
        // however we know if we see a `Minus` now, then it is a `Neg`.
        if self.next_tok_is(Op(Minus)) {
            let tok_span = self.consume_tok().span;
            let rhs = try!(self.parse_factor());
            Ok(Ast {
                val: AstVal::Op(Neg),
                span: tok_span,
                branches: Unary(Box::new(rhs)),
            })
        } else {
            let lhs = try!(self.parse_exponent());
            if self.next_tok_is(Op(Pow)) {
                let tok_span = self.consume_tok().span;
                let rhs = try!(self.parse_factor());
                Ok(Ast {
                    val: AstVal::Op(Pow),
                    span: tok_span,
                    branches: Binary(Box::new(lhs), Box::new(rhs)),
                })
            } else {
                Ok(lhs)
            }
        }
    }

    fn parse_exponent(&mut self) -> CalcrResult<Ast> {
        let mut out = try!(self.parse_number());

        while self.next_tok_is(Op(Fact)) {
            let tok_span = self.consume_tok().span;
            out = Ast {
                val: AstVal::Op(Fact),
                span: tok_span,
                branches: Unary(Box::new(out)),
            };
        }
        Ok(out)
    }

    fn parse_number(&mut self) -> CalcrResult<Ast> {
        if self.toks_empty() {
            Err(CalcrError {
                desc: format!("Expected number or constant"),
                span: Some((self.end_pos, self.end_pos)),
            })
        } else {
            let Token { val: tok_val, span: tok_span } = self.consume_tok();
            match tok_val {
                Name(ref name) => {
                    let val = match name.as_ref() {
                        "pi" => AstVal::Const(Pi),
                        "e" => AstVal::Const(E),
                        "phi" => AstVal::Const(Phi),
                        "cos" => AstVal::Func(Cos),
                        "sin" => AstVal::Func(Sin),
                        "tan" => AstVal::Func(Tan),
                        "asin" => AstVal::Func(Asin),
                        "acos" => AstVal::Func(Acos),
                        "atan" => AstVal::Func(Atan),
                        "sqrt" => AstVal::Func(Sqrt),
                        "abs" => AstVal::Func(Abs),
                        "exp" => AstVal::Func(Exp),
                        "ln" => AstVal::Func(Ln),
                        "log" => AstVal::Func(Log),
                        _ => return Err(CalcrError {
                            desc: format!("Invalid function or constant: {}", name),
                            span: Some(tok_span),
                        }),
                    };
                    if let AstVal::Func(_) = val {
                        // it's a function so we need to grab its argument
                        if self.next_tok_is(ParenOpen) {
                            // since we know the next token is an open paren, we use
                            // this function to get its AST
                            let arg = try!(self.parse_equation());
                            Ok(Ast {
                                val: val,
                                span: tok_span,
                                branches: Unary(Box::new(arg)),
                            })
                        } else {
                            Err(CalcrError {
                                desc: "Missing opening parentheses after function".to_string(),
                                span: Some(tok_span),
                            })
                        }
                    } else {
                        Ok(Ast {
                            val: val,
                            span: tok_span,
                            branches: Leaf,
                        })
                    }
                },
                ParenOpen => {
                    self.paren_level += 1;
                    let eq = try!(self.parse_equation());
                    if !self.next_tok_is(ParenClose) {
                        Err(CalcrError {
                            desc: "Missing closing parentheses".to_string(),
                            span: Some(tok_span),
                        })
                    } else {
                        self.paren_level -= 1;
                        let close_paren_span = self.consume_tok().span;
                        Ok(Ast {
                            val: AstVal::Paren,
                            span: (tok_span.0, close_paren_span.1),
                            branches: Unary(Box::new(eq)),
                        })
                    }
                },
                AbsDelim => {
                    self.abs_level += 1;
                    let eq = try!(self.parse_equation());
                    if !self.next_tok_is(AbsDelim) {
                        Err(CalcrError {
                            desc: "Missing closing abs delimiter".to_string(),
                            span: Some(tok_span),
                        })
                    } else {
                        self.abs_level -= 1;
                        let close_delim_span = self.consume_tok().span;
                        Ok(Ast {
                            val: AstVal::Func(Abs),
                            span: (tok_span.0, close_delim_span.1),
                            branches: Unary(Box::new(eq)),
                        })
                    }
                },
                Num(num) => {
                    Ok(Ast {
                        val: AstVal::Num(num),
                        span: tok_span,
                        branches: Leaf,
                    })
                },
                _ => Err(CalcrError {
                    desc: format!("Expected number or constant"),
                    span: Some(tok_span),
                }),
            }
        }
    }

    /// Peeks at the next token and check whether its values is equal to `val`
    fn next_tok_is(&mut self, val: TokVal) -> bool {
        self.next_tok_matches(|v| *v == val)
    }

    /// Peeks at the next token and checks whether its value makes `pred` true
    fn next_tok_matches<F>(&mut self, pred: F) -> bool where F: FnOnce(&TokVal) -> bool {
        self.iter.peek().map_or(false, |ref tok| pred(&tok.val))
    }

    /// Checks if we have run out of `Token`s to parse
    fn toks_empty(&mut self) -> bool {
        self.iter.peek().is_none()
    }

    /// Consumes a `Token` - thereby advanding `pos` - and returns it
    ///
    /// # Panics
    /// This function panics if there are no more `Token`s to consume
    fn consume_tok(&mut self) -> Token {
        let tok = self.iter.next();
        tok.unwrap()
    }
}<commit_msg>Fix small bug in parser<commit_after>//! The parser is based on the following grammar
//!
//! Equation ==> Product { "+" Product }
//!           |  Product { "-" Product }
//!
//! Product  ==> Factor { "*" Factor }
//!           |  Factor { "/" Factor }
//!
//! Factor   ==> "-" Factor
//!           |  Exponent { "^" Factor }
//!
//! Exponent ==> Number { "!" }
//!
//! Number   ==> Function "(" Equation ")"
//!           |  Constant
//!           |  "(" Equation ")"
//!           |  "|" Equation "|"
//!           |  Digit { Digit } | Digit { Digit } "." { Digit }
//!
//! Function ==> "sin" | "cos" | "tan" | "asin" | "acos" | "atan" | "sqrt" | "abs" | "exp"
//!           |  "ln" | "log"
//!
//! Constant ==> "pi" | "e" | "phi"
//!
//! Digit    ==> "0".."9"

use std::vec::IntoIter;
use std::iter::Peekable;
use errors::{CalcrResult, CalcrError};
use ast::Ast;
use ast::AstVal;
use ast::AstBranch::*;
use ast::FuncKind::*;
use ast::ConstKind::*;
use ast::OpKind::*;
use token::Token;
use token::TokVal;
use token::TokVal::*;

pub fn parse_equation(tokens: Vec<Token>) -> CalcrResult<Ast> {
    let end_pos = tokens.last().and_then(|tok| Some(tok.span.1)).unwrap_or(0);
    let mut parser = Parser {
        iter: tokens.into_iter().peekable(),
        paren_level: 0,
        abs_level: 0,
        end_pos: end_pos,
    };
    parser.parse_equation()
}

pub struct Parser {
    iter: Peekable<IntoIter<Token>>,
    paren_level: u32,
    abs_level: u32,
    end_pos: usize,
}

impl Parser {
    fn parse_equation(&mut self) -> CalcrResult<Ast> {
        let mut lhs = try!(self.parse_product());
        while self.next_tok_matches(|val| *val == Op(Plus) || *val == Op(Minus)) {
            let Token { val: tok_val, span: tok_span } = self.consume_tok();
            let rhs = try!(self.parse_product());
            lhs = Ast {
                val: AstVal::Op(tok_val.op().unwrap()),
                span: tok_span,
                branches: Binary(Box::new(lhs), Box::new(rhs)),
            }
        }
        if self.next_tok_is(ParenClose) && self.paren_level < 1 {
            let Token { val: _, span: tok_span } = self.consume_tok();
            Err(CalcrError {
                desc: format!("Missing opening parentheses"),
                span: Some(tok_span),
            })
        } else if self.next_tok_is(AbsDelim) && self.abs_level < 1 {
            let Token { val: _, span: tok_span } = self.consume_tok();
            Err(CalcrError {
                desc: format!("Missing opening abs delimiter"),
                span: Some(tok_span),
            })
        } else {
            Ok(lhs)
        }
    }

    fn parse_product(&mut self) -> CalcrResult<Ast> {
        let mut lhs = try!(self.parse_factor());
        while self.next_tok_matches(|val| *val == Op(Mult) || *val == Op(Div)) {
            let Token { val: tok_val, span: tok_span } = self.consume_tok();
            let rhs = try!(self.parse_factor());
            lhs = Ast {
                val: AstVal::Op(tok_val.op().unwrap()),
                span: tok_span,
                branches: Binary(Box::new(lhs), Box::new(rhs)),
            };
        }
        Ok(lhs)
    }

    fn parse_factor(&mut self) -> CalcrResult<Ast> {
        // when we lex we only store `Minus`s since we do not have any context there,
        // however we know if we see a `Minus` now, then it is a `Neg`.
        if self.next_tok_is(Op(Minus)) {
            let tok_span = self.consume_tok().span;
            let rhs = try!(self.parse_factor());
            Ok(Ast {
                val: AstVal::Op(Neg),
                span: tok_span,
                branches: Unary(Box::new(rhs)),
            })
        } else {
            let lhs = try!(self.parse_exponent());
            if self.next_tok_is(Op(Pow)) {
                let tok_span = self.consume_tok().span;
                let rhs = try!(self.parse_factor());
                Ok(Ast {
                    val: AstVal::Op(Pow),
                    span: tok_span,
                    branches: Binary(Box::new(lhs), Box::new(rhs)),
                })
            } else {
                Ok(lhs)
            }
        }
    }

    fn parse_exponent(&mut self) -> CalcrResult<Ast> {
        let mut out = try!(self.parse_number());

        while self.next_tok_is(Op(Fact)) {
            let tok_span = self.consume_tok().span;
            out = Ast {
                val: AstVal::Op(Fact),
                span: tok_span,
                branches: Unary(Box::new(out)),
            };
        }
        Ok(out)
    }

    fn parse_number(&mut self) -> CalcrResult<Ast> {
        if self.toks_empty() {
            Err(CalcrError {
                desc: format!("Expected number or constant"),
                span: Some((self.end_pos, self.end_pos)),
            })
        } else {
            let Token { val: tok_val, span: tok_span } = self.consume_tok();
            match tok_val {
                Name(ref name) => {
                    let val = match name.as_ref() {
                        "pi" => AstVal::Const(Pi),
                        "e" => AstVal::Const(E),
                        "phi" => AstVal::Const(Phi),
                        "cos" => AstVal::Func(Cos),
                        "sin" => AstVal::Func(Sin),
                        "tan" => AstVal::Func(Tan),
                        "asin" => AstVal::Func(Asin),
                        "acos" => AstVal::Func(Acos),
                        "atan" => AstVal::Func(Atan),
                        "sqrt" => AstVal::Func(Sqrt),
                        "abs" => AstVal::Func(Abs),
                        "exp" => AstVal::Func(Exp),
                        "ln" => AstVal::Func(Ln),
                        "log" => AstVal::Func(Log),
                        _ => return Err(CalcrError {
                            desc: format!("Invalid function or constant: {}", name),
                            span: Some(tok_span),
                        }),
                    };
                    if let AstVal::Func(_) = val {
                        // it's a function so we need to grab its argument
                        if self.next_tok_is(ParenOpen) {
                            // since we know the next token is an open paren, we use
                            // this function to get its AST
                            let arg = try!(self.parse_equation());
                            Ok(Ast {
                                val: val,
                                span: tok_span,
                                branches: Unary(Box::new(arg)),
                            })
                        } else {
                            Err(CalcrError {
                                desc: "Missing opening parentheses after function".to_string(),
                                span: Some(tok_span),
                            })
                        }
                    } else {
                        Ok(Ast {
                            val: val,
                            span: tok_span,
                            branches: Leaf,
                        })
                    }
                },
                ParenOpen => {
                    self.paren_level += 1;
                    let eq = try!(self.parse_equation());
                    if !self.next_tok_is(ParenClose) {
                        Err(CalcrError {
                            desc: "Missing closing parentheses".to_string(),
                            span: Some(tok_span),
                        })
                    } else {
                        self.paren_level -= 1;
                        let close_paren_span = self.consume_tok().span;
                        Ok(Ast {
                            val: AstVal::Paren,
                            span: (tok_span.0, close_paren_span.1),
                            branches: Unary(Box::new(eq)),
                        })
                    }
                },
                AbsDelim => {
                    self.abs_level += 1;
                    let eq = try!(self.parse_equation());
                    if !self.next_tok_is(AbsDelim) {
                        Err(CalcrError {
                            desc: "Missing closing abs delimiter".to_string(),
                            span: Some(tok_span),
                        })
                    } else {
                        self.abs_level -= 1;
                        let close_delim_span = self.consume_tok().span;
                        Ok(Ast {
                            val: AstVal::Func(Abs),
                            span: (tok_span.0, close_delim_span.1),
                            branches: Unary(Box::new(eq)),
                        })
                    }
                },
                Num(num) => {
                    Ok(Ast {
                        val: AstVal::Num(num),
                        span: tok_span,
                        branches: Leaf,
                    })
                },
                _ => Err(CalcrError {
                    desc: format!("Expected number or constant"),
                    span: Some(tok_span),
                }),
            }
        }
    }

    /// Peeks at the next token and check whether its values is equal to `val`
    fn next_tok_is(&mut self, val: TokVal) -> bool {
        self.next_tok_matches(|v| *v == val)
    }

    /// Peeks at the next token and checks whether its value makes `pred` true
    fn next_tok_matches<F>(&mut self, pred: F) -> bool where F: FnOnce(&TokVal) -> bool {
        self.iter.peek().map_or(false, |ref tok| pred(&tok.val))
    }

    /// Checks if we have run out of `Token`s to parse
    fn toks_empty(&mut self) -> bool {
        self.iter.peek().is_none()
    }

    /// Consumes a `Token` - thereby advanding `pos` - and returns it
    ///
    /// # Panics
    /// This function panics if there are no more `Token`s to consume
    fn consume_tok(&mut self) -> Token {
        let tok = self.iter.next();
        tok.unwrap()
    }
}<|endoftext|>


<commit_before>use nom::IResult;
use std::str;

pub use common::{FieldExpression, Operator};
use select::*;
use insert::*;

#[derive(Clone, Debug, PartialEq)]
pub enum SqlQuery {
    Insert(InsertStatement),
    Select(SelectStatement),
}

#[derive(Clone, Debug, PartialEq)]
pub struct Column {
    pub name: String,
    pub table: Option<String>,
}

impl<'a> From<&'a str> for Column {
    fn from(c: &str) -> Column {
        match c.find(".") {
            None => {
                Column {
                    name: String::from(c),
                    table: None,
                }
            }
            Some(i) => {
                Column {
                    name: String::from(&c[i..]),
                    table: Some(String::from(&c[0..i - 1])),
                }
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum ConditionBase {
    Field(Column),
    Literal(String),
    Placeholder,
}

#[derive(Clone, Debug, PartialEq)]
pub struct ConditionTree {
    pub operator: Operator,
    pub left: Option<Box<ConditionExpression>>,
    pub right: Option<Box<ConditionExpression>>,
}

#[derive(Clone, Debug, PartialEq)]
pub enum ConditionExpression {
    ComparisonOp(ConditionTree),
    LogicalOp(ConditionTree),
    Base(ConditionBase),
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct Table {
    pub name: String,
    pub alias: Option<String>,
}

impl<'a> From<&'a str> for Table {
    fn from(t: &str) -> Table {
        Table {
            name: String::from(t),
            alias: None,
        }
    }
}

/// Parse sequence of SQL statements, divided by semicolons or newlines
// named!(pub query_list<&[u8], Vec<SqlQuery> >,
//    many1!(map_res!(selection, |s| { SqlQuery::Select(s) }))
// );

pub fn parse_query(input: &str) -> Result<SqlQuery, &str> {
    // we process all queries in lowercase to avoid having to deal with capitalization in the
    // parser.
    let q_bytes = String::from(input).into_bytes();

    // TODO(malte): appropriately pass through errors from nom
    match insertion(&q_bytes) {
        IResult::Done(_, o) => return Ok(SqlQuery::Insert(o)),
        _ => (),
    };

    match selection(&q_bytes) {
        IResult::Done(_, o) => return Ok(SqlQuery::Select(o)),
        _ => (),
    };

    Err("failed to parse query")
}
<commit_msg>Fix column name parsing bug<commit_after>use nom::IResult;
use std::str;

pub use common::{FieldExpression, Operator};
use select::*;
use insert::*;

#[derive(Clone, Debug, PartialEq)]
pub enum SqlQuery {
    Insert(InsertStatement),
    Select(SelectStatement),
}

#[derive(Clone, Debug, PartialEq)]
pub struct Column {
    pub name: String,
    pub table: Option<String>,
}

impl<'a> From<&'a str> for Column {
    fn from(c: &str) -> Column {
        match c.find(".") {
            None => {
                Column {
                    name: String::from(c),
                    table: None,
                }
            }
            Some(i) => {
                Column {
                    name: String::from(&c[i + 1..]),
                    table: Some(String::from(&c[0..i])),
                }
            }
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub enum ConditionBase {
    Field(Column),
    Literal(String),
    Placeholder,
}

#[derive(Clone, Debug, PartialEq)]
pub struct ConditionTree {
    pub operator: Operator,
    pub left: Option<Box<ConditionExpression>>,
    pub right: Option<Box<ConditionExpression>>,
}

#[derive(Clone, Debug, PartialEq)]
pub enum ConditionExpression {
    ComparisonOp(ConditionTree),
    LogicalOp(ConditionTree),
    Base(ConditionBase),
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct Table {
    pub name: String,
    pub alias: Option<String>,
}

impl<'a> From<&'a str> for Table {
    fn from(t: &str) -> Table {
        Table {
            name: String::from(t),
            alias: None,
        }
    }
}

/// Parse sequence of SQL statements, divided by semicolons or newlines
// named!(pub query_list<&[u8], Vec<SqlQuery> >,
//    many1!(map_res!(selection, |s| { SqlQuery::Select(s) }))
// );

pub fn parse_query(input: &str) -> Result<SqlQuery, &str> {
    // we process all queries in lowercase to avoid having to deal with capitalization in the
    // parser.
    let q_bytes = String::from(input).into_bytes();

    // TODO(malte): appropriately pass through errors from nom
    match insertion(&q_bytes) {
        IResult::Done(_, o) => return Ok(SqlQuery::Insert(o)),
        _ => (),
    };

    match selection(&q_bytes) {
        IResult::Done(_, o) => return Ok(SqlQuery::Select(o)),
        _ => (),
    };

    Err("failed to parse query")
}

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

    #[test]
    fn column_from_str() {
        let s = "table.col";
        let c = Column::from(s);

        assert_eq!(c,
                   Column {
                       name: String::from("col"),
                       table: Some(String::from("table")),
                   });
    }
}
<|endoftext|>


<commit_before>extern crate stemmer;
use self::stemmer::Stemmer;
use word::Word;
use std::collections::HashMap;

static IGNORED_FR:[&'static str; 26] = ["la", "le", "les", "pas", "ne",
                                       "nos", "des", "ils", "elles", "il",
                                       "elle", "se", "on", "nous", "vous",
                                        "leur", "leurs", "de", "et", "un",
                                        "une", "t", "s", "à", "d",
                                        "l"];
static IGNORED_DEFAULT:[&'static str; 0] = [];

/// Parser type
pub struct Parser<'a> {
    /// The stemmer object
    stemmer: Stemmer,
    /// List of ignored words
    ignored: &'a[&'a str],
    /// Detect HTML in input
    html: bool,
    /// Ignores proper nouns
    ignore_proper: bool,
    /// Leak, only used for detect_leak
    leak: f32,
    /// Max distance to consider a repetition, only used for detect_local
    max_distance: u32
}

impl<'a> Parser<'a> {
    /// Returns Some(Parser) if language is ok, None else
    pub fn new(lang: &str) -> Option<Parser> {
        let stemmer = Stemmer::new(lang);
        if stemmer.is_none() {
            return None;
        }
        let stemmer = stemmer.unwrap();
        let ignored:&'static[&'static str] = match lang {
            "french" => &IGNORED_FR,
            _ => &IGNORED_DEFAULT
        };
        Some(Parser{stemmer: stemmer,
                    ignored: ignored,
                    html: true,
                    ignore_proper: false,
                    leak: 0.98,
                    max_distance: 50})
    }

    /// Sets max distance for repetition (default 50)
    pub fn with_max_distance(mut self, max_dist: u32) -> Parser<'a> {
        self.max_distance = max_dist;
        self
    }

    /// Sets HTML detection in input (default true)
    pub fn with_html(mut self, html: bool) -> Parser<'a> {
        self.html = html;
        self
    }

    /// Sets ignore_proper flag (default false)
    pub fn with_ignore_proper(mut self, proper: bool) -> Parser<'a> {
        self.ignore_proper = proper;
        self
    }

    /// Sets the leak (default 0.98)
    pub fn with_leak(mut self, leak: f32) -> Parser<'a> {
        self.leak = leak;
        self
    }

    /// Sets the ignored keyword
    pub fn with_ignored(mut self, ignored: &'a [&'a str]) -> Parser<'a> {
        self.ignored = ignored;
        self
    }

    fn tokenize_html<'b>(&self, c:&'b [char]) -> (&'b [char], Word) {
        let mut res = String::new();
        let mut chars:&[char] = c;

        let mut brackets = 1;
        res.push(chars[0]);
        chars = &chars[1..];

        loop {
            if chars.is_empty() {
                panic!("Error reading HTML: ill-formed HTML. Maybe this is not an HTML file?");
            }
            let c = chars[0];
            res.push(c);
            chars = &chars[1..];
            if c == '<' {
                brackets += 1;
            }
            if c == '>' {
                brackets -= 1;
                if brackets == 0 {
                    break;
                }
            }
        }

        (chars, Word::Untracked(res))
    }

    fn tokenize_whitespace<'b>(&self, c:&'b [char], is_begin: &mut bool) -> (&'b [char], Word) {
        let mut res = String::new();
        let mut chars:&[char] = c;

        loop {
            if chars.is_empty() {
                break;
            }
            let c = chars[0];
            if c == '<' || c.is_alphabetic() {
                break;
            }
            chars = &chars[1..];
            res.push(c);
            if c == '.' {
                *is_begin = true;
            }
        }

        (chars, Word::Untracked(res))
    }

    /// Return true if `s` is a proper noun, false else
    fn is_proper_noun(&self, s:&str, is_begin: bool) -> bool {
        if self.ignore_proper {
            if !is_begin {
                let o = s.chars().next();
                match o {
                    None => false,
                    Some(c) => c.is_uppercase()
                }
            }
            else {
                // Technically a proper noun could be at the beginning of a sentence :s
                false
            }
        } else {
            false
        }
    }

    fn tokenize_word<'b>(&self, c: &'b [char], is_begin:&mut bool) -> (&'b [char], Word) {
        let mut res = String::new();
        let mut chars:&[char] = c;

        loop {
            if chars.is_empty() {
                break;
            }
            let c = chars[0];
            if !c.is_alphabetic() {
                break;
            }
            res.push(c);
            chars = &chars[1..];
        }

        let lower_s:Vec<String> = res.chars()
            .map(|c| c.to_lowercase().collect::<String>())
            .collect();
        let lower_s = lower_s.concat();
        let word = if self.ignored.contains(&&*lower_s)
            || self.is_proper_noun(&res, *is_begin) {
            Word::Ignored(res)
        } else {
            Word::Tracked(res,
                          self.stemmer.stem(&lower_s),
                          0.0)
        };

        *is_begin = false;
        (chars, word)
    }

    /// Tokenize a string into a list of words
    ///
    /// # Arguments
    ///
    /// * `s` – The string to tokenize
    pub fn tokenize(&self, s: &str) -> Vec<Word> {
        let v_chars:Vec<char> = s.chars().collect();
        let mut chars:&[char] = &v_chars;
        let mut res:Vec<Word> = vec!();
        let mut is_sentence_beginning = true;

        while !chars.is_empty() {
            let c = chars[0];
            if c.is_alphabetic() {
                let (c, word) = self.tokenize_word(chars, &mut is_sentence_beginning);
                chars = c;
                res.push(word);
            } else if self.html && c == '<' {
                let (c, word) = self.tokenize_html(chars);
                chars = c;
                res.push(word);
                is_sentence_beginning = false;
            } else {
                let (c, word) = self.tokenize_whitespace(chars, &mut is_sentence_beginning);
                chars = c;
                res.push(word);
            }
        }

        res
    }

    /// Detect the local repetitions, using a leak value
    ///
    /// Basically, each time a word occurs, increase value by 1.0
    /// and each time it does not, multiply by leak (default: 0.98)
    pub fn detect_leak(&self, mut vec:Vec<Word>) -> Vec<Word> {
        let mut h:HashMap<String, (u32, f32)> = HashMap::new();
        let mut pos = 0;

        for i in 0 .. vec.len() {
            match &mut vec[i] {
                &mut Word::Untracked(_) => {}
                &mut Word::Ignored(_) => {
                    pos += 1;
                },
                &mut Word::Tracked(_, ref stemmed, ref mut v) => {
                    pos += 1;
                    let x = match h.get(stemmed) {
                        None => 0.0,
                        Some(tmp) => {
                            let &(n, y) = tmp;
                            y * self.leak.powi((pos - n) as i32)
                        }
                    } + 1.0;
                    h.insert(stemmed.clone(), (pos, x));
                    *v = x;
                }
            };
        }
        vec
    }

    /// Detect the local number of repetitions.
    ///
    /// For each word, value is set to the total number of occurences of this word
    /// but it is reset to zero if there is more than `self.max_distance` between
    /// two occurences.
    pub fn detect_local(&self, mut vec:Vec<Word>) -> Vec<Word> {
        let mut h:HashMap<String, (u32, Vec<usize>)> = HashMap::new();
        let mut pos = 1;

        for i in 0 .. vec.len() {
            let e:Word = vec[i].clone();
            match e {
                Word::Untracked(_) => {}
                Word::Ignored(_) => {pos += 1},
                Word::Tracked(_, ref stemmed, _) => {
                    let (mut p_pos, mut subvec) = match h.remove(stemmed) {
                        None => (0, vec!()),
                        Some(y) => y
                    };
                    if p_pos != 0 && pos - p_pos < self.max_distance {
                        subvec.push(i);
                        let v = subvec.len() as f32;
                        subvec.iter().map(|e| vec[*e].set_count(v)).count();
                        h.insert(stemmed.clone(), (pos, subvec));
                    } else {
                        subvec = vec!(i);
                        h.insert(stemmed.clone(), (pos, subvec));
                    }
                }
            };
        }
        vec
    }

    /// Detect the global number of repetitions.
    ///
    /// For each word, value is set to the total number of occurences of this word.
    ///
    /// # Arguments
    ///
    /// * `vec` – A vector of `Word`
    /// * `is_relative` – If true, divide the number of occurences by total number of words
    pub fn detect_global(&self, mut vec:Vec<Word>, is_relative: bool) -> Vec<Word> {
        let mut h:HashMap<String, f32> = HashMap::new();
        let mut count = 0;

        // first loop: we fill the map to count the occurences
        for i in 0 .. vec.len() {
            match &vec[i] {
                &Word::Untracked(_) => {}
                &Word::Ignored(_) => {count += 1;},
                &Word::Tracked(_, ref stemmed, _) => {
                    count += 1;
                    let x = match h.get(stemmed) {
                        None => 0.0,
                        Some(y) => *y
                    } + 1.0;
                    h.insert(stemmed.clone(), x);
                }
            };
        }
        // second loop: we set each word value to the number of
        // occurences
        for i in 0..vec.len() {
            let tmp = if let Word::Tracked(_, ref stemmed, _) = vec[i] {
                let x = h.get(stemmed).expect("HashMap was not filled correctly");
                Some(*x)
            } else {
                None
            };
            if let Some(x) = tmp {
                if is_relative {
                    vec[i].set_count(x / (count as f32));
                } else {
                    vec[i].set_count(x);
                }
            }
        }
        vec
    }
}

<commit_msg>Added Parser::list_languages()<commit_after>extern crate stemmer;
use self::stemmer::Stemmer;
use word::Word;
use std::collections::HashMap;

static IGNORED_FR:[&'static str; 26] = ["la", "le", "les", "pas", "ne",
                                       "nos", "des", "ils", "elles", "il",
                                       "elle", "se", "on", "nous", "vous",
                                        "leur", "leurs", "de", "et", "un",
                                        "une", "t", "s", "à", "d",
                                        "l"];
static IGNORED_DEFAULT:[&'static str; 0] = [];

/// Parser type
pub struct Parser<'a> {
    /// The stemmer object
    stemmer: Stemmer,
    /// List of ignored words
    ignored: &'a[&'a str],
    /// Detect HTML in input
    html: bool,
    /// Ignores proper nouns
    ignore_proper: bool,
    /// Leak, only used for detect_leak
    leak: f32,
    /// Max distance to consider a repetition, only used for detect_local
    max_distance: u32
}

impl<'a> Parser<'a> {
    ///  Returns a vector containing all languages implemented
    pub fn list_languages() -> Vec<&'static str> {
        Stemmer::list()
    }

    /// Returns Some(Parser) if language is ok, None else
    pub fn new(lang: &str) -> Option<Parser> {
        let stemmer = Stemmer::new(lang);
        if stemmer.is_none() {
            return None;
        }
        let stemmer = stemmer.unwrap();
        let ignored:&'static[&'static str] = match lang {
            "french" => &IGNORED_FR,
            _ => &IGNORED_DEFAULT
        };
        Some(Parser{stemmer: stemmer,
                    ignored: ignored,
                    html: true,
                    ignore_proper: false,
                    leak: 0.98,
                    max_distance: 50})
    }

    /// Sets max distance for repetition (default 50)
    pub fn with_max_distance(mut self, max_dist: u32) -> Parser<'a> {
        self.max_distance = max_dist;
        self
    }

    /// Sets HTML detection in input (default true)
    pub fn with_html(mut self, html: bool) -> Parser<'a> {
        self.html = html;
        self
    }

    /// Sets ignore_proper flag (default false)
    pub fn with_ignore_proper(mut self, proper: bool) -> Parser<'a> {
        self.ignore_proper = proper;
        self
    }

    /// Sets the leak (default 0.98)
    pub fn with_leak(mut self, leak: f32) -> Parser<'a> {
        self.leak = leak;
        self
    }

    /// Sets the ignored keyword
    pub fn with_ignored(mut self, ignored: &'a [&'a str]) -> Parser<'a> {
        self.ignored = ignored;
        self
    }

    fn tokenize_html<'b>(&self, c:&'b [char]) -> (&'b [char], Word) {
        let mut res = String::new();
        let mut chars:&[char] = c;

        let mut brackets = 1;
        res.push(chars[0]);
        chars = &chars[1..];

        loop {
            if chars.is_empty() {
                panic!("Error reading HTML: ill-formed HTML. Maybe this is not an HTML file?");
            }
            let c = chars[0];
            res.push(c);
            chars = &chars[1..];
            if c == '<' {
                brackets += 1;
            }
            if c == '>' {
                brackets -= 1;
                if brackets == 0 {
                    break;
                }
            }
        }

        (chars, Word::Untracked(res))
    }

    fn tokenize_whitespace<'b>(&self, c:&'b [char], is_begin: &mut bool) -> (&'b [char], Word) {
        let mut res = String::new();
        let mut chars:&[char] = c;

        loop {
            if chars.is_empty() {
                break;
            }
            let c = chars[0];
            if c == '<' || c.is_alphabetic() {
                break;
            }
            chars = &chars[1..];
            res.push(c);
            if c == '.' {
                *is_begin = true;
            }
        }

        (chars, Word::Untracked(res))
    }

    /// Return true if `s` is a proper noun, false else
    fn is_proper_noun(&self, s:&str, is_begin: bool) -> bool {
        if self.ignore_proper {
            if !is_begin {
                let o = s.chars().next();
                match o {
                    None => false,
                    Some(c) => c.is_uppercase()
                }
            }
            else {
                // Technically a proper noun could be at the beginning of a sentence :s
                false
            }
        } else {
            false
        }
    }

    fn tokenize_word<'b>(&self, c: &'b [char], is_begin:&mut bool) -> (&'b [char], Word) {
        let mut res = String::new();
        let mut chars:&[char] = c;

        loop {
            if chars.is_empty() {
                break;
            }
            let c = chars[0];
            if !c.is_alphabetic() {
                break;
            }
            res.push(c);
            chars = &chars[1..];
        }

        let lower_s:Vec<String> = res.chars()
            .map(|c| c.to_lowercase().collect::<String>())
            .collect();
        let lower_s = lower_s.concat();
        let word = if self.ignored.contains(&&*lower_s)
            || self.is_proper_noun(&res, *is_begin) {
            Word::Ignored(res)
        } else {
            Word::Tracked(res,
                          self.stemmer.stem(&lower_s),
                          0.0)
        };

        *is_begin = false;
        (chars, word)
    }

    /// Tokenize a string into a list of words
    ///
    /// # Arguments
    ///
    /// * `s` – The string to tokenize
    pub fn tokenize(&self, s: &str) -> Vec<Word> {
        let v_chars:Vec<char> = s.chars().collect();
        let mut chars:&[char] = &v_chars;
        let mut res:Vec<Word> = vec!();
        let mut is_sentence_beginning = true;

        while !chars.is_empty() {
            let c = chars[0];
            if c.is_alphabetic() {
                let (c, word) = self.tokenize_word(chars, &mut is_sentence_beginning);
                chars = c;
                res.push(word);
            } else if self.html && c == '<' {
                let (c, word) = self.tokenize_html(chars);
                chars = c;
                res.push(word);
                is_sentence_beginning = false;
            } else {
                let (c, word) = self.tokenize_whitespace(chars, &mut is_sentence_beginning);
                chars = c;
                res.push(word);
            }
        }

        res
    }

    /// Detect the local repetitions, using a leak value
    ///
    /// Basically, each time a word occurs, increase value by 1.0
    /// and each time it does not, multiply by leak (default: 0.98)
    pub fn detect_leak(&self, mut vec:Vec<Word>) -> Vec<Word> {
        let mut h:HashMap<String, (u32, f32)> = HashMap::new();
        let mut pos = 0;

        for i in 0 .. vec.len() {
            match &mut vec[i] {
                &mut Word::Untracked(_) => {}
                &mut Word::Ignored(_) => {
                    pos += 1;
                },
                &mut Word::Tracked(_, ref stemmed, ref mut v) => {
                    pos += 1;
                    let x = match h.get(stemmed) {
                        None => 0.0,
                        Some(tmp) => {
                            let &(n, y) = tmp;
                            y * self.leak.powi((pos - n) as i32)
                        }
                    } + 1.0;
                    h.insert(stemmed.clone(), (pos, x));
                    *v = x;
                }
            };
        }
        vec
    }

    /// Detect the local number of repetitions.
    ///
    /// For each word, value is set to the total number of occurences of this word
    /// but it is reset to zero if there is more than `self.max_distance` between
    /// two occurences.
    pub fn detect_local(&self, mut vec:Vec<Word>) -> Vec<Word> {
        let mut h:HashMap<String, (u32, Vec<usize>)> = HashMap::new();
        let mut pos = 1;

        for i in 0 .. vec.len() {
            let e:Word = vec[i].clone();
            match e {
                Word::Untracked(_) => {}
                Word::Ignored(_) => {pos += 1},
                Word::Tracked(_, ref stemmed, _) => {
                    let (p_pos, mut subvec) = match h.remove(stemmed) {
                        None => (0, vec!()),
                        Some(y) => y
                    };
                    if p_pos != 0 && pos - p_pos < self.max_distance {
                        subvec.push(i);
                        let v = subvec.len() as f32;
                        subvec.iter().map(|e| vec[*e].set_count(v)).count();
                        h.insert(stemmed.clone(), (pos, subvec));
                    } else {
                        subvec = vec!(i);
                        h.insert(stemmed.clone(), (pos, subvec));
                    }
                }
            };
        }
        vec
    }

    /// Detect the global number of repetitions.
    ///
    /// For each word, value is set to the total number of occurences of this word.
    ///
    /// # Arguments
    ///
    /// * `vec` – A vector of `Word`
    /// * `is_relative` – If true, divide the number of occurences by total number of words
    pub fn detect_global(&self, mut vec:Vec<Word>, is_relative: bool) -> Vec<Word> {
        let mut h:HashMap<String, f32> = HashMap::new();
        let mut count = 0;

        // first loop: we fill the map to count the occurences
        for i in 0 .. vec.len() {
            match &vec[i] {
                &Word::Untracked(_) => {}
                &Word::Ignored(_) => {count += 1;},
                &Word::Tracked(_, ref stemmed, _) => {
                    count += 1;
                    let x = match h.get(stemmed) {
                        None => 0.0,
                        Some(y) => *y
                    } + 1.0;
                    h.insert(stemmed.clone(), x);
                }
            };
        }
        // second loop: we set each word value to the number of
        // occurences
        for i in 0..vec.len() {
            let tmp = if let Word::Tracked(_, ref stemmed, _) = vec[i] {
                let x = h.get(stemmed).expect("HashMap was not filled correctly");
                Some(*x)
            } else {
                None
            };
            if let Some(x) = tmp {
                if is_relative {
                    vec[i].set_count(x / (count as f32));
                } else {
                    vec[i].set_count(x);
                }
            }
        }
        vec
    }
}

<|endoftext|>


<commit_before>use std::str::from_utf8;
use nom::{not_line_ending, GetInput};

named!(quoted,
       delimited!(tag!("\""), take_until!("\""), tag!("\"")));

named!(property, take_until!(" = "));

named!(key_value<&[u8], Line>,
       do_parse!(key: property >>
                 tag!(" = ") >>
                 value: alt!(
                     quoted |
                     not_line_ending
                 ) >>
                 (KeyValue(to_string(key), to_string(value)))));

named!(comment<&[u8], Line>,
       do_parse!(
           content: preceded!(tag!("#"), not_line_ending) >>
           (Comment(to_string(content)))));

named!(line<&[u8], Line>, alt!(
        ws!(key_value) |
        ws!(comment)));

named!(parse<&[u8], Vec<Line> >, many0!(line));

#[derive(Debug, PartialEq)]
pub enum Line {
    KeyValue(String, String),
    Comment(String),
}
use self::Line::*;

#[derive(Debug, PartialEq)]
pub enum ParseError {
    Syntax,
}
use self::ParseError::*;

impl From<::nom::IError> for ParseError {
    fn from(error: ::nom::IError) -> ParseError {
        match error {
            ::nom::IError::Incomplete(_) => Syntax,
            _ => panic!("Error evaluating parser"),
        }
    }
}

fn to_string(bytes: &[u8]) -> String {
    from_utf8(bytes)
        .expect("Managed to parse but failed to convert non-utf8")
        .to_string()
}

pub fn get_lines(s: &str) -> Result<Vec<Line>, ParseError> {
    let parsed = parse(s.as_bytes());
    match parsed.remaining_input() {
        Some(bytes) if (bytes.len() != 0) => return Err(Syntax),
        _ => {}
    }
    parsed.to_full_result().map_err(::std::convert::From::from)
}

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

    fn assert_syntax_error(contents: &str) {
        assert_eq!(get_lines(contents).unwrap_err(),
               ParseError::Syntax);
    }

    fn get_lines_vec(contents: &str) -> Vec<Line> {
        get_lines(contents).unwrap()
    }

    fn key_value(key: &str, value: &str) -> super::Line {
        super::Line::KeyValue(key.to_string(), value.to_string())
    }

    #[test]
    fn root_level_empty() {
        assert_eq!(get_lines_vec(""), vec![]);
    }

    #[test]
    fn root_level_single_property() {
        let result = vec![
            key_value("hostname", "dynamo"),
        ];
        assert_eq!(get_lines("hostname = \"dynamo\"").unwrap(), result);
    }

    #[test]
    fn root_level_three_properties() {
        let result = vec![
            key_value("hostname", "dynamo"),
            key_value("port", "5153"),
            key_value("path", "/foo/bar"),
        ];
        assert_eq!(
            get_lines("hostname = \"dynamo\"\nport = \"5153\"\npath = \"/foo/bar\"").unwrap(),
            result);
    }

    #[test]
    fn just_a_word() {
        assert_syntax_error("hostname");
    }

    #[test]
    fn word_and_equals() {
        assert_syntax_error("hostname = ");
    }

    #[test]
    #[ignore]
    fn root_level_property_with_many_dots_together() {
        assert_syntax_error("host..name = \"dynamo\"");
    }

    #[test]
    fn quoted_word_with_stuff_after() {
        assert_syntax_error("hostname = \"dynamo\"great");
        assert_syntax_error("hostname = \"dynamo\" great");
    }

    #[test]
    fn root_level_property_with_dots() {
        let result = vec![
            key_value("host.name", "dynamo"),
        ];
        assert_eq!(get_lines("host.name = \"dynamo\"").unwrap(), result);
    }

    #[test]
    fn root_level_property_with_underscores() {
        let result = vec![
            key_value("host_name", "dynamo"),
        ];
        assert_eq!(get_lines("host_name = \"dynamo\"").unwrap(), result);
    }

    #[test]
    fn bare_words() {
        let result = vec![
            key_value("hostname", "dynamo"),
        ];
        assert_eq!(get_lines("hostname = dynamo").unwrap(), result);
    }

    #[test]
    fn multiline_bare_words() {
        let result = vec![
            key_value("hostname", "dynamo"),
            key_value("port", "5153"),
            key_value("path", "/foo/bar"),
        ];
        assert_eq!(
            get_lines("hostname = dynamo\nport = 5153\npath = /foo/bar").unwrap(),
            result);
    }

    #[test]
    fn bare_words_until_end_of_line() {
        let result = vec![
            key_value("hostname", "dynamo is bae"),
        ];
        assert_eq!(get_lines("hostname = dynamo is bae").unwrap(), result);
    }

    #[test]
    fn comments() {
        let result = vec![
            Line::Comment(" Just a comment.".to_string()),
        ];
        assert_eq!(get_lines("# Just a comment.").unwrap(), result);
    }

    #[test]
    #[ignore]
    fn simple_multiline() {
        // let result = vec![
        //     Namespace("host".to_string(), vec![
        //         key_value("name", "dynamo"),
        //     ]),
        // ];
        // assert_eq!(get_lines("host\n    name = dynamo").unwrap(), result);
    }
}
<commit_msg>handle comments at the end of lines<commit_after>use std::str::from_utf8;
use nom::{line_ending, newline, not_line_ending, GetInput, IResult};

named!(quoted,
       delimited!(tag!("\""), take_until!("\""), tag!("\"")));

named!(quote_and_done,
       alt!(terminated!(quoted, eof!()) | terminated!(quoted, line_ending)));

named!(property, take_until!(" = "));

named!(bare_key, preceded!(not!(tag!("\"")), not_line_ending));

named!(key_value<&[u8], Line>,
       do_parse!(key: property >>
                 tag!(" = ") >>
                 value: alt!(
                     quote_and_done |
                     bare_key
                 ) >>
                 (KeyValue(to_string(key), to_string(value)))));

named!(comment<&[u8], Line>,
       do_parse!(
           content: preceded!(tag!("#"), not_line_ending) >>
           (Comment(to_string(content)))));

named!(line<&[u8], Line>, alt!(
        ws!(key_value) |
        ws!(comment)));

named!(parse<&[u8], Vec<Line> >, many0!(line));

#[derive(Debug, PartialEq)]
pub enum Line {
    KeyValue(String, String),
    Comment(String),
}
use self::Line::*;

#[derive(Debug, PartialEq)]
pub enum ParseError {
    Syntax,
}
use self::ParseError::*;

impl From<::nom::IError> for ParseError {
    fn from(error: ::nom::IError) -> ParseError {
        match error {
            ::nom::IError::Incomplete(_) => Syntax,
            _ => panic!("Error evaluating parser"),
        }
    }
}

fn to_string(bytes: &[u8]) -> String {
    from_utf8(bytes)
        .expect("Managed to parse but failed to convert non-utf8")
        .to_string()
}

pub fn get_lines(s: &str) -> Result<Vec<Line>, ParseError> {
    let parsed = parse(s.as_bytes());
    match parsed.remaining_input() {
        Some(bytes) if (bytes.len() != 0) => return Err(Syntax),
        _ => {}
    }
    parsed.to_full_result().map_err(::std::convert::From::from)
}

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

    fn assert_syntax_error(contents: &str) {
        assert_eq!(get_lines(contents).unwrap_err(),
               ParseError::Syntax);
    }

    fn get_lines_vec(contents: &str) -> Vec<Line> {
        get_lines(contents).unwrap()
    }

    fn key_value(key: &str, value: &str) -> super::Line {
        super::Line::KeyValue(key.to_string(), value.to_string())
    }

    #[test]
    fn root_level_empty() {
        assert_eq!(get_lines_vec(""), vec![]);
    }

    #[test]
    fn root_level_single_property() {
        let result = vec![
            key_value("hostname", "dynamo"),
        ];
        assert_eq!(get_lines("hostname = \"dynamo\"").unwrap(), result);
    }

    #[test]
    fn root_level_three_properties() {
        let result = vec![
            key_value("hostname", "dynamo"),
            key_value("port", "5153"),
            key_value("path", "/foo/bar"),
        ];
        assert_eq!(
            get_lines("hostname = \"dynamo\"\nport = \"5153\"\npath = \"/foo/bar\"").unwrap(),
            result);
    }

    #[test]
    fn just_a_word() {
        assert_syntax_error("hostname");
    }

    #[test]
    fn word_and_equals() {
        assert_syntax_error("hostname = ");
    }

    #[test]
    #[ignore]
    fn root_level_property_with_many_dots_together() {
        assert_syntax_error("host..name = \"dynamo\"");
    }

    #[test]
    fn quoted_word_with_stuff_after() {
        assert_syntax_error("hostname = \"dynamo\"great");
        assert_syntax_error("hostname = \"dynamo\" great");
    }

    #[test]
    fn root_level_property_with_dots() {
        let result = vec![
            key_value("host.name", "dynamo"),
        ];
        assert_eq!(get_lines("host.name = \"dynamo\"").unwrap(), result);
    }

    #[test]
    fn root_level_property_with_underscores() {
        let result = vec![
            key_value("host_name", "dynamo"),
        ];
        assert_eq!(get_lines("host_name = \"dynamo\"").unwrap(), result);
    }

    #[test]
    fn bare_words() {
        let result = vec![
            key_value("hostname", "dynamo"),
        ];
        assert_eq!(get_lines("hostname = dynamo").unwrap(), result);
    }

    #[test]
    fn multiline_bare_words() {
        let result = vec![
            key_value("hostname", "dynamo"),
            key_value("port", "5153"),
            key_value("path", "/foo/bar"),
        ];
        assert_eq!(
            get_lines("hostname = dynamo\nport = 5153\npath = /foo/bar").unwrap(),
            result);
    }

    #[test]
    fn bare_words_until_end_of_line() {
        let result = vec![
            key_value("hostname", "dynamo is bae"),
        ];
        assert_eq!(get_lines("hostname = dynamo is bae").unwrap(), result);
    }

    #[test]
    fn comments() {
        let result = vec![
            Line::Comment(" Just a comment.".to_string()),
        ];
        assert_eq!(get_lines("# Just a comment.").unwrap(), result);
    }

    #[test]
    fn comment_after_bare_words() {
        let result = vec![
            key_value("hostname", "dynamo # This isn't actually a comment."),
        ];
        assert_eq!(
            get_lines("hostname = dynamo # This isn't actually a comment.").unwrap(),
            result);
    }

    #[test]
    fn comment_after_quote() {
        assert_syntax_error("hostname = \"dynamo\" # This isn't actually a comment.");
    }

    #[test]
    #[ignore]
    fn simple_multiline() {
        // let result = vec![
        //     Namespace("host".to_string(), vec![
        //         key_value("name", "dynamo"),
        //     ]),
        // ];
        // assert_eq!(get_lines("host\n    name = dynamo").unwrap(), result);
    }
}
<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{CliCommand, CliError, CliResult, CliTypedResult};
use crate::Tool;
use async_trait::async_trait;
use clap::CommandFactory;
use clap::Parser;
use clap_complete::{generate, Shell};
use std::path::PathBuf;

/// Tool for configuration of the CLI tool
///
#[derive(Parser)]
pub enum ConfigTool {
    Init(crate::common::init::InitTool),
    GenerateShellCompletions(GenerateShellCompletions),
}

impl ConfigTool {
    pub async fn execute(self) -> CliResult {
        match self {
            ConfigTool::Init(tool) => tool.execute_serialized_success().await,
            ConfigTool::GenerateShellCompletions(tool) => tool.execute_serialized_success().await,
        }
    }
}

/// Generates shell completion files
///
/// First generate the completion file, then follow the shell specific directions on how
/// to install the completion file.
#[derive(Parser)]
pub struct GenerateShellCompletions {
    /// Shell to generate completions for one of [bash, elvish, powershell, zsh]
    #[clap(long)]
    shell: Shell,
    /// File to output shell completions to
    #[clap(long, parse(from_os_str))]
    output_file: PathBuf,
}

#[async_trait]
impl CliCommand<()> for GenerateShellCompletions {
    fn command_name(&self) -> &'static str {
        "generate-shell-completions"
    }

    async fn execute(self) -> CliTypedResult<()> {
        let mut command = Tool::command();
        let mut file = std::fs::File::create(self.output_file.as_path())
            .map_err(|err| CliError::IO(self.output_file.display().to_string(), err))?;
        generate(self.shell, &mut command, "aptos".to_string(), &mut file);
        Ok(())
    }
}
<commit_msg>[aptos-cli] Add ability for global config on CLI<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::common::types::{CliCommand, CliError, CliResult, CliTypedResult};
use crate::common::utils::{read_from_file, write_to_user_only_file};
use crate::genesis::git::{from_yaml, to_yaml};
use crate::Tool;
use async_trait::async_trait;
use clap::ArgEnum;
use clap::CommandFactory;
use clap::Parser;
use clap_complete::{generate, Shell};
use serde::Deserialize;
use serde::Serialize;
use std::fmt::Formatter;
use std::path::PathBuf;
use std::str::FromStr;

/// Tool for configuration of the CLI tool
///
#[derive(Parser)]
pub enum ConfigTool {
    Init(crate::common::init::InitTool),
    GenerateShellCompletions(GenerateShellCompletions),
    SetGlobalConfig(SetGlobalConfig),
}

impl ConfigTool {
    pub async fn execute(self) -> CliResult {
        match self {
            ConfigTool::Init(tool) => tool.execute_serialized_success().await,
            ConfigTool::GenerateShellCompletions(tool) => tool.execute_serialized_success().await,
            ConfigTool::SetGlobalConfig(tool) => tool.execute_serialized_success().await,
        }
    }
}

/// Generates shell completion files
///
/// First generate the completion file, then follow the shell specific directions on how
/// to install the completion file.
#[derive(Parser)]
pub struct GenerateShellCompletions {
    /// Shell to generate completions for one of [bash, elvish, powershell, zsh]
    #[clap(long)]
    shell: Shell,
    /// File to output shell completions to
    #[clap(long, parse(from_os_str))]
    output_file: PathBuf,
}

#[async_trait]
impl CliCommand<()> for GenerateShellCompletions {
    fn command_name(&self) -> &'static str {
        "GenerateShellCompletions"
    }

    async fn execute(self) -> CliTypedResult<()> {
        let mut command = Tool::command();
        let mut file = std::fs::File::create(self.output_file.as_path())
            .map_err(|err| CliError::IO(self.output_file.display().to_string(), err))?;
        generate(self.shell, &mut command, "aptos".to_string(), &mut file);
        Ok(())
    }
}

/// Set global configuration settings
#[derive(Parser, Debug)]
pub struct SetGlobalConfig {
    /// A configuration for where to place and use the config
    ///
    /// Workspace allows for multiple configs based on location, where
    /// Global allows for one config for every part of the code
    #[clap(long)]
    config_type: Option<ConfigType>,
}

#[async_trait]
impl CliCommand<()> for SetGlobalConfig {
    fn command_name(&self) -> &'static str {
        "SetGlobalConfig"
    }

    async fn execute(self) -> CliTypedResult<()> {
        // Load the global config
        let mut config = GlobalConfig::load()?;

        // Enable all features that are actually listed
        if let Some(config_type) = self.config_type {
            config.config_type = config_type;
        }

        config.save()
    }
}

const GLOBAL_CONFIG_PATH: &str = "~/.aptos/global_config.yaml";

/// A global configuration for global settings related to a user
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct GlobalConfig {
    /// Whether to be using Global or Workspace mode
    pub config_type: ConfigType,
}

impl GlobalConfig {
    pub fn load() -> CliTypedResult<Self> {
        // TODO: Ensure this path works on all platforms
        let path = PathBuf::from(GLOBAL_CONFIG_PATH);
        if path.exists() {
            from_yaml(&String::from_utf8(read_from_file(path.as_path())?)?)
        } else {
            // If we don't have a config, let's load the default
            Ok(GlobalConfig::default())
        }
    }

    /// Get the config location based on the type
    pub fn get_config_location(&self) -> PathBuf {
        match self.config_type {
            ConfigType::Global => PathBuf::from("~/.aptos"),
            ConfigType::Workspace => PathBuf::from(".aptos"),
        }
    }

    fn save(&self) -> CliTypedResult<()> {
        write_to_user_only_file(
            PathBuf::from(GLOBAL_CONFIG_PATH).as_path(),
            "Global Config",
            &to_yaml(&self)?.into_bytes(),
        )
    }
}

const GLOBAL: &str = "global";
const WORKSPACE: &str = "workspace";

/// A configuration for where to place and use the config
///
/// Workspace allows for multiple configs based on location, where
/// Global allows for one config for every part of the code
#[derive(Debug, Copy, Clone, Serialize, Deserialize, ArgEnum)]
pub enum ConfigType {
    /// Per system user configuration put in `<HOME>/.aptos`
    Global,
    /// Per directory configuration put in `<CURRENT_DIR>/.aptos`
    Workspace,
}

impl Default for ConfigType {
    fn default() -> Self {
        // TODO: When we version up, we can change this to global
        Self::Workspace
    }
}

impl std::fmt::Display for ConfigType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            ConfigType::Global => GLOBAL,
            ConfigType::Workspace => WORKSPACE,
        })
    }
}

impl FromStr for ConfigType {
    type Err = CliError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().trim() {
            GLOBAL => Ok(Self::Global),
            WORKSPACE => Ok(Self::Workspace),
            _ => Err(CliError::CommandArgumentError(
                "Invalid config type, must be one of [global, workspace]".to_string(),
            )),
        }
    }
}
<|endoftext|>


<commit_before>use std::env;
use ansi_term::Colour::{Cyan, Blue, Red};
use ansi_term::ANSIStrings;
use git2;
use git2::{Repository, StatusOptions};
use regex::Regex;
use clap::{ArgMatches, App, SubCommand};

fn first_char(s: &str) -> String {
  let chars: Vec<char> = s.chars().collect();
  match chars.len() {
    0 => String::from(""),
    _ => chars[0].to_string(),
  }
}

fn fmt_current_path(cwd: &str) -> String {
  let friendly_path = if let Some(path) = env::home_dir() {
    Regex::new(path.to_str().unwrap()).unwrap().replace(cwd, "~").to_string()
  } else {
    String::from("")
  };

  let mut friendly_path_split: Vec<&str> = friendly_path.split('/').collect();
  let current_dir = friendly_path_split.pop().unwrap().to_string();
  let mut short_path: Vec<String> = friendly_path_split.iter().map(|s| first_char(s)).collect();
  short_path.push(current_dir);
  short_path.join("/")
}

fn repo_status(r: Repository) -> String {
  let mut opts = StatusOptions::new();
  opts.include_untracked(true);
  let head = r.head().unwrap();
  let shorthand = Cyan.paint(head.shorthand().unwrap().to_string());
  let statuses = r.statuses(Some(&mut opts)).unwrap();

  let mut is_dirty = false;

  for entry in statuses.iter().filter(
    |e| e.status() != git2::STATUS_CURRENT,
  ) {
    is_dirty = match entry.status() {
      s if s.contains(git2::STATUS_INDEX_NEW) => true,
      s if s.contains(git2::STATUS_INDEX_MODIFIED) => true,
      s if s.contains(git2::STATUS_INDEX_DELETED) => true,
      s if s.contains(git2::STATUS_INDEX_RENAMED) => true,
      s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => true,
      s if s.contains(git2::STATUS_WT_NEW) => true,
      s if s.contains(git2::STATUS_WT_MODIFIED) => true,
      s if s.contains(git2::STATUS_WT_DELETED) => true,
      s if s.contains(git2::STATUS_WT_RENAMED) => true,
      s if s.contains(git2::STATUS_WT_TYPECHANGE) => true,
      _ => false,
    };
    match is_dirty {
      true => break,
      false => continue,
    }
  }
  let mut out = vec![shorthand];
  if is_dirty == true {
    out.push(Red.bold().paint("*"));
  }

  ANSIStrings(&out).to_string()
}

pub fn display(_sub: &ArgMatches) {
  let my_path = env::current_dir().unwrap();
  let display_path = Blue.paint(fmt_current_path(my_path.to_str().unwrap()));

  let branch = match Repository::open(my_path) {
    Ok(repo) => repo_status(repo),
    Err(_e) => String::from(""),
  };
  let display_branch = Cyan.paint(branch);

  println!("");
  println!("{} {}", display_path, display_branch);
}

pub fn cli_arguments<'a>() -> App<'a, 'a> {
  SubCommand::with_name("precmd")
}
<commit_msg>Replace by-value with by-ref parameter<commit_after>use std::env;
use ansi_term::Colour::{Cyan, Blue, Red};
use ansi_term::ANSIStrings;
use git2;
use git2::{Repository, StatusOptions};
use regex::Regex;
use clap::{ArgMatches, App, SubCommand};

fn first_char(s: &str) -> String {
  let chars: Vec<char> = s.chars().collect();
  match chars.len() {
    0 => String::from(""),
    _ => chars[0].to_string(),
  }
}

fn fmt_current_path(cwd: &str) -> String {
  let friendly_path = if let Some(path) = env::home_dir() {
    Regex::new(path.to_str().unwrap()).unwrap().replace(cwd, "~").to_string()
  } else {
    String::from("")
  };

  let mut friendly_path_split: Vec<&str> = friendly_path.split('/').collect();
  let current_dir = friendly_path_split.pop().unwrap().to_string();
  let mut short_path: Vec<String> = friendly_path_split.iter().map(|s| first_char(s)).collect();
  short_path.push(current_dir);
  short_path.join("/")
}

fn repo_status(r: &Repository) -> String {
  let mut opts = StatusOptions::new();
  opts.include_untracked(true);
  let head = r.head().unwrap();
  let shorthand = Cyan.paint(head.shorthand().unwrap().to_string());
  let statuses = r.statuses(Some(&mut opts)).unwrap();

  let mut is_dirty = false;

  for entry in statuses.iter().filter(
    |e| e.status() != git2::STATUS_CURRENT,
  ) {
    is_dirty = match entry.status() {
      s if s.contains(git2::STATUS_INDEX_NEW) => true,
      s if s.contains(git2::STATUS_INDEX_MODIFIED) => true,
      s if s.contains(git2::STATUS_INDEX_DELETED) => true,
      s if s.contains(git2::STATUS_INDEX_RENAMED) => true,
      s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => true,
      s if s.contains(git2::STATUS_WT_NEW) => true,
      s if s.contains(git2::STATUS_WT_MODIFIED) => true,
      s if s.contains(git2::STATUS_WT_DELETED) => true,
      s if s.contains(git2::STATUS_WT_RENAMED) => true,
      s if s.contains(git2::STATUS_WT_TYPECHANGE) => true,
      _ => false,
    };
    match is_dirty {
      true => break,
      false => continue,
    }
  }
  let mut out = vec![shorthand];
  if is_dirty == true {
    out.push(Red.bold().paint("*"));
  }

  ANSIStrings(&out).to_string()
}

pub fn display(_sub: &ArgMatches) {
  let my_path = env::current_dir().unwrap();
  let display_path = Blue.paint(fmt_current_path(my_path.to_str().unwrap()));

  let branch = match Repository::open(my_path) {
    Ok(repo) => repo_status(&repo),
    Err(_e) => String::from(""),
  };
  let display_branch = Cyan.paint(branch);

  println!("");
  println!("{} {}", display_path, display_branch);
}

pub fn cli_arguments<'a>() -> App<'a, 'a> {
  SubCommand::with_name("precmd")
}
<|endoftext|>