<commit_before>// Copyright 2018 Syn Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Parsing interface for parsing a token stream into a syntax tree node.
//!
//! Parsing in Syn is built on parser functions that take in a [`Cursor`] and
//! produce a [`PResult<T>`] where `T` is some syntax tree node. `Cursor` is a
//! cheaply copyable cursor over a range of tokens in a token stream, and
//! `PResult` is a result that packages together a parsed syntax tree node `T`
//! with a stream of remaining unparsed tokens after `T` represented as another
//! `Cursor`, or a [`ParseError`] if parsing failed.
//!
//! [`Cursor`]: ../buffer/index.html
//! [`PResult<T>`]: type.PResult.html
//! [`ParseError`]: struct.ParseError.html
//!
//! This `Cursor`- and `PResult`-based interface is convenient for parser
//! combinators and parser implementations, but not necessarily when you just
//! have some tokens that you want to parse. For that we expose the following
//! two entry points.
//!
//! ## The `syn::parse*` functions
//!
//! The [`syn::parse`], [`syn::parse2`], and [`syn::parse_str`] functions serve
//! as an entry point for parsing syntax tree nodes that can be parsed in an
//! obvious default way. These functions can return any syntax tree node that
//! implements the [`Synom`] trait, which includes most types in Syn.
//!
//! [`syn::parse`]: ../fn.parse.html
//! [`syn::parse2`]: ../fn.parse2.html
//! [`syn::parse_str`]: ../fn.parse_str.html
//! [`Synom`]: trait.Synom.html
//!
//! ```
//! use syn::Type;
//!
//! # fn run_parser() -> Result<(), syn::synom::ParseError> {
//! let t: Type = syn::parse_str("std::collections::HashMap<String, Value>")?;
//! #     Ok(())
//! # }
//! #
//! # fn main() {
//! #     run_parser().unwrap();
//! # }
//! ```
//!
//! The [`parse_quote!`] macro also uses this approach.
//!
//! [`parse_quote!`]: ../macro.parse_quote.html
//!
//! ## The `Parser` trait
//!
//! Some types can be parsed in several ways depending on context. For example
//! an [`Attribute`] can be either "outer" like `#[...]` or "inner" like
//! `#![...]` and parsing the wrong one would be a bug. Similarly [`Punctuated`]
//! may or may not allow trailing punctuation, and parsing it the wrong way
//! would either reject valid input or accept invalid input.
//!
//! [`Attribute`]: ../struct.Attribute.html
//! [`Punctuated`]: ../punctuated/index.html
//!
//! The `Synom` trait is not implemented in these cases because there is no good
//! behavior to consider the default.
//!
//! ```ignore
//! // Can't parse `Punctuated` without knowing whether trailing punctuation
//! // should be allowed in this context.
//! let path: Punctuated<PathSegment, Token![::]> = syn::parse(tokens)?;
//! ```
//!
//! In these cases the types provide a choice of parser functions rather than a
//! single `Synom` implementation, and those parser functions can be invoked
//! through the [`Parser`] trait.
//!
//! [`Parser`]: trait.Parser.html
//!
//! ```
//! # #[macro_use]
//! # extern crate syn;
//! #
//! # extern crate proc_macro2;
//! # use proc_macro2::TokenStream;
//! #
//! use syn::synom::Parser;
//! use syn::punctuated::Punctuated;
//! use syn::{PathSegment, Expr, Attribute};
//!
//! # fn run_parsers() -> Result<(), syn::synom::ParseError> {
//! #     let tokens = TokenStream::empty().into();
//! // Parse a nonempty sequence of path segments separated by `::` punctuation
//! // with no trailing punctuation.
//! let parser = Punctuated::<PathSegment, Token![::]>::parse_separated_nonempty;
//! let path = parser.parse(tokens)?;
//!
//! #     let tokens = TokenStream::empty().into();
//! // Parse a possibly empty sequence of expressions terminated by commas with
//! // an optional trailing punctuation.
//! let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
//! let args = parser.parse(tokens)?;
//!
//! #     let tokens = TokenStream::empty().into();
//! // Parse zero or more outer attributes but not inner attributes.
//! named!(outer_attrs -> Vec<Attribute>, many0!(Attribute::parse_outer));
//! let attrs = outer_attrs.parse(tokens)?;
//! #
//! #     Ok(())
//! # }
//! #
//! # fn main() {}
//! ```
//!
//! # Implementing a parser function
//!
//! Parser functions are usually implemented using the [`nom`]-style parser
//! combinator macros provided by Syn, but may also be implemented without
//! macros be using the low-level [`Cursor`] API directly.
//!
//! [`nom`]: https://github.com/Geal/nom
//!
//! The following parser combinator macros are available and a `Synom` parsing
//! example is provided for each one.
//!
//! - [`alt!`](../macro.alt.html)
//! - [`braces!`](../macro.braces.html)
//! - [`brackets!`](../macro.brackets.html)
//! - [`call!`](../macro.call.html)
//! - [`cond!`](../macro.cond.html)
//! - [`cond_reduce!`](../macro.cond_reduce.html)
//! - [`do_parse!`](../macro.do_parse.html)
//! - [`epsilon!`](../macro.epsilon.html)
//! - [`input_end!`](../macro.input_end.html)
//! - [`keyword!`](../macro.keyword.html)
//! - [`many0!`](../macro.many0.html)
//! - [`map!`](../macro.map.html)
//! - [`not!`](../macro.not.html)
//! - [`option!`](../macro.option.html)
//! - [`parens!`](../macro.parens.html)
//! - [`punct!`](../macro.punct.html)
//! - [`reject!`](../macro.reject.html)
//! - [`switch!`](../macro.switch.html)
//! - [`syn!`](../macro.syn.html)
//! - [`tuple!`](../macro.tuple.html)
//! - [`value!`](../macro.value.html)
//!
//! *This module is available if Syn is built with the `"parsing"` feature.*

use proc_macro;
use proc_macro2;

pub use error::{PResult, ParseError};

use buffer::{Cursor, TokenBuffer};

/// Parsing interface implemented by all types that can be parsed in a default
/// way from a token stream.
///
/// Refer to the [module documentation] for details about parsing in Syn.
///
/// [module documentation]: index.html
///
/// *This trait is available if Syn is built with the `"parsing"` feature.*
pub trait Synom: Sized {
    fn parse(input: Cursor) -> PResult<Self>;

    /// A short name of the type being parsed.
    ///
    /// The description should only be used for a simple name.  It should not
    /// contain newlines or sentence-ending punctuation, to facilitate embedding in
    /// larger user-facing strings.  Syn will use this description when building
    /// error messages about parse failures.
    ///
    /// # Examples
    ///
    /// ```
    /// # use syn::buffer::Cursor;
    /// # use syn::synom::{Synom, PResult};
    /// #
    /// struct ExprForLoop {
    ///     // ...
    /// }
    ///
    /// impl Synom for ExprForLoop {
    /// #   fn parse(input: Cursor) -> PResult<Self> { unimplemented!() }
    ///     // fn parse(...) -> ... { ... }
    ///
    ///     fn description() -> Option<&'static str> {
    ///         // will result in messages like "failed to parse `for` loop: $reason"
    ///         Some("`for` loop")
    ///     }
    /// }
    /// ```
    fn description() -> Option<&'static str> {
        None
    }
}

impl Synom for proc_macro2::TokenStream {
    fn parse(input: Cursor) -> PResult<Self> {
        Ok((input.token_stream(), Cursor::empty()))
    }

    fn description() -> Option<&'static str> {
        Some("arbitrary token stream")
    }
}

/// Parser that can parse Rust tokens into a particular syntax tree node.
///
/// Refer to the [module documentation] for details about parsing in Syn.
///
/// [module documentation]: index.html
///
/// *This trait is available if Syn is built with the `"parsing"` feature.*
pub trait Parser: Sized {
    type Output;

    /// Parse a proc-macro2 token stream into the chosen syntax tree node.
    fn parse2(self, tokens: proc_macro2::TokenStream) -> Result<Self::Output, ParseError>;

    /// Parse tokens of source code into the chosen syntax tree node.
    fn parse(self, tokens: proc_macro::TokenStream) -> Result<Self::Output, ParseError> {
        self.parse2(tokens.into())
    }

    /// Parse a string of Rust code into the chosen syntax tree node.
    ///
    /// # Hygiene
    ///
    /// Every span in the resulting syntax tree will be set to resolve at the
    /// macro call site.
    fn parse_str(self, s: &str) -> Result<Self::Output, ParseError> {
        match s.parse() {
            Ok(tts) => self.parse2(tts),
            Err(_) => Err(ParseError::new("error while lexing input string")),
        }
    }
}

impl<F, T> Parser for F where F: FnOnce(Cursor) -> PResult<T> {
    type Output = T;

    fn parse2(self, tokens: proc_macro2::TokenStream) -> Result<T, ParseError> {
        let buf = TokenBuffer::new2(tokens);
        let (t, rest) = self(buf.begin())?;
        if rest.eof() {
            Ok(t)
        } else if rest == buf.begin() {
            // parsed nothing
            Err(ParseError::new("failed to parse anything"))
        } else {
            Err(ParseError::new("failed to parse all tokens"))
        }
    }
}
<commit_msg>Pick a description that sounds less mechanical<commit_after>// Copyright 2018 Syn Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Parsing interface for parsing a token stream into a syntax tree node.
//!
//! Parsing in Syn is built on parser functions that take in a [`Cursor`] and
//! produce a [`PResult<T>`] where `T` is some syntax tree node. `Cursor` is a
//! cheaply copyable cursor over a range of tokens in a token stream, and
//! `PResult` is a result that packages together a parsed syntax tree node `T`
//! with a stream of remaining unparsed tokens after `T` represented as another
//! `Cursor`, or a [`ParseError`] if parsing failed.
//!
//! [`Cursor`]: ../buffer/index.html
//! [`PResult<T>`]: type.PResult.html
//! [`ParseError`]: struct.ParseError.html
//!
//! This `Cursor`- and `PResult`-based interface is convenient for parser
//! combinators and parser implementations, but not necessarily when you just
//! have some tokens that you want to parse. For that we expose the following
//! two entry points.
//!
//! ## The `syn::parse*` functions
//!
//! The [`syn::parse`], [`syn::parse2`], and [`syn::parse_str`] functions serve
//! as an entry point for parsing syntax tree nodes that can be parsed in an
//! obvious default way. These functions can return any syntax tree node that
//! implements the [`Synom`] trait, which includes most types in Syn.
//!
//! [`syn::parse`]: ../fn.parse.html
//! [`syn::parse2`]: ../fn.parse2.html
//! [`syn::parse_str`]: ../fn.parse_str.html
//! [`Synom`]: trait.Synom.html
//!
//! ```
//! use syn::Type;
//!
//! # fn run_parser() -> Result<(), syn::synom::ParseError> {
//! let t: Type = syn::parse_str("std::collections::HashMap<String, Value>")?;
//! #     Ok(())
//! # }
//! #
//! # fn main() {
//! #     run_parser().unwrap();
//! # }
//! ```
//!
//! The [`parse_quote!`] macro also uses this approach.
//!
//! [`parse_quote!`]: ../macro.parse_quote.html
//!
//! ## The `Parser` trait
//!
//! Some types can be parsed in several ways depending on context. For example
//! an [`Attribute`] can be either "outer" like `#[...]` or "inner" like
//! `#![...]` and parsing the wrong one would be a bug. Similarly [`Punctuated`]
//! may or may not allow trailing punctuation, and parsing it the wrong way
//! would either reject valid input or accept invalid input.
//!
//! [`Attribute`]: ../struct.Attribute.html
//! [`Punctuated`]: ../punctuated/index.html
//!
//! The `Synom` trait is not implemented in these cases because there is no good
//! behavior to consider the default.
//!
//! ```ignore
//! // Can't parse `Punctuated` without knowing whether trailing punctuation
//! // should be allowed in this context.
//! let path: Punctuated<PathSegment, Token![::]> = syn::parse(tokens)?;
//! ```
//!
//! In these cases the types provide a choice of parser functions rather than a
//! single `Synom` implementation, and those parser functions can be invoked
//! through the [`Parser`] trait.
//!
//! [`Parser`]: trait.Parser.html
//!
//! ```
//! # #[macro_use]
//! # extern crate syn;
//! #
//! # extern crate proc_macro2;
//! # use proc_macro2::TokenStream;
//! #
//! use syn::synom::Parser;
//! use syn::punctuated::Punctuated;
//! use syn::{PathSegment, Expr, Attribute};
//!
//! # fn run_parsers() -> Result<(), syn::synom::ParseError> {
//! #     let tokens = TokenStream::empty().into();
//! // Parse a nonempty sequence of path segments separated by `::` punctuation
//! // with no trailing punctuation.
//! let parser = Punctuated::<PathSegment, Token![::]>::parse_separated_nonempty;
//! let path = parser.parse(tokens)?;
//!
//! #     let tokens = TokenStream::empty().into();
//! // Parse a possibly empty sequence of expressions terminated by commas with
//! // an optional trailing punctuation.
//! let parser = Punctuated::<Expr, Token![,]>::parse_terminated;
//! let args = parser.parse(tokens)?;
//!
//! #     let tokens = TokenStream::empty().into();
//! // Parse zero or more outer attributes but not inner attributes.
//! named!(outer_attrs -> Vec<Attribute>, many0!(Attribute::parse_outer));
//! let attrs = outer_attrs.parse(tokens)?;
//! #
//! #     Ok(())
//! # }
//! #
//! # fn main() {}
//! ```
//!
//! # Implementing a parser function
//!
//! Parser functions are usually implemented using the [`nom`]-style parser
//! combinator macros provided by Syn, but may also be implemented without
//! macros be using the low-level [`Cursor`] API directly.
//!
//! [`nom`]: https://github.com/Geal/nom
//!
//! The following parser combinator macros are available and a `Synom` parsing
//! example is provided for each one.
//!
//! - [`alt!`](../macro.alt.html)
//! - [`braces!`](../macro.braces.html)
//! - [`brackets!`](../macro.brackets.html)
//! - [`call!`](../macro.call.html)
//! - [`cond!`](../macro.cond.html)
//! - [`cond_reduce!`](../macro.cond_reduce.html)
//! - [`do_parse!`](../macro.do_parse.html)
//! - [`epsilon!`](../macro.epsilon.html)
//! - [`input_end!`](../macro.input_end.html)
//! - [`keyword!`](../macro.keyword.html)
//! - [`many0!`](../macro.many0.html)
//! - [`map!`](../macro.map.html)
//! - [`not!`](../macro.not.html)
//! - [`option!`](../macro.option.html)
//! - [`parens!`](../macro.parens.html)
//! - [`punct!`](../macro.punct.html)
//! - [`reject!`](../macro.reject.html)
//! - [`switch!`](../macro.switch.html)
//! - [`syn!`](../macro.syn.html)
//! - [`tuple!`](../macro.tuple.html)
//! - [`value!`](../macro.value.html)
//!
//! *This module is available if Syn is built with the `"parsing"` feature.*

use proc_macro;
use proc_macro2;

pub use error::{PResult, ParseError};

use buffer::{Cursor, TokenBuffer};

/// Parsing interface implemented by all types that can be parsed in a default
/// way from a token stream.
///
/// Refer to the [module documentation] for details about parsing in Syn.
///
/// [module documentation]: index.html
///
/// *This trait is available if Syn is built with the `"parsing"` feature.*
pub trait Synom: Sized {
    fn parse(input: Cursor) -> PResult<Self>;

    /// A short name of the type being parsed.
    ///
    /// The description should only be used for a simple name.  It should not
    /// contain newlines or sentence-ending punctuation, to facilitate embedding in
    /// larger user-facing strings.  Syn will use this description when building
    /// error messages about parse failures.
    ///
    /// # Examples
    ///
    /// ```
    /// # use syn::buffer::Cursor;
    /// # use syn::synom::{Synom, PResult};
    /// #
    /// struct ExprMacro {
    ///     // ...
    /// }
    ///
    /// impl Synom for ExprMacro {
    /// #   fn parse(input: Cursor) -> PResult<Self> { unimplemented!() }
    ///     // fn parse(...) -> ... { ... }
    ///
    ///     fn description() -> Option<&'static str> {
    ///         // Will result in messages like
    ///         //
    ///         //     "failed to parse macro invocation expression: $reason"
    ///         Some("macro invocation expression")
    ///     }
    /// }
    /// ```
    fn description() -> Option<&'static str> {
        None
    }
}

impl Synom for proc_macro2::TokenStream {
    fn parse(input: Cursor) -> PResult<Self> {
        Ok((input.token_stream(), Cursor::empty()))
    }

    fn description() -> Option<&'static str> {
        Some("arbitrary token stream")
    }
}

/// Parser that can parse Rust tokens into a particular syntax tree node.
///
/// Refer to the [module documentation] for details about parsing in Syn.
///
/// [module documentation]: index.html
///
/// *This trait is available if Syn is built with the `"parsing"` feature.*
pub trait Parser: Sized {
    type Output;

    /// Parse a proc-macro2 token stream into the chosen syntax tree node.
    fn parse2(self, tokens: proc_macro2::TokenStream) -> Result<Self::Output, ParseError>;

    /// Parse tokens of source code into the chosen syntax tree node.
    fn parse(self, tokens: proc_macro::TokenStream) -> Result<Self::Output, ParseError> {
        self.parse2(tokens.into())
    }

    /// Parse a string of Rust code into the chosen syntax tree node.
    ///
    /// # Hygiene
    ///
    /// Every span in the resulting syntax tree will be set to resolve at the
    /// macro call site.
    fn parse_str(self, s: &str) -> Result<Self::Output, ParseError> {
        match s.parse() {
            Ok(tts) => self.parse2(tts),
            Err(_) => Err(ParseError::new("error while lexing input string")),
        }
    }
}

impl<F, T> Parser for F where F: FnOnce(Cursor) -> PResult<T> {
    type Output = T;

    fn parse2(self, tokens: proc_macro2::TokenStream) -> Result<T, ParseError> {
        let buf = TokenBuffer::new2(tokens);
        let (t, rest) = self(buf.begin())?;
        if rest.eof() {
            Ok(t)
        } else if rest == buf.begin() {
            // parsed nothing
            Err(ParseError::new("failed to parse anything"))
        } else {
            Err(ParseError::new("failed to parse all tokens"))
        }
    }
}
<|endoftext|>


<commit_before>extern crate csv;

use std::path::PathBuf;
use std::thread;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::io::{self, Error, ErrorKind};

use walkdir::WalkDir;

use product::{self, ProductSHA, ProductMatch};
use product::RowSerializer;
use configs;
use api;
use checker;

pub fn start_path_scanner(dir: PathBuf)
    -> (Receiver<ProductSHA>, thread::JoinHandle<Result<(), io::Error>> ) {

    let (sender, receiver) = channel::<ProductSHA>();
    let handle = thread::spawn(move || {
        if dir.exists() == false {
            return Err(
              Error::new(ErrorKind::Other, "Scannable folder doesnt exists")
            );
        }

        for entry in WalkDir::new(&dir).into_iter().filter_map(|e| e.ok()){
            if let Some(sha) = checker::digest_file(&entry.path()) {
                if sender.send(sha).is_err() {
                    println!(
                        "start_path_scanner2: failed to send ProductSHA for {}",
                        entry.path().display()
                    );
                    break
                }
            }
        }

        Ok(())
    });

    (receiver, handle)
}

//pumps vector of SHAs onto sha channel
pub fn start_sha_publisher(shas: Vec<ProductSHA>)
    -> (Receiver<ProductSHA>, thread::JoinHandle<Result<(), io::Error>>) {

    let (sender, receiver) = channel::<ProductSHA>();
    let handle = thread::spawn(move || {
        for sha in shas.into_iter() {
            if sender.send(sha).is_err() {
                println!("start_sha_publisher: failed to send ProductSHAs");
                break
            }
        }

        Ok(())
    });

    (receiver, handle)
}

//pumps each item of productMatch vector onto product channel
//used to simplify testing
pub fn start_product_match_publisher(prod_matches: Vec<ProductMatch>)
    -> (Receiver<ProductMatch>, thread::JoinHandle<Result<(), io::Error>>) {

    let (sender, receiver) = channel::<ProductMatch>();
    let handle = thread::spawn(move || {
        for prod_match in prod_matches.into_iter() {
            if sender.send(prod_match).is_err() {
                println!("start_product_match_publisher: failed to pipe ProductMatch onto channel");
                break
            }
        }

        Ok(())
    });

    (receiver, handle)
}

pub fn start_sha_fetcher(api_configs: configs::ApiConfigs, sha_ch:  Receiver<ProductSHA>)
    -> (Receiver<product::ProductMatch>, thread::JoinHandle<io::Result<()>>) {

    let (sender, receiver) = channel::<ProductMatch>();
    let handle = thread::spawn(move || {
        for sha in sha_ch.into_iter() {
            let sha_code = sha.value.clone();
            let prod = match api::fetch_product_details_by_sha(&api_configs, sha_code.as_str()) {
                Ok(mut m) => {
                    m.sha = Some(sha); //attach original sha document to have filepath data
                    m
                },
                Err(_) => {
                    //use empty product, so non-matched products will show up in output file
                    let mut m = ProductMatch::empty();
                    m.sha = Some(sha);
                    m
                }
            };

            if sender.send(prod).is_err(){
                break;
            }
        }

        Ok(())
    });

    (receiver, handle)
}

pub fn start_product_csv_writer(outpath: PathBuf, product_ch: Receiver<ProductMatch>)
    -> thread::JoinHandle< Result<(), csv::Error> > {

    thread::spawn(move || {
        let mut n = 0u32;
        let mut wtr = csv::Writer::from_file(outpath).expect("Failed to open output file");

        println!();
        for product in product_ch.into_iter() {
            if n == 0 {
                wtr.encode(product.to_fields()).unwrap();
            };

            for row in product.to_rows().into_iter() {
                wtr.encode(row).unwrap();
            }

            print!("\rrow: {}", n + 1); //to show some progress
            n += 1;
        }

        println!();
        Ok(())
    })

}

pub fn start_product_stdio_writer(product_ch: Receiver<ProductMatch>)
    -> thread::JoinHandle<Result<(), csv::Error >> {

    thread::spawn(move || {
        let mut n = 0u32;

        for product in product_ch.into_iter() {
            let mut wtr = csv::Writer::from_memory();

            if n == 0 {
                wtr.encode(product.to_fields()).unwrap();
            }

            for row in product.to_rows().into_iter() {
                wtr.encode(row).unwrap();
            }

            print!("{}", wtr.as_string());
            n += 1;
        }

        Ok(())
    })
}

pub fn start_sha_csv_writer(outpath: PathBuf, sha_ch: Receiver<ProductSHA>)
    -> thread::JoinHandle< Result<(), csv::Error> > {

    thread::spawn(move || {
        let mut n = 0u32;
        let mut wtr = csv::Writer::from_file(outpath).expect("Failed to open output file");

        println!();
        for sha in sha_ch.into_iter() {
            if n == 0 {
                wtr.encode(sha.to_fields()).unwrap();
            };

            if let Some(row) = sha.to_rows().pop() {
                wtr.encode(row).unwrap();
            }

            print!("\rrow: {}", n + 1); //to show some progress
            n += 1;
        }

        println!();
        Ok(())
    })

}

pub fn start_sha_stdio_writer(sha_ch: Receiver<ProductSHA>)
    -> thread::JoinHandle<Result<(), csv::Error >> {

    thread::spawn(move || {
        let mut n = 0u32;

        for sha in sha_ch.into_iter() {
            let mut wtr = csv::Writer::from_memory();

            if n == 0 {
                wtr.encode(sha.to_fields()).unwrap();
            }

            if let Some(row) = sha.to_rows().pop() {
                wtr.encode(row).unwrap();
            }

            print!("{}", wtr.as_string());
            n += 1;
        }

        Ok(())
    })
}<commit_msg>fix #12 - attach error message from APIs into ProducMatch model passed to outputters;<commit_after>extern crate csv;

use std::path::PathBuf;
use std::thread;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::io::{self, ErrorKind};
use std::error::Error;

use walkdir::WalkDir;

use product::{self, ProductSHA, ProductMatch};
use product::RowSerializer;
use configs;
use api;
use checker;

pub fn start_path_scanner(dir: PathBuf)
    -> (Receiver<ProductSHA>, thread::JoinHandle<Result<(), io::Error>> ) {

    let (sender, receiver) = channel::<ProductSHA>();
    let handle = thread::spawn(move || {
        if dir.exists() == false {
            return Err(
              io::Error::new(ErrorKind::Other, "Scannable folder doesnt exists")

            );
        }

        for entry in WalkDir::new(&dir).into_iter().filter_map(|e| e.ok()){
            if let Some(sha) = checker::digest_file(&entry.path()) {
                if sender.send(sha).is_err() {
                    println!(
                        "start_path_scanner2: failed to send ProductSHA for {}",
                        entry.path().display()
                    );
                    break
                }
            }
        }

        Ok(())
    });

    (receiver, handle)
}

//pumps vector of SHAs onto sha channel
pub fn start_sha_publisher(shas: Vec<ProductSHA>)
    -> (Receiver<ProductSHA>, thread::JoinHandle<Result<(), io::Error>>) {

    let (sender, receiver) = channel::<ProductSHA>();
    let handle = thread::spawn(move || {
        for sha in shas.into_iter() {
            if sender.send(sha).is_err() {
                println!("start_sha_publisher: failed to send ProductSHAs");
                break
            }
        }

        Ok(())
    });

    (receiver, handle)
}

//pumps each item of productMatch vector onto product channel
//used to simplify testing
pub fn start_product_match_publisher(prod_matches: Vec<ProductMatch>)
    -> (Receiver<ProductMatch>, thread::JoinHandle<Result<(), io::Error>>) {

    let (sender, receiver) = channel::<ProductMatch>();
    let handle = thread::spawn(move || {
        for prod_match in prod_matches.into_iter() {
            if sender.send(prod_match).is_err() {
                println!("start_product_match_publisher: failed to pipe ProductMatch onto channel");
                break
            }
        }

        Ok(())
    });

    (receiver, handle)
}

pub fn start_sha_fetcher(api_configs: configs::ApiConfigs, sha_ch:  Receiver<ProductSHA>)
    -> (Receiver<product::ProductMatch>, thread::JoinHandle<io::Result<()>>) {

    let (sender, receiver) = channel::<ProductMatch>();
    let handle = thread::spawn(move || {
        for sha in sha_ch.into_iter() {
            let sha_code = sha.value.clone();
            let prod = match api::fetch_product_details_by_sha(&api_configs, sha_code.as_str()) {
                Ok(mut m) => {
                    m.sha = Some(sha); //attach original sha document to have filepath data
                    m
                },
                Err(e) => {
                    //use empty product, so non-matched products will show up in output file
                    let mut m = ProductMatch::empty();
                    m.sha = Some(sha);
                    m.error = Some(e.description().to_string()); //attach error message
                    m
                }
            };

            if sender.send(prod).is_err(){
                break;
            }
        }

        Ok(())
    });

    (receiver, handle)
}

pub fn start_product_csv_writer(outpath: PathBuf, product_ch: Receiver<ProductMatch>)
    -> thread::JoinHandle< Result<(), csv::Error> > {

    thread::spawn(move || {
        let mut n = 0u32;
        let mut wtr = csv::Writer::from_file(outpath).expect("Failed to open output file");

        println!();
        for product in product_ch.into_iter() {
            if n == 0 {
                wtr.encode(product.to_fields()).unwrap();
            };

            for row in product.to_rows().into_iter() {
                wtr.encode(row).unwrap();
            }

            print!("\rrow: {}", n + 1); //to show some progress
            n += 1;
        }

        println!();
        Ok(())
    })

}

pub fn start_product_stdio_writer(product_ch: Receiver<ProductMatch>)
    -> thread::JoinHandle<Result<(), csv::Error >> {

    thread::spawn(move || {
        let mut n = 0u32;

        for product in product_ch.into_iter() {
            let mut wtr = csv::Writer::from_memory();

            if n == 0 {
                wtr.encode(product.to_fields()).unwrap();
            }

            for row in product.to_rows().into_iter() {
                wtr.encode(row).unwrap();
            }

            print!("{}", wtr.as_string());
            n += 1;
        }

        Ok(())
    })
}

pub fn start_sha_csv_writer(outpath: PathBuf, sha_ch: Receiver<ProductSHA>)
    -> thread::JoinHandle< Result<(), csv::Error> > {

    thread::spawn(move || {
        let mut n = 0u32;
        let mut wtr = csv::Writer::from_file(outpath).expect("Failed to open output file");

        println!();
        for sha in sha_ch.into_iter() {
            if n == 0 {
                wtr.encode(sha.to_fields()).unwrap();
            };

            if let Some(row) = sha.to_rows().pop() {
                wtr.encode(row).unwrap();
            }

            print!("\rrow: {}", n + 1); //to show some progress
            n += 1;
        }

        println!();
        Ok(())
    })

}

pub fn start_sha_stdio_writer(sha_ch: Receiver<ProductSHA>)
    -> thread::JoinHandle<Result<(), csv::Error >> {

    thread::spawn(move || {
        let mut n = 0u32;

        for sha in sha_ch.into_iter() {
            let mut wtr = csv::Writer::from_memory();

            if n == 0 {
                wtr.encode(sha.to_fields()).unwrap();
            }

            if let Some(row) = sha.to_rows().pop() {
                wtr.encode(row).unwrap();
            }

            print!("{}", wtr.as_string());
            n += 1;
        }

        Ok(())
    })
}<|endoftext|>


<commit_before>// TODO maybe pull from https://github.com/rust-lang/rust-www/blob/master/_data/team.yml instead

use std::collections::BTreeMap;

use diesel::prelude::*;
use toml;
use serde::de;

use super::DB_POOL;
use domain::github::GitHubUser;
use error::*;

//==============================================================================
// Public API
//==============================================================================

lazy_static! {
    pub static ref SETUP: RfcbotConfig = read_rfcbot_cfg_validated();
}

#[derive(Debug, Deserialize)]
pub struct RfcbotConfig {
    fcp_behaviors: BTreeMap<String, FcpBehavior>,
    teams: BTreeMap<TeamLabel, Team>,
}

impl RfcbotConfig {
    /// Retrive an iterator over all the team labels.
    pub fn team_labels(&self) -> impl Iterator<Item = &TeamLabel> {
        self.teams.keys()
    }

    /// Retrive an iterator over all the (team label, team) pairs.
    pub fn teams(&self) -> impl Iterator<Item = (&TeamLabel, &Team)> {
        self.teams.iter()
    }

    /// Are we allowed to auto-close issues after F-FCP in this repo?
    pub fn ffcp_auto_close(&self, repo: &str) -> bool {
        self.fcp_behaviors.get(repo).map(|fcp| fcp.close).unwrap_or_default()
    }

    /// Are we allowed to auto-postpone issues after F-FCP in this repo?
    pub fn ffcp_auto_postpone(&self, repo: &str) -> bool {
        self.fcp_behaviors.get(repo).map(|fcp| fcp.postpone).unwrap_or_default()
    }
}

#[derive(Debug, Deserialize)]
pub struct FcpBehavior {
    #[serde(default)]
    close: bool,
    #[serde(default)]
    postpone: bool,
}

#[derive(Debug, Deserialize)]
pub struct Team {
    // FIXME: The two following first fields don't seem to be used anywhere...
    // If this is intended, document why.
    name: String,
    ping: String,
    members: Vec<String>,
}

impl Team {
    pub fn member_logins(&self) -> impl Iterator<Item = &str> {
        self.members.iter().map(|s| s.as_str())
    }
}

#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TeamLabel(pub String);

impl<'de> de::Deserialize<'de> for TeamLabel {
    fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
        String::deserialize(de).map(TeamLabel)
    }

    fn deserialize_in_place<D: de::Deserializer<'de>>(de: D, place: &mut Self)
        -> Result<(), D::Error>
    {
        String::deserialize_in_place(de, &mut place.0)
    }
}

//==============================================================================
// Implementation details
//==============================================================================

/// Read the validated `rfcbot.toml` configuration file.
fn read_rfcbot_cfg_validated() -> RfcbotConfig {
    let cfg = read_rfcbot_cfg();

    cfg.teams.values().for_each(|team|
        team.validate()
            .expect("unable to verify team member from database.
if you're running this for tests, make sure you've pulled github users from prod")
    );

    cfg
}

/// Read the unprocessed `rfcbot.toml` configuration file.
fn read_rfcbot_cfg() -> RfcbotConfig {
    read_rfcbot_cfg_from(
        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/rfcbot.toml")))
}

fn read_rfcbot_cfg_from(input: &str) -> RfcbotConfig {
    toml::from_str(input).expect("couldn't parse rfcbot.toml!")
}

impl Team {
    fn validate(&self) -> DashResult<()> {
        use domain::schema::githubuser::dsl::*;
        let conn = &*(DB_POOL.get()?);

        // bail if they don't exist, but we don't want to actually keep the id in ram
        for member_login in self.member_logins() {
            match githubuser
                    .filter(login.eq(member_login))
                    .first::<GitHubUser>(conn)
            {
                Ok(_) => (),
                Err(why) => {
                    error!("unable to find {} in database: {:?}", member_login, why);
                    return Err(why.into());
                }
            }
        }

        Ok(())
    }
}

//==============================================================================
// Tests
//==============================================================================

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

    #[test]
    fn setup_parser_correct() {
let test = r#"
[fcp_behaviors]

[fcp_behaviors."rust-lang/alpha"]
close = true
postpone = true

[fcp_behaviors."foobar/beta"]
close = false

[fcp_behaviors."bazquux/gamma"]
postpone = false

[fcp_behaviors."wibble/epsilon"]

[teams]

[teams.avengers]
name = "The Avengers"
ping = "marvel/avengers"
members = [
  "hulk",
  "thor",
  "thevision",
  "blackwidow",
  "spiderman",
  "captainamerica",
]

[teams.justice-league]
name = "Justice League of America"
ping = "dc-comics/justice-league"
members = [
  "superman",
  "wonderwoman",
  "aquaman",
  "batman",
  "theflash"
]
"#;
        let cfg = read_rfcbot_cfg_from(test);

        // Labels are correct:
        assert_eq!(cfg.team_labels().map(|tl| tl.0.clone()).collect::<Vec<_>>(),
                   vec!["avengers", "justice-league"]);

        // Teams are correct:
        let map: BTreeMap<_, _> =
            cfg.teams().map(|(k, v)| (k.0.clone(), v.clone())).collect();

        let avengers = map.get("avengers").unwrap();
        assert_eq!(avengers.name, "The Avengers");
        assert_eq!(avengers.ping, "marvel/avengers");
        assert_eq!(avengers.member_logins().collect::<Vec<_>>(),
            vec!["hulk", "thor", "thevision", "blackwidow",
                 "spiderman", "captainamerica"]);

        let jsa = map.get("justice-league").unwrap();
        assert_eq!(jsa.name, "Justice League of America");
        assert_eq!(jsa.ping, "dc-comics/justice-league");
        assert_eq!(jsa.member_logins().collect::<Vec<_>>(),
            vec!["superman", "wonderwoman", "aquaman", "batman", "theflash"]);

        // FFCP behavior correct:
        assert!(cfg.ffcp_auto_close("rust-lang/alpha"));
        assert!(cfg.ffcp_auto_postpone("rust-lang/alpha"));
        assert!(!cfg.ffcp_auto_close("foobar/beta"));
        assert!(!cfg.ffcp_auto_postpone("foobar/beta"));
        assert!(!cfg.ffcp_auto_close("bazquux/gamma"));
        assert!(!cfg.ffcp_auto_postpone("bazquux/gamma"));
        assert!(!cfg.ffcp_auto_close("wibble/epsilon"));
        assert!(!cfg.ffcp_auto_postpone("wibble/epsilon"));
        assert!(!cfg.ffcp_auto_close("random"));
        assert!(!cfg.ffcp_auto_postpone("random"));
    }

    #[test]
    fn cfg_file_wellformed() {
        // Just parse it and ensure that we get no panics for now!
        // This is a crap test; but, better than nothing.
        let _ = read_rfcbot_cfg();
    }

    #[test]
    fn team_members_exist() {
        for (label, _) in SETUP.teams.iter() {
            println!("found team {:?}", label);
        }
    }
}
<commit_msg>improve setup_parser_correct.<commit_after>// TODO maybe pull from https://github.com/rust-lang/rust-www/blob/master/_data/team.yml instead

use std::collections::BTreeMap;

use diesel::prelude::*;
use toml;
use serde::de;

use super::DB_POOL;
use domain::github::GitHubUser;
use error::*;

//==============================================================================
// Public API
//==============================================================================

lazy_static! {
    pub static ref SETUP: RfcbotConfig = read_rfcbot_cfg_validated();
}

#[derive(Debug, Deserialize)]
pub struct RfcbotConfig {
    fcp_behaviors: BTreeMap<String, FcpBehavior>,
    teams: BTreeMap<TeamLabel, Team>,
}

impl RfcbotConfig {
    /// Retrive an iterator over all the team labels.
    pub fn team_labels(&self) -> impl Iterator<Item = &TeamLabel> {
        self.teams.keys()
    }

    /// Retrive an iterator over all the (team label, team) pairs.
    pub fn teams(&self) -> impl Iterator<Item = (&TeamLabel, &Team)> {
        self.teams.iter()
    }

    /// Are we allowed to auto-close issues after F-FCP in this repo?
    pub fn ffcp_auto_close(&self, repo: &str) -> bool {
        self.fcp_behaviors.get(repo).map(|fcp| fcp.close).unwrap_or_default()
    }

    /// Are we allowed to auto-postpone issues after F-FCP in this repo?
    pub fn ffcp_auto_postpone(&self, repo: &str) -> bool {
        self.fcp_behaviors.get(repo).map(|fcp| fcp.postpone).unwrap_or_default()
    }
}

#[derive(Debug, Deserialize)]
pub struct FcpBehavior {
    #[serde(default)]
    close: bool,
    #[serde(default)]
    postpone: bool,
}

#[derive(Debug, Deserialize)]
pub struct Team {
    // FIXME: The two following first fields don't seem to be used anywhere...
    // If this is intended, document why.
    name: String,
    ping: String,
    members: Vec<String>,
}

impl Team {
    pub fn member_logins(&self) -> impl Iterator<Item = &str> {
        self.members.iter().map(|s| s.as_str())
    }
}

#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct TeamLabel(pub String);

impl<'de> de::Deserialize<'de> for TeamLabel {
    fn deserialize<D: de::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
        String::deserialize(de).map(TeamLabel)
    }

    fn deserialize_in_place<D: de::Deserializer<'de>>(de: D, place: &mut Self)
        -> Result<(), D::Error>
    {
        String::deserialize_in_place(de, &mut place.0)
    }
}

//==============================================================================
// Implementation details
//==============================================================================

/// Read the validated `rfcbot.toml` configuration file.
fn read_rfcbot_cfg_validated() -> RfcbotConfig {
    let cfg = read_rfcbot_cfg();

    cfg.teams.values().for_each(|team|
        team.validate()
            .expect("unable to verify team member from database.
if you're running this for tests, make sure you've pulled github users from prod")
    );

    cfg
}

/// Read the unprocessed `rfcbot.toml` configuration file.
fn read_rfcbot_cfg() -> RfcbotConfig {
    read_rfcbot_cfg_from(
        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/rfcbot.toml")))
}

fn read_rfcbot_cfg_from(input: &str) -> RfcbotConfig {
    toml::from_str(input).expect("couldn't parse rfcbot.toml!")
}

impl Team {
    fn validate(&self) -> DashResult<()> {
        use domain::schema::githubuser::dsl::*;
        let conn = &*(DB_POOL.get()?);

        // bail if they don't exist, but we don't want to actually keep the id in ram
        for member_login in self.member_logins() {
            match githubuser
                    .filter(login.eq(member_login))
                    .first::<GitHubUser>(conn)
            {
                Ok(_) => (),
                Err(why) => {
                    error!("unable to find {} in database: {:?}", member_login, why);
                    return Err(why.into());
                }
            }
        }

        Ok(())
    }
}

//==============================================================================
// Tests
//==============================================================================

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

    #[test]
    fn setup_parser_correct() {
let test = r#"
[fcp_behaviors]

[fcp_behaviors."rust-lang/alpha"]
close = true
postpone = true

[fcp_behaviors."foobar/beta"]
close = false

[fcp_behaviors."bazquux/gamma"]
postpone = false

[fcp_behaviors."wibble/epsilon"]

[teams]

[teams.avengers]
name = "The Avengers"
ping = "marvel/avengers"
members = [
  "hulk",
  "thor",
  "thevision",
  "blackwidow",
  "spiderman",
  "captainamerica",
]

[teams.justice-league]
name = "Justice League of America"
ping = "dc-comics/justice-league"
members = [
  "superman",
  "wonderwoman",
  "aquaman",
  "batman",
  "theflash"
]
"#;
        let cfg = read_rfcbot_cfg_from(test);

        // Labels are correct:
        assert_eq!(cfg.team_labels().map(|tl| tl.0.clone()).collect::<Vec<_>>(),
                   vec!["avengers", "justice-league"]);

        // Teams are correct:
        let map: BTreeMap<_, _> =
            cfg.teams().map(|(k, v)| (k.0.clone(), v.clone())).collect();

        let avengers = map.get("avengers").unwrap();
        assert_eq!(avengers.name, "The Avengers");
        assert_eq!(avengers.ping, "marvel/avengers");
        assert_eq!(avengers.member_logins().collect::<Vec<_>>(),
            vec!["hulk", "thor", "thevision", "blackwidow",
                 "spiderman", "captainamerica"]);

        let jsa = map.get("justice-league").unwrap();
        assert_eq!(jsa.name, "Justice League of America");
        assert_eq!(jsa.ping, "dc-comics/justice-league");
        assert_eq!(jsa.member_logins().collect::<Vec<_>>(),
            vec!["superman", "wonderwoman", "aquaman", "batman", "theflash"]);

        // Random non-existent team does not exist:
        assert!(map.get("random").is_none());

        // FFCP behavior correct:
        assert!(cfg.ffcp_auto_close("rust-lang/alpha"));
        assert!(cfg.ffcp_auto_postpone("rust-lang/alpha"));
        assert!(!cfg.ffcp_auto_close("foobar/beta"));
        assert!(!cfg.ffcp_auto_postpone("foobar/beta"));
        assert!(!cfg.ffcp_auto_close("bazquux/gamma"));
        assert!(!cfg.ffcp_auto_postpone("bazquux/gamma"));
        assert!(!cfg.ffcp_auto_close("wibble/epsilon"));
        assert!(!cfg.ffcp_auto_postpone("wibble/epsilon"));
        assert!(!cfg.ffcp_auto_close("random"));
        assert!(!cfg.ffcp_auto_postpone("random"));
    }

    #[test]
    fn cfg_file_wellformed() {
        // Just parse it and ensure that we get no panics for now!
        // This is a crap test; but, better than nothing.
        let _ = read_rfcbot_cfg();
    }

    #[test]
    fn team_members_exist() {
        for (label, _) in SETUP.teams.iter() {
            println!("found team {:?}", label);
        }
    }
}
<|endoftext|>


<commit_before>use {AtomicDateTime, Run, Time, TimerPhase, TimingMethod, TimeStamp, TimeSpan, Segment};
use TimerPhase::*;
use run::PERSONAL_BEST_COMPARISON_NAME;
use parking_lot::RwLock;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct Timer {
    run: Run,
    phase: TimerPhase,
    current_split_index: isize,
    current_timing_method: TimingMethod,
    current_comparison: String,
    attempt_started: Option<AtomicDateTime>,
    attempt_ended: Option<AtomicDateTime>,
    adjusted_start_time: TimeStamp,
    start_time: TimeStamp,
    time_paused_at: TimeSpan,
    is_game_time_paused: bool,
    game_time_pause_time: Option<TimeSpan>,
    loading_times: Option<TimeSpan>,
}

pub type SharedTimer = Arc<RwLock<Timer>>;

impl Timer {
    #[inline]
    pub fn new(mut run: Run) -> Self {
        assert!(run.len() > 0);

        run.regenerate_comparisons();
        let now = TimeStamp::now();

        Timer {
            run: run,
            phase: TimerPhase::NotRunning,
            current_split_index: -1,
            current_timing_method: TimingMethod::RealTime,
            current_comparison: PERSONAL_BEST_COMPARISON_NAME.into(),
            attempt_started: None,
            attempt_ended: None,
            adjusted_start_time: now,
            start_time: now,
            time_paused_at: TimeSpan::zero(),
            is_game_time_paused: false,
            game_time_pause_time: None,
            loading_times: None,
        }
    }

    pub fn into_shared(self) -> SharedTimer {
        Arc::new(RwLock::new(self))
    }

    #[inline]
    pub fn run(&self) -> &Run {
        &self.run
    }

    #[inline]
    pub fn current_phase(&self) -> TimerPhase {
        self.phase
    }

    pub fn current_time(&self) -> Time {
        let real_time = match self.phase {
            NotRunning => Some(self.run.offset()),
            Running => Some(TimeStamp::now() - self.adjusted_start_time),
            Paused => Some(self.time_paused_at),
            Ended => {
                self.run
                    .segments()
                    .last()
                    .unwrap()
                    .split_time()
                    .real_time
            }
        };

        let game_time = match self.phase {
            NotRunning => Some(self.run.offset()),
            Ended => {
                self.run
                    .segments()
                    .last()
                    .unwrap()
                    .split_time()
                    .game_time
            }
            _ => {
                if self.is_game_time_paused() {
                    self.game_time_pause_time
                } else {
                    TimeSpan::option_op(real_time,
                                        if self.is_game_time_initialized() {
                                            Some(self.loading_times())
                                        } else {
                                            None
                                        },
                                        |a, b| a - b)
                }
            }
        };

        Time::new()
            .with_real_time(real_time)
            .with_game_time(game_time)
    }

    #[inline]
    pub fn current_timing_method(&self) -> TimingMethod {
        self.current_timing_method
    }

    #[inline]
    pub fn set_current_timing_method(&mut self, method: TimingMethod) {
        self.current_timing_method = method;
    }

    #[inline]
    pub fn current_comparison(&self) -> &str {
        &self.current_comparison
    }

    pub fn current_split(&self) -> Option<&Segment> {
        if self.current_split_index >= 0 {
            self.run
                .segments()
                .get(self.current_split_index as usize)
        } else {
            None
        }
    }

    fn current_split_mut(&mut self) -> Option<&mut Segment> {
        if self.current_split_index >= 0 {
            self.run
                .segments_mut()
                .get_mut(self.current_split_index as usize)
        } else {
            None
        }
    }

    #[inline]
    pub fn current_split_index(&self) -> isize {
        self.current_split_index
    }

    pub fn split(&mut self) {
        if self.phase == NotRunning {
            self.phase = Running;
            self.current_split_index = 0;
            self.attempt_started = Some(AtomicDateTime::now());
            self.adjusted_start_time = TimeStamp::now() - self.run.offset();
            self.start_time = self.adjusted_start_time;
            self.time_paused_at = self.run.offset();
            self.uninitialize_game_time();
            self.run.start_next_run();

            // TODO OnStart
        } else {
            let current_time = self.current_time();
            if self.phase == TimerPhase::Running &&
               current_time
                   .real_time
                   .map_or(false, |t| t >= TimeSpan::zero()) {
                self.current_split_mut()
                    .unwrap()
                    .set_split_time(current_time);
                self.current_split_index += 1;
                if self.run.len() as isize == self.current_split_index {
                    self.phase = TimerPhase::Ended;
                    self.attempt_ended = Some(AtomicDateTime::now());
                }
                self.run.mark_as_changed();

                // TODO OnSplit
            }
        }
    }

    pub fn skip_split(&mut self) {
        if (self.phase == TimerPhase::Running || self.phase == TimerPhase::Paused) &&
           self.current_split_index < self.run.len() as isize - 1 {
            self.current_split_mut().unwrap().clear_split_time();
            self.current_split_index += 1;
            self.run.mark_as_changed();

            // TODO OnSkipSplit
        }
    }

    pub fn undo_split(&mut self) {
        if self.phase != TimerPhase::NotRunning && self.current_split_index > 0 {
            if self.phase == TimerPhase::Ended {
                self.phase = TimerPhase::Running;
            }
            self.current_split_index -= 1;
            self.current_split_mut().unwrap().clear_split_time();
            self.run.mark_as_changed();

            // TODO OnUndoSplit
        }
    }

    pub fn reset(&mut self, update_splits: bool) {
        if self.phase != TimerPhase::NotRunning {
            if self.phase != TimerPhase::Ended {
                self.attempt_ended = Some(AtomicDateTime::now());
            }
            self.unpause_game_time();
            self.set_loading_times(TimeSpan::zero());

            if update_splits {
                self.update_attempt_history();
                self.update_best_segments();
                self.update_pb_splits();
                self.update_segment_history();
            }

            self.reset_splits();
            self.run.fix_splits();
            self.run.regenerate_comparisons();
        }
    }

    fn reset_splits(&mut self) {
        self.phase = TimerPhase::NotRunning;
        self.current_split_index = -1;

        // Reset Splits
        for segment in self.run.segments_mut() {
            segment.clear_split_time();
        }

        // TODO OnReset
    }

    pub fn pause(&mut self) {
        match self.phase {
            TimerPhase::Running => {
                self.time_paused_at = self.current_time().real_time.unwrap();
                self.phase = TimerPhase::Paused;

                // TODO OnPause
            }
            TimerPhase::Paused => {
                self.adjusted_start_time = TimeStamp::now() - self.time_paused_at;
                self.phase = TimerPhase::Running;

                // TODO OnResume
            }
            TimerPhase::NotRunning => self.split(), // Fuck abahbob
            _ => {}
        }
    }

    pub fn undo_all_pauses(&mut self) {
        match self.current_phase() {
            TimerPhase::Paused => self.pause(),
            TimerPhase::Ended => {
                let pause_time = self.get_pause_time();

                let split_time = self.run
                    .segments_mut()
                    .iter_mut()
                    .last()
                    .unwrap()
                    .split_time_mut();

                *split_time += Time::new()
                    .with_real_time(pause_time)
                    .with_game_time(pause_time);
            }
            _ => {}
        }

        self.adjusted_start_time = self.start_time;

        // TODO OnUndoAllPauses
    }

    pub fn switch_to_next_comparison(&mut self) {
        let mut comparisons = self.run.comparisons();
        let len = comparisons.len();
        let index = comparisons
            .position(|c| c == self.current_comparison)
            .unwrap();
        let index = (index + 1) % len;
        self.current_comparison = self.run.comparisons().nth(index).unwrap().to_owned();

        // TODO OnNextComparison
    }

    pub fn switch_to_previous_comparison(&mut self) {
        let mut comparisons = self.run.comparisons();
        let len = comparisons.len();
        let index = comparisons
            .position(|c| c == self.current_comparison)
            .unwrap();
        let index = (index + len - 1) % len;
        self.current_comparison = self.run.comparisons().nth(index).unwrap().to_owned();

        // TODO OnPreviousComparison
    }

    pub fn get_pause_time(&self) -> Option<TimeSpan> {
        if self.phase != TimerPhase::NotRunning && self.start_time != self.adjusted_start_time {
            Some(self.adjusted_start_time - self.start_time)
        } else {
            None
        }
    }

    #[inline]
    pub fn is_game_time_initialized(&self) -> bool {
        self.loading_times.is_some()
    }

    #[inline]
    pub fn initialize_game_time(&mut self) {
        self.loading_times = Some(self.loading_times());
    }

    #[inline]
    pub fn uninitialize_game_time(&mut self) {
        self.loading_times = None;
    }

    #[inline]
    pub fn is_game_time_paused(&self) -> bool {
        self.is_game_time_paused
    }

    pub fn pause_game_time(&mut self) {
        if !self.is_game_time_paused() {
            let current_time = self.current_time();
            self.game_time_pause_time = current_time.game_time.or(current_time.real_time);
            self.is_game_time_paused = true;
        }
    }

    pub fn unpause_game_time(&mut self) {
        if self.is_game_time_paused() {
            let current_time = self.current_time();
            self.set_loading_times(TimeSpan::option_op(current_time.real_time,
                                                       current_time.game_time,
                                                       |r, g| r - g)
                                           .unwrap_or_default());
            self.is_game_time_paused = false;
        }
    }

    #[inline]
    pub fn set_game_time(&mut self, game_time: TimeSpan) {
        if self.is_game_time_paused() {
            self.game_time_pause_time = Some(game_time);
        }
        let loading_times = self.current_time().real_time.unwrap() - game_time;
        self.loading_times = Some(loading_times);
    }

    #[inline]
    pub fn loading_times(&self) -> TimeSpan {
        self.loading_times.unwrap_or_default()
    }

    #[inline]
    pub fn set_loading_times(&mut self, time: TimeSpan) {
        self.loading_times = Some(time);
        if self.is_game_time_paused() {
            self.game_time_pause_time = Some(self.current_time().real_time.unwrap() - time);
        }
    }

    fn update_attempt_history(&mut self) {
        let (time, pause_time) = if self.phase == TimerPhase::Ended {
            (self.current_time(), self.get_pause_time())
        } else {
            Default::default()
        };
        self.run
            .add_attempt(time, self.attempt_started, self.attempt_ended, pause_time);
    }

    fn update_best_segments(&mut self) {
        let mut previous_split_time_rta = Some(TimeSpan::zero());
        let mut previous_split_time_game_time = Some(TimeSpan::zero());

        for split in self.run.segments_mut() {
            let mut new_best_segment = split.best_segment_time();
            if let Some(split_time) = split.split_time().real_time {
                let current_segment = previous_split_time_rta.map(|previous| split_time - previous);
                previous_split_time_rta = Some(split_time);
                if split
                       .best_segment_time()
                       .real_time
                       .map_or(true, |b| current_segment.map_or(false, |c| c < b)) {
                    new_best_segment.real_time = current_segment;
                }
            }
            if let Some(split_time) = split.split_time().game_time {
                let current_segment =
                    previous_split_time_game_time.map(|previous| split_time - previous);
                previous_split_time_game_time = Some(split_time);
                if split
                       .best_segment_time()
                       .game_time
                       .map_or(true, |b| current_segment.map_or(false, |c| c < b)) {
                    new_best_segment.game_time = current_segment;
                }
            }
            split.set_best_segment_time(new_best_segment);
        }
    }

    fn update_pb_splits(&mut self) {
        let method = self.current_timing_method;
        let (split_time, pb_split_time) = {
            let last_segment = self.run.segments().last().unwrap();
            (last_segment.split_time()[method], last_segment.personal_best_split_time()[method])
        };
        if split_time.map_or(false, |s| pb_split_time.map_or(true, |pb| s < pb)) {
            self.set_run_as_pb();
        }
    }

    fn update_segment_history(&mut self) {
        self.run.update_segment_history(self.current_split_index);
    }

    fn set_run_as_pb(&mut self) {
        self.run.import_segment_history();
        self.run.fix_splits();
        for segment in self.run.segments_mut() {
            let split_time = segment.split_time();
            segment.set_personal_best_split_time(split_time);
        }
    }
}
<commit_msg>Fix Bug in Undo All Pauses<commit_after>use {AtomicDateTime, Run, Time, TimerPhase, TimingMethod, TimeStamp, TimeSpan, Segment};
use TimerPhase::*;
use run::PERSONAL_BEST_COMPARISON_NAME;
use parking_lot::RwLock;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct Timer {
    run: Run,
    phase: TimerPhase,
    current_split_index: isize,
    current_timing_method: TimingMethod,
    current_comparison: String,
    attempt_started: Option<AtomicDateTime>,
    attempt_ended: Option<AtomicDateTime>,
    adjusted_start_time: TimeStamp,
    start_time: TimeStamp,
    time_paused_at: TimeSpan,
    is_game_time_paused: bool,
    game_time_pause_time: Option<TimeSpan>,
    loading_times: Option<TimeSpan>,
}

pub type SharedTimer = Arc<RwLock<Timer>>;

impl Timer {
    #[inline]
    pub fn new(mut run: Run) -> Self {
        assert!(run.len() > 0);

        run.regenerate_comparisons();
        let now = TimeStamp::now();

        Timer {
            run: run,
            phase: TimerPhase::NotRunning,
            current_split_index: -1,
            current_timing_method: TimingMethod::RealTime,
            current_comparison: PERSONAL_BEST_COMPARISON_NAME.into(),
            attempt_started: None,
            attempt_ended: None,
            adjusted_start_time: now,
            start_time: now,
            time_paused_at: TimeSpan::zero(),
            is_game_time_paused: false,
            game_time_pause_time: None,
            loading_times: None,
        }
    }

    pub fn into_shared(self) -> SharedTimer {
        Arc::new(RwLock::new(self))
    }

    #[inline]
    pub fn run(&self) -> &Run {
        &self.run
    }

    #[inline]
    pub fn current_phase(&self) -> TimerPhase {
        self.phase
    }

    pub fn current_time(&self) -> Time {
        let real_time = match self.phase {
            NotRunning => Some(self.run.offset()),
            Running => Some(TimeStamp::now() - self.adjusted_start_time),
            Paused => Some(self.time_paused_at),
            Ended => {
                self.run
                    .segments()
                    .last()
                    .unwrap()
                    .split_time()
                    .real_time
            }
        };

        let game_time = match self.phase {
            NotRunning => Some(self.run.offset()),
            Ended => {
                self.run
                    .segments()
                    .last()
                    .unwrap()
                    .split_time()
                    .game_time
            }
            _ => {
                if self.is_game_time_paused() {
                    self.game_time_pause_time
                } else {
                    TimeSpan::option_op(real_time,
                                        if self.is_game_time_initialized() {
                                            Some(self.loading_times())
                                        } else {
                                            None
                                        },
                                        |a, b| a - b)
                }
            }
        };

        Time::new()
            .with_real_time(real_time)
            .with_game_time(game_time)
    }

    #[inline]
    pub fn current_timing_method(&self) -> TimingMethod {
        self.current_timing_method
    }

    #[inline]
    pub fn set_current_timing_method(&mut self, method: TimingMethod) {
        self.current_timing_method = method;
    }

    #[inline]
    pub fn current_comparison(&self) -> &str {
        &self.current_comparison
    }

    pub fn current_split(&self) -> Option<&Segment> {
        if self.current_split_index >= 0 {
            self.run
                .segments()
                .get(self.current_split_index as usize)
        } else {
            None
        }
    }

    fn current_split_mut(&mut self) -> Option<&mut Segment> {
        if self.current_split_index >= 0 {
            self.run
                .segments_mut()
                .get_mut(self.current_split_index as usize)
        } else {
            None
        }
    }

    #[inline]
    pub fn current_split_index(&self) -> isize {
        self.current_split_index
    }

    pub fn split(&mut self) {
        if self.phase == NotRunning {
            self.phase = Running;
            self.current_split_index = 0;
            self.attempt_started = Some(AtomicDateTime::now());
            self.adjusted_start_time = TimeStamp::now() - self.run.offset();
            self.start_time = self.adjusted_start_time;
            self.time_paused_at = self.run.offset();
            self.uninitialize_game_time();
            self.run.start_next_run();

            // TODO OnStart
        } else {
            let current_time = self.current_time();
            if self.phase == TimerPhase::Running &&
               current_time
                   .real_time
                   .map_or(false, |t| t >= TimeSpan::zero()) {
                self.current_split_mut()
                    .unwrap()
                    .set_split_time(current_time);
                self.current_split_index += 1;
                if self.run.len() as isize == self.current_split_index {
                    self.phase = TimerPhase::Ended;
                    self.attempt_ended = Some(AtomicDateTime::now());
                }
                self.run.mark_as_changed();

                // TODO OnSplit
            }
        }
    }

    pub fn skip_split(&mut self) {
        if (self.phase == TimerPhase::Running || self.phase == TimerPhase::Paused) &&
           self.current_split_index < self.run.len() as isize - 1 {
            self.current_split_mut().unwrap().clear_split_time();
            self.current_split_index += 1;
            self.run.mark_as_changed();

            // TODO OnSkipSplit
        }
    }

    pub fn undo_split(&mut self) {
        if self.phase != TimerPhase::NotRunning && self.current_split_index > 0 {
            if self.phase == TimerPhase::Ended {
                self.phase = TimerPhase::Running;
            }
            self.current_split_index -= 1;
            self.current_split_mut().unwrap().clear_split_time();
            self.run.mark_as_changed();

            // TODO OnUndoSplit
        }
    }

    pub fn reset(&mut self, update_splits: bool) {
        if self.phase != TimerPhase::NotRunning {
            if self.phase != TimerPhase::Ended {
                self.attempt_ended = Some(AtomicDateTime::now());
            }
            self.unpause_game_time();
            self.set_loading_times(TimeSpan::zero());

            if update_splits {
                self.update_attempt_history();
                self.update_best_segments();
                self.update_pb_splits();
                self.update_segment_history();
            }

            self.reset_splits();
            self.run.fix_splits();
            self.run.regenerate_comparisons();
        }
    }

    fn reset_splits(&mut self) {
        self.phase = TimerPhase::NotRunning;
        self.current_split_index = -1;

        // Reset Splits
        for segment in self.run.segments_mut() {
            segment.clear_split_time();
        }

        // TODO OnReset
    }

    pub fn pause(&mut self) {
        match self.phase {
            TimerPhase::Running => {
                self.time_paused_at = self.current_time().real_time.unwrap();
                self.phase = TimerPhase::Paused;

                // TODO OnPause
            }
            TimerPhase::Paused => {
                self.adjusted_start_time = TimeStamp::now() - self.time_paused_at;
                self.phase = TimerPhase::Running;

                // TODO OnResume
            }
            TimerPhase::NotRunning => self.split(), // Fuck abahbob
            _ => {}
        }
    }

    pub fn undo_all_pauses(&mut self) {
        match self.current_phase() {
            TimerPhase::Paused => self.pause(),
            TimerPhase::Ended => {
                let pause_time = Some(self.get_pause_time().unwrap_or_default());

                let split_time = self.run
                    .segments_mut()
                    .iter_mut()
                    .last()
                    .unwrap()
                    .split_time_mut();

                *split_time += Time::new()
                    .with_real_time(pause_time)
                    .with_game_time(pause_time);
            }
            _ => {}
        }

        self.adjusted_start_time = self.start_time;

        // TODO OnUndoAllPauses
    }

    pub fn switch_to_next_comparison(&mut self) {
        let mut comparisons = self.run.comparisons();
        let len = comparisons.len();
        let index = comparisons
            .position(|c| c == self.current_comparison)
            .unwrap();
        let index = (index + 1) % len;
        self.current_comparison = self.run.comparisons().nth(index).unwrap().to_owned();

        // TODO OnNextComparison
    }

    pub fn switch_to_previous_comparison(&mut self) {
        let mut comparisons = self.run.comparisons();
        let len = comparisons.len();
        let index = comparisons
            .position(|c| c == self.current_comparison)
            .unwrap();
        let index = (index + len - 1) % len;
        self.current_comparison = self.run.comparisons().nth(index).unwrap().to_owned();

        // TODO OnPreviousComparison
    }

    pub fn get_pause_time(&self) -> Option<TimeSpan> {
        if self.phase != TimerPhase::NotRunning && self.start_time != self.adjusted_start_time {
            Some(self.adjusted_start_time - self.start_time)
        } else {
            None
        }
    }

    #[inline]
    pub fn is_game_time_initialized(&self) -> bool {
        self.loading_times.is_some()
    }

    #[inline]
    pub fn initialize_game_time(&mut self) {
        self.loading_times = Some(self.loading_times());
    }

    #[inline]
    pub fn uninitialize_game_time(&mut self) {
        self.loading_times = None;
    }

    #[inline]
    pub fn is_game_time_paused(&self) -> bool {
        self.is_game_time_paused
    }

    pub fn pause_game_time(&mut self) {
        if !self.is_game_time_paused() {
            let current_time = self.current_time();
            self.game_time_pause_time = current_time.game_time.or(current_time.real_time);
            self.is_game_time_paused = true;
        }
    }

    pub fn unpause_game_time(&mut self) {
        if self.is_game_time_paused() {
            let current_time = self.current_time();
            self.set_loading_times(TimeSpan::option_op(current_time.real_time,
                                                       current_time.game_time,
                                                       |r, g| r - g)
                                           .unwrap_or_default());
            self.is_game_time_paused = false;
        }
    }

    #[inline]
    pub fn set_game_time(&mut self, game_time: TimeSpan) {
        if self.is_game_time_paused() {
            self.game_time_pause_time = Some(game_time);
        }
        let loading_times = self.current_time().real_time.unwrap() - game_time;
        self.loading_times = Some(loading_times);
    }

    #[inline]
    pub fn loading_times(&self) -> TimeSpan {
        self.loading_times.unwrap_or_default()
    }

    #[inline]
    pub fn set_loading_times(&mut self, time: TimeSpan) {
        self.loading_times = Some(time);
        if self.is_game_time_paused() {
            self.game_time_pause_time = Some(self.current_time().real_time.unwrap() - time);
        }
    }

    fn update_attempt_history(&mut self) {
        let (time, pause_time) = if self.phase == TimerPhase::Ended {
            (self.current_time(), self.get_pause_time())
        } else {
            Default::default()
        };
        self.run
            .add_attempt(time, self.attempt_started, self.attempt_ended, pause_time);
    }

    fn update_best_segments(&mut self) {
        let mut previous_split_time_rta = Some(TimeSpan::zero());
        let mut previous_split_time_game_time = Some(TimeSpan::zero());

        for split in self.run.segments_mut() {
            let mut new_best_segment = split.best_segment_time();
            if let Some(split_time) = split.split_time().real_time {
                let current_segment = previous_split_time_rta.map(|previous| split_time - previous);
                previous_split_time_rta = Some(split_time);
                if split
                       .best_segment_time()
                       .real_time
                       .map_or(true, |b| current_segment.map_or(false, |c| c < b)) {
                    new_best_segment.real_time = current_segment;
                }
            }
            if let Some(split_time) = split.split_time().game_time {
                let current_segment =
                    previous_split_time_game_time.map(|previous| split_time - previous);
                previous_split_time_game_time = Some(split_time);
                if split
                       .best_segment_time()
                       .game_time
                       .map_or(true, |b| current_segment.map_or(false, |c| c < b)) {
                    new_best_segment.game_time = current_segment;
                }
            }
            split.set_best_segment_time(new_best_segment);
        }
    }

    fn update_pb_splits(&mut self) {
        let method = self.current_timing_method;
        let (split_time, pb_split_time) = {
            let last_segment = self.run.segments().last().unwrap();
            (last_segment.split_time()[method], last_segment.personal_best_split_time()[method])
        };
        if split_time.map_or(false, |s| pb_split_time.map_or(true, |pb| s < pb)) {
            self.set_run_as_pb();
        }
    }

    fn update_segment_history(&mut self) {
        self.run.update_segment_history(self.current_split_index);
    }

    fn set_run_as_pb(&mut self) {
        self.run.import_segment_history();
        self.run.fix_splits();
        for segment in self.run.segments_mut() {
            let split_time = segment.split_time();
            segment.set_personal_best_split_time(split_time);
        }
    }
}
<|endoftext|>


<commit_before>//! Data types used to describe the data that makes up the Chip8.

/// One byte in RAM.
pub type MemoryCell = u8;
/// A value in an 8-bit register.
pub type Register8 = u8;
/// A value in a 16-bit register.
pub type Register16 = u16;
/// An 8-bit timer.
pub type Timer = u8;
/// An Address.
pub type Address = u16;
/// A 16-bit chip8 codeword.
pub type Codeword = u16;
/// A single pixel.
pub type Pixel = u8;
/// The state of the keyboard.
pub type Keyboard = [bool; 16];
/// The state of the buzzer.
pub type Buzzer = bool;
/// The audio buffer.
pub type Audio = [u8; 16];
/// Vram
pub type Vram = Vec<Pixel>;

#[derive(Debug)]
pub enum Chip8Error {
    OutOfBoundsAt(usize),
    OutOfBounds,
    PopEmptyStack,
    InvalidOperand,
}
pub type Chip8Result<T> = Result<T, Chip8Error>;
<commit_msg>Improve documentation of type definitions.<commit_after>//! Defines the data types used to describe the Chip8 and associated peripherals.

/// One byte in RAM.
pub type MemoryCell = u8;
/// An 8-bit register.
pub type Register8 = u8;
/// A 16-bit register.
pub type Register16 = u16;
/// An 8-bit timer.
pub type Timer = u8;
/// An address pointing to a location in CHIP-8 memory.
pub type Address = u16;
/// A 16-bit CHIP-8 codeword.
pub type Codeword = u16;
/// A single pixel.
pub type Pixel = u8;
/// The state of the keyboard.
pub type Keyboard = [bool; 16];
/// The state of the buzzer.
pub type Buzzer = bool;
/// The audio buffer used for XOCHIP.
pub type Audio = [u8; 16];
/// Vram
pub type Vram = Vec<Pixel>;

/// A set of errors that could be returned.
#[derive(Debug)]
pub enum Chip8Error {
    OutOfBoundsAt(usize),
    OutOfBounds,
    PopEmptyStack,
    InvalidOperand,
}
/// The result type used throughout the library.
pub type Chip8Result<T> = Result<T, Chip8Error>;
<|endoftext|>


<commit_before>use parser;
use edit::EditMode;
use edit::ViMode;

pub enum Instr {
    MoveCursorLeft,
    MoveCursorRight,
    MoveCursorStart,
    MoveCursorEnd,
    MoveEndOfWordRight,
    MoveEndOfWordWsRight,
    MoveWordRight,
    MoveWordWsRight,
    MoveWordLeft,
    MoveWordWsLeft,
    MoveCharRight(char),
    MoveCharLeft(char),
    MoveBeforeCharRight(char),
    MoveBeforeCharLeft(char),
    DeleteCharLeftOfCursor,
    DeleteCharRightOfCursor,
    DeleteCharRightOfCursorOrEOF,
    DeleteLine,
    DeleteToEnd,
    ChangeLine,
    ChangeToEnd,
    Substitute,
    InsertAtCursor(String),
    ReplaceAtCursor(String),
    HistoryNext,
    HistoryPrev,
    Insert,
    InsertStart,
    Append,
    AppendEnd,
    NormalMode,
    ReplaceMode,
    MoveCharMode(CharMoveType),
    DeleteMode,
    ChangeMode,
    Digit(u32),
    Done,
    DoneOrEof,
    Cancel,
    Clear,
    Noop
}

#[derive(Copy,Clone,PartialEq)]
pub enum CharMoveType {
    BeforeRight,
    BeforeLeft,
    Right,
    Left,
}

pub fn interpret_token(token: parser::Token, edit_mode: EditMode, vi_mode: ViMode) -> Instr {
    match edit_mode {
        EditMode::Emacs => emacs_mode(token),
        EditMode::Vi => match vi_mode {
            ViMode::Insert => vi_insert_mode(token),
            ViMode::Normal => vi_normal_mode(token),
            ViMode::Replace => vi_replace_mode(token),
            ViMode::MoveChar(move_type) => vi_move_char_mode(move_type, token),
            ViMode::DeleteMoveChar(move_type) => vi_move_char_mode(move_type, token),
            ViMode::ChangeMoveChar(move_type) => vi_move_char_mode(move_type, token),
            ViMode::Delete => vi_delete_mode(token),
            ViMode::Change => vi_change_mode(token),
        },
    }
}

fn emacs_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Enter        => Instr::Done,
        parser::Token::Backspace    => Instr::DeleteCharLeftOfCursor,
        parser::Token::CtrlH        => Instr::DeleteCharLeftOfCursor,
        parser::Token::EscBracket3T => Instr::DeleteCharRightOfCursor,
        parser::Token::CtrlD        => Instr::DeleteCharRightOfCursorOrEOF,
        parser::Token::EscBracketA  => Instr::HistoryPrev,
        parser::Token::CtrlP        => Instr::HistoryPrev,
        parser::Token::EscBracketB  => Instr::HistoryNext,
        parser::Token::CtrlN        => Instr::HistoryNext,
        parser::Token::EscBracketC  => Instr::MoveCursorRight,
        parser::Token::CtrlF        => Instr::MoveCursorRight,
        parser::Token::EscBracketD  => Instr::MoveCursorLeft,
        parser::Token::CtrlB        => Instr::MoveCursorLeft,
        parser::Token::CtrlA        => Instr::MoveCursorStart,
        parser::Token::EscBracketH  => Instr::MoveCursorStart,
        parser::Token::CtrlE        => Instr::MoveCursorEnd,
        parser::Token::EscBracketF  => Instr::MoveCursorEnd,
        parser::Token::Text(text)   => Instr::InsertAtCursor(text),
        parser::Token::CtrlC        => Instr::Cancel,
        parser::Token::CtrlL        => Instr::Clear,
        _                           => Instr::Noop
    }
}

fn vi_common(token: &parser::Token) -> Instr {
    match *token {
        parser::Token::Enter        => Instr::Done,
        parser::Token::CtrlD        => Instr::DoneOrEof,
        parser::Token::Esc          => Instr::NormalMode,
        parser::Token::Backspace    => Instr::DeleteCharLeftOfCursor,
        parser::Token::EscBracket3T => Instr::DeleteCharRightOfCursor,
        // movement keys
        parser::Token::EscBracketA  => Instr::HistoryPrev,
        parser::Token::EscBracketB  => Instr::HistoryNext,
        parser::Token::EscBracketC  => Instr::MoveCursorRight,
        parser::Token::EscBracketD  => Instr::MoveCursorLeft,
        // home
        parser::Token::EscBracketH  => Instr::MoveCursorStart,
        // end
        parser::Token::EscBracketF  => Instr::MoveCursorEnd,
        parser::Token::CtrlC        => Instr::Cancel,
        parser::Token::CtrlL        => Instr::Clear,
        _                           => Instr::Noop,
    }
}

fn vi_insert_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(text)   => Instr::InsertAtCursor(text),
        _                           => vi_common(&token),
    }
}
fn vi_normal_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(text)   => match text.as_ref() {
            "h"                     => Instr::MoveCursorLeft,
            "j"                     => Instr::HistoryNext,
            "k"                     => Instr::HistoryPrev,
            "l"                     => Instr::MoveCursorRight,
            "0"                     => Instr::Digit(0),
            "$"                     => Instr::MoveCursorEnd,

            "x"                     => Instr::DeleteCharRightOfCursor,
            "s"                     => Instr::Substitute,
            "r"                     => Instr::ReplaceMode,
            "c"                     => Instr::ChangeMode,
            "C"                     => Instr::ChangeToEnd,
            "d"                     => Instr::DeleteMode,
            "D"                     => Instr::DeleteToEnd,

            "e"                     => Instr::MoveEndOfWordRight,
            "E"                     => Instr::MoveEndOfWordWsRight,
            "w"                     => Instr::MoveWordRight,
            "W"                     => Instr::MoveWordWsRight,
            "b"                     => Instr::MoveWordLeft,
            "B"                     => Instr::MoveWordWsLeft,
            "t"                     => Instr::MoveCharMode(CharMoveType::BeforeRight),
            "T"                     => Instr::MoveCharMode(CharMoveType::BeforeLeft),
            "f"                     => Instr::MoveCharMode(CharMoveType::Right),
            "F"                     => Instr::MoveCharMode(CharMoveType::Left),

            "a"                     => Instr::Append,
            "A"                     => Instr::AppendEnd,
            "i"                     => Instr::Insert,
            "I"                     => Instr::InsertStart,

            "1"                     => Instr::Digit(1),
            "2"                     => Instr::Digit(2),
            "3"                     => Instr::Digit(3),
            "4"                     => Instr::Digit(4),
            "5"                     => Instr::Digit(5),
            "6"                     => Instr::Digit(6),
            "7"                     => Instr::Digit(7),
            "8"                     => Instr::Digit(8),
            "9"                     => Instr::Digit(9),

            _                       => Instr::Noop,
        },
        _                           => vi_common(&token),
    }
}
fn vi_replace_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(text)   => Instr::ReplaceAtCursor(text),
        _                           => Instr::NormalMode,
    }
}
fn vi_move_char_mode(move_type: CharMoveType, token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(ref text) if text.chars().count() == 1 => match move_type {
            CharMoveType::BeforeLeft  => Instr::MoveBeforeCharLeft(text.chars().next().unwrap()),
            CharMoveType::BeforeRight => Instr::MoveBeforeCharRight(text.chars().next().unwrap()),
            CharMoveType::Left        => Instr::MoveCharLeft(text.chars().next().unwrap()),
            CharMoveType::Right       => Instr::MoveCharRight(text.chars().next().unwrap()),
        },
        _                           => Instr::NormalMode,
    }
}
fn vi_change_delete_common(token: &parser::Token) -> Instr {
    match *token {
        parser::Token::Text(ref text) => match text.as_ref() {
            "h"                     => Instr::MoveCursorLeft,
            "l"                     => Instr::MoveCursorRight,
            "0"                     => Instr::Digit(0),
            "$"                     => Instr::MoveCursorEnd,

            "e"                     => Instr::MoveEndOfWordRight,
            "E"                     => Instr::MoveEndOfWordWsRight,
            "w"                     => Instr::MoveWordRight,
            "W"                     => Instr::MoveWordWsRight,
            "b"                     => Instr::MoveWordLeft,
            "B"                     => Instr::MoveWordWsLeft,
            "t"                     => Instr::MoveCharMode(CharMoveType::BeforeRight),
            "T"                     => Instr::MoveCharMode(CharMoveType::BeforeLeft),
            "f"                     => Instr::MoveCharMode(CharMoveType::Right),
            "F"                     => Instr::MoveCharMode(CharMoveType::Left),

            "1"                     => Instr::Digit(1),
            "2"                     => Instr::Digit(2),
            "3"                     => Instr::Digit(3),
            "4"                     => Instr::Digit(4),
            "5"                     => Instr::Digit(5),
            "6"                     => Instr::Digit(6),
            "7"                     => Instr::Digit(7),
            "8"                     => Instr::Digit(8),
            "9"                     => Instr::Digit(9),
            _                       => Instr::NormalMode,
        },
        _                           => Instr::NormalMode,
    }
}
fn vi_change_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(ref text) => match text.as_ref() {
            "c"                     => Instr::ChangeLine,
            _                       => vi_change_delete_common(&token),
        },
        _                           => Instr::NormalMode,
    }
}
fn vi_delete_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(ref text) => match text.as_ref() {
            "d"                     => Instr::DeleteLine,
            _                       => vi_change_delete_common(&token),
        },
        _                           => Instr::NormalMode,
    }
}
<commit_msg>avoid use of unwrap in vi_move_char_mode()<commit_after>use parser;
use edit::EditMode;
use edit::ViMode;

pub enum Instr {
    MoveCursorLeft,
    MoveCursorRight,
    MoveCursorStart,
    MoveCursorEnd,
    MoveEndOfWordRight,
    MoveEndOfWordWsRight,
    MoveWordRight,
    MoveWordWsRight,
    MoveWordLeft,
    MoveWordWsLeft,
    MoveCharRight(char),
    MoveCharLeft(char),
    MoveBeforeCharRight(char),
    MoveBeforeCharLeft(char),
    DeleteCharLeftOfCursor,
    DeleteCharRightOfCursor,
    DeleteCharRightOfCursorOrEOF,
    DeleteLine,
    DeleteToEnd,
    ChangeLine,
    ChangeToEnd,
    Substitute,
    InsertAtCursor(String),
    ReplaceAtCursor(String),
    HistoryNext,
    HistoryPrev,
    Insert,
    InsertStart,
    Append,
    AppendEnd,
    NormalMode,
    ReplaceMode,
    MoveCharMode(CharMoveType),
    DeleteMode,
    ChangeMode,
    Digit(u32),
    Done,
    DoneOrEof,
    Cancel,
    Clear,
    Noop
}

#[derive(Copy,Clone,PartialEq)]
pub enum CharMoveType {
    BeforeRight,
    BeforeLeft,
    Right,
    Left,
}

pub fn interpret_token(token: parser::Token, edit_mode: EditMode, vi_mode: ViMode) -> Instr {
    match edit_mode {
        EditMode::Emacs => emacs_mode(token),
        EditMode::Vi => match vi_mode {
            ViMode::Insert => vi_insert_mode(token),
            ViMode::Normal => vi_normal_mode(token),
            ViMode::Replace => vi_replace_mode(token),
            ViMode::MoveChar(move_type) => vi_move_char_mode(move_type, token),
            ViMode::DeleteMoveChar(move_type) => vi_move_char_mode(move_type, token),
            ViMode::ChangeMoveChar(move_type) => vi_move_char_mode(move_type, token),
            ViMode::Delete => vi_delete_mode(token),
            ViMode::Change => vi_change_mode(token),
        },
    }
}

fn emacs_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Enter        => Instr::Done,
        parser::Token::Backspace    => Instr::DeleteCharLeftOfCursor,
        parser::Token::CtrlH        => Instr::DeleteCharLeftOfCursor,
        parser::Token::EscBracket3T => Instr::DeleteCharRightOfCursor,
        parser::Token::CtrlD        => Instr::DeleteCharRightOfCursorOrEOF,
        parser::Token::EscBracketA  => Instr::HistoryPrev,
        parser::Token::CtrlP        => Instr::HistoryPrev,
        parser::Token::EscBracketB  => Instr::HistoryNext,
        parser::Token::CtrlN        => Instr::HistoryNext,
        parser::Token::EscBracketC  => Instr::MoveCursorRight,
        parser::Token::CtrlF        => Instr::MoveCursorRight,
        parser::Token::EscBracketD  => Instr::MoveCursorLeft,
        parser::Token::CtrlB        => Instr::MoveCursorLeft,
        parser::Token::CtrlA        => Instr::MoveCursorStart,
        parser::Token::EscBracketH  => Instr::MoveCursorStart,
        parser::Token::CtrlE        => Instr::MoveCursorEnd,
        parser::Token::EscBracketF  => Instr::MoveCursorEnd,
        parser::Token::Text(text)   => Instr::InsertAtCursor(text),
        parser::Token::CtrlC        => Instr::Cancel,
        parser::Token::CtrlL        => Instr::Clear,
        _                           => Instr::Noop
    }
}

fn vi_common(token: &parser::Token) -> Instr {
    match *token {
        parser::Token::Enter        => Instr::Done,
        parser::Token::CtrlD        => Instr::DoneOrEof,
        parser::Token::Esc          => Instr::NormalMode,
        parser::Token::Backspace    => Instr::DeleteCharLeftOfCursor,
        parser::Token::EscBracket3T => Instr::DeleteCharRightOfCursor,
        // movement keys
        parser::Token::EscBracketA  => Instr::HistoryPrev,
        parser::Token::EscBracketB  => Instr::HistoryNext,
        parser::Token::EscBracketC  => Instr::MoveCursorRight,
        parser::Token::EscBracketD  => Instr::MoveCursorLeft,
        // home
        parser::Token::EscBracketH  => Instr::MoveCursorStart,
        // end
        parser::Token::EscBracketF  => Instr::MoveCursorEnd,
        parser::Token::CtrlC        => Instr::Cancel,
        parser::Token::CtrlL        => Instr::Clear,
        _                           => Instr::Noop,
    }
}

fn vi_insert_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(text)   => Instr::InsertAtCursor(text),
        _                           => vi_common(&token),
    }
}
fn vi_normal_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(text)   => match text.as_ref() {
            "h"                     => Instr::MoveCursorLeft,
            "j"                     => Instr::HistoryNext,
            "k"                     => Instr::HistoryPrev,
            "l"                     => Instr::MoveCursorRight,
            "0"                     => Instr::Digit(0),
            "$"                     => Instr::MoveCursorEnd,

            "x"                     => Instr::DeleteCharRightOfCursor,
            "s"                     => Instr::Substitute,
            "r"                     => Instr::ReplaceMode,
            "c"                     => Instr::ChangeMode,
            "C"                     => Instr::ChangeToEnd,
            "d"                     => Instr::DeleteMode,
            "D"                     => Instr::DeleteToEnd,

            "e"                     => Instr::MoveEndOfWordRight,
            "E"                     => Instr::MoveEndOfWordWsRight,
            "w"                     => Instr::MoveWordRight,
            "W"                     => Instr::MoveWordWsRight,
            "b"                     => Instr::MoveWordLeft,
            "B"                     => Instr::MoveWordWsLeft,
            "t"                     => Instr::MoveCharMode(CharMoveType::BeforeRight),
            "T"                     => Instr::MoveCharMode(CharMoveType::BeforeLeft),
            "f"                     => Instr::MoveCharMode(CharMoveType::Right),
            "F"                     => Instr::MoveCharMode(CharMoveType::Left),

            "a"                     => Instr::Append,
            "A"                     => Instr::AppendEnd,
            "i"                     => Instr::Insert,
            "I"                     => Instr::InsertStart,

            "1"                     => Instr::Digit(1),
            "2"                     => Instr::Digit(2),
            "3"                     => Instr::Digit(3),
            "4"                     => Instr::Digit(4),
            "5"                     => Instr::Digit(5),
            "6"                     => Instr::Digit(6),
            "7"                     => Instr::Digit(7),
            "8"                     => Instr::Digit(8),
            "9"                     => Instr::Digit(9),

            _                       => Instr::Noop,
        },
        _                           => vi_common(&token),
    }
}
fn vi_replace_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(text)   => Instr::ReplaceAtCursor(text),
        _                           => Instr::NormalMode,
    }
}
fn vi_move_char_mode(move_type: CharMoveType, token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(ref text) => match (move_type, text.chars().next()) {
            (CharMoveType::BeforeLeft, Some(c))  => Instr::MoveBeforeCharLeft(c),
            (CharMoveType::BeforeRight, Some(c)) => Instr::MoveBeforeCharRight(c),
            (CharMoveType::Left, Some(c))        => Instr::MoveCharLeft(c),
            (CharMoveType::Right, Some(c))       => Instr::MoveCharRight(c),
            (_, None)                            => Instr::NormalMode, // this is probably unreachable!()
        },
        _                           => Instr::NormalMode,
    }
}
fn vi_change_delete_common(token: &parser::Token) -> Instr {
    match *token {
        parser::Token::Text(ref text) => match text.as_ref() {
            "h"                     => Instr::MoveCursorLeft,
            "l"                     => Instr::MoveCursorRight,
            "0"                     => Instr::Digit(0),
            "$"                     => Instr::MoveCursorEnd,

            "e"                     => Instr::MoveEndOfWordRight,
            "E"                     => Instr::MoveEndOfWordWsRight,
            "w"                     => Instr::MoveWordRight,
            "W"                     => Instr::MoveWordWsRight,
            "b"                     => Instr::MoveWordLeft,
            "B"                     => Instr::MoveWordWsLeft,
            "t"                     => Instr::MoveCharMode(CharMoveType::BeforeRight),
            "T"                     => Instr::MoveCharMode(CharMoveType::BeforeLeft),
            "f"                     => Instr::MoveCharMode(CharMoveType::Right),
            "F"                     => Instr::MoveCharMode(CharMoveType::Left),

            "1"                     => Instr::Digit(1),
            "2"                     => Instr::Digit(2),
            "3"                     => Instr::Digit(3),
            "4"                     => Instr::Digit(4),
            "5"                     => Instr::Digit(5),
            "6"                     => Instr::Digit(6),
            "7"                     => Instr::Digit(7),
            "8"                     => Instr::Digit(8),
            "9"                     => Instr::Digit(9),
            _                       => Instr::NormalMode,
        },
        _                           => Instr::NormalMode,
    }
}
fn vi_change_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(ref text) => match text.as_ref() {
            "c"                     => Instr::ChangeLine,
            _                       => vi_change_delete_common(&token),
        },
        _                           => Instr::NormalMode,
    }
}
fn vi_delete_mode(token: parser::Token) -> Instr {
    match token {
        parser::Token::Text(ref text) => match text.as_ref() {
            "d"                     => Instr::DeleteLine,
            _                       => vi_change_delete_common(&token),
        },
        _                           => Instr::NormalMode,
    }
}
<|endoftext|>