<commit_before>use std::collections::HashMap;

use chrono::NaiveDateTime;
use diesel::prelude::*;

use crate::util::errors::{cargo_err, AppResult};

use crate::models::{Crate, Dependency, User};
use crate::schema::*;

// Queryable has a custom implementation below
#[derive(Clone, Identifiable, Associations, Debug, Queryable, Deserialize, Serialize)]
#[belongs_to(Crate)]
pub struct Version {
    pub id: i32,
    pub crate_id: i32,
    pub num: String,
    pub updated_at: NaiveDateTime,
    pub created_at: NaiveDateTime,
    pub downloads: i32,
    pub features: serde_json::Value,
    pub yanked: bool,
    pub license: Option<String>,
    pub crate_size: Option<i32>,
    pub published_by: Option<i32>,
}

#[derive(Insertable, Debug)]
#[table_name = "versions"]
pub struct NewVersion {
    crate_id: i32,
    num: String,
    features: serde_json::Value,
    license: Option<String>,
    crate_size: Option<i32>,
    published_by: i32,
}

/// The highest version (semver order) and the most recently updated version.
/// Typically used for a single crate.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TopVersions {
    /// The "highest" version in terms of semver
    pub highest: Option<semver::Version>,
    /// The "highest" non-prerelease version
    pub highest_stable: Option<semver::Version>,
    /// The "newest" version in terms of publishing date
    pub newest: Option<semver::Version>,
}

impl TopVersions {
    /// Return both the newest (most recently updated) and the
    /// highest version (in semver order) for a list of `Version` instances.
    pub fn from_versions(versions: Vec<Version>) -> Self {
        Self::from_date_version_pairs(versions.into_iter().map(|v| (v.created_at, v.num)))
    }

    /// Return both the newest (most recently updated) and the
    /// highest version (in semver order) for a collection of date/version pairs.
    pub fn from_date_version_pairs<T>(pairs: T) -> Self
    where
        T: Clone + IntoIterator<Item = (NaiveDateTime, String)>,
    {
        // filter out versions that we can't parse
        let pairs: Vec<(NaiveDateTime, semver::Version)> = pairs
            .into_iter()
            .filter_map(|(date, version)| {
                semver::Version::parse(&version)
                    .ok()
                    .map(|version| (date, version))
            })
            .collect();

        let newest = pairs.iter().max().map(|(_, v)| v.clone());
        let highest = pairs.iter().map(|(_, v)| v).max().map(|v| v.clone());
        let highest_stable = pairs
            .iter()
            .map(|(_, v)| v)
            .filter(|v| !v.is_prerelease())
            .max()
            .map(|v| v.clone());

        Self {
            highest,
            highest_stable,
            newest,
        }
    }
}

impl Version {
    /// Returns (dependency, crate dependency name)
    pub fn dependencies(&self, conn: &PgConnection) -> QueryResult<Vec<(Dependency, String)>> {
        Dependency::belonging_to(self)
            .inner_join(crates::table)
            .select((dependencies::all_columns, crates::name))
            .order((dependencies::optional, crates::name))
            .load(conn)
    }

    pub fn record_readme_rendering(version_id_: i32, conn: &PgConnection) -> QueryResult<usize> {
        use crate::schema::readme_renderings::dsl::*;
        use diesel::dsl::now;

        diesel::insert_into(readme_renderings)
            .values(version_id.eq(version_id_))
            .on_conflict(version_id)
            .do_update()
            .set(rendered_at.eq(now))
            .execute(conn)
    }

    /// Gets the User who ran `cargo publish` for this version, if recorded.
    /// Not for use when you have a group of versions you need the publishers for.
    pub fn published_by(&self, conn: &PgConnection) -> Option<User> {
        match self.published_by {
            Some(pb) => users::table.find(pb).first(conn).ok(),
            None => None,
        }
    }
}

impl NewVersion {
    pub fn new(
        crate_id: i32,
        num: &semver::Version,
        features: &HashMap<String, Vec<String>>,
        license: Option<String>,
        license_file: Option<&str>,
        crate_size: i32,
        published_by: i32,
    ) -> AppResult<Self> {
        let features = serde_json::to_value(features)?;

        let mut new_version = NewVersion {
            crate_id,
            num: num.to_string(),
            features,
            license,
            crate_size: Some(crate_size),
            published_by,
        };

        new_version.validate_license(license_file)?;

        Ok(new_version)
    }

    pub fn save(&self, conn: &PgConnection, published_by_email: &str) -> AppResult<Version> {
        use crate::schema::versions::dsl::*;
        use diesel::dsl::exists;
        use diesel::{insert_into, select};

        conn.transaction(|| {
            let already_uploaded = versions
                .filter(crate_id.eq(self.crate_id))
                .filter(num.eq(&self.num));
            if select(exists(already_uploaded)).get_result(conn)? {
                return Err(cargo_err(&format_args!(
                    "crate version `{}` is already \
                     uploaded",
                    self.num
                )));
            }

            let version: Version = insert_into(versions).values(self).get_result(conn)?;

            insert_into(versions_published_by::table)
                .values((
                    versions_published_by::version_id.eq(version.id),
                    versions_published_by::email.eq(published_by_email),
                ))
                .execute(conn)?;
            Ok(version)
        })
    }

    fn validate_license(&mut self, license_file: Option<&str>) -> AppResult<()> {
        if let Some(ref license) = self.license {
            for part in license.split('/') {
                license_exprs::validate_license_expr(part).map_err(|e| {
                    cargo_err(&format_args!(
                        "{}; see http://opensource.org/licenses \
                         for options, and http://spdx.org/licenses/ \
                         for their identifiers",
                        e
                    ))
                })?;
            }
        } else if license_file.is_some() {
            // If no license is given, but a license file is given, flag this
            // crate as having a nonstandard license. Note that we don't
            // actually do anything else with license_file currently.
            self.license = Some(String::from("non-standard"));
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::TopVersions;
    use chrono::NaiveDateTime;

    #[track_caller]
    fn date(str: &str) -> NaiveDateTime {
        str.parse().unwrap()
    }

    #[track_caller]
    fn version(str: &str) -> semver::Version {
        semver::Version::parse(str).unwrap()
    }

    #[test]
    fn top_versions_empty() {
        let versions = vec![];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: None,
                highest_stable: None,
                newest: None,
            }
        );
    }

    #[test]
    fn top_versions_single() {
        let versions = vec![(date("2020-12-03T12:34:56"), "1.0.0".into())];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: Some(version("1.0.0")),
                highest_stable: Some(version("1.0.0")),
                newest: Some(version("1.0.0")),
            }
        );
    }

    #[test]
    fn top_versions_prerelease() {
        let versions = vec![(date("2020-12-03T12:34:56"), "1.0.0-beta.5".into())];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: Some(version("1.0.0-beta.5")),
                highest_stable: None,
                newest: Some(version("1.0.0-beta.5")),
            }
        );
    }

    #[test]
    fn top_versions_multiple() {
        let versions = vec![
            (date("2018-12-03T12:34:56"), "1.0.0".into()),
            (date("2019-12-03T12:34:56"), "2.0.0-alpha.1".into()),
            (date("2020-12-01T12:34:56"), "everything is broken".into()),
            (date("2020-12-03T12:34:56"), "1.1.0".into()),
            (date("2020-12-31T12:34:56"), "1.0.4".into()),
        ];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: Some(version("2.0.0-alpha.1")),
                highest_stable: Some(version("1.1.0")),
                newest: Some(version("1.0.4")),
            }
        );
    }
}
<commit_msg>models/version: Replace removed `is_prerelease()` method<commit_after>use std::collections::HashMap;

use chrono::NaiveDateTime;
use diesel::prelude::*;

use crate::util::errors::{cargo_err, AppResult};

use crate::models::{Crate, Dependency, User};
use crate::schema::*;

// Queryable has a custom implementation below
#[derive(Clone, Identifiable, Associations, Debug, Queryable, Deserialize, Serialize)]
#[belongs_to(Crate)]
pub struct Version {
    pub id: i32,
    pub crate_id: i32,
    pub num: String,
    pub updated_at: NaiveDateTime,
    pub created_at: NaiveDateTime,
    pub downloads: i32,
    pub features: serde_json::Value,
    pub yanked: bool,
    pub license: Option<String>,
    pub crate_size: Option<i32>,
    pub published_by: Option<i32>,
}

#[derive(Insertable, Debug)]
#[table_name = "versions"]
pub struct NewVersion {
    crate_id: i32,
    num: String,
    features: serde_json::Value,
    license: Option<String>,
    crate_size: Option<i32>,
    published_by: i32,
}

/// The highest version (semver order) and the most recently updated version.
/// Typically used for a single crate.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct TopVersions {
    /// The "highest" version in terms of semver
    pub highest: Option<semver::Version>,
    /// The "highest" non-prerelease version
    pub highest_stable: Option<semver::Version>,
    /// The "newest" version in terms of publishing date
    pub newest: Option<semver::Version>,
}

impl TopVersions {
    /// Return both the newest (most recently updated) and the
    /// highest version (in semver order) for a list of `Version` instances.
    pub fn from_versions(versions: Vec<Version>) -> Self {
        Self::from_date_version_pairs(versions.into_iter().map(|v| (v.created_at, v.num)))
    }

    /// Return both the newest (most recently updated) and the
    /// highest version (in semver order) for a collection of date/version pairs.
    pub fn from_date_version_pairs<T>(pairs: T) -> Self
    where
        T: Clone + IntoIterator<Item = (NaiveDateTime, String)>,
    {
        // filter out versions that we can't parse
        let pairs: Vec<(NaiveDateTime, semver::Version)> = pairs
            .into_iter()
            .filter_map(|(date, version)| {
                semver::Version::parse(&version)
                    .ok()
                    .map(|version| (date, version))
            })
            .collect();

        let newest = pairs.iter().max().map(|(_, v)| v.clone());
        let highest = pairs.iter().map(|(_, v)| v).max().map(|v| v.clone());
        let highest_stable = pairs
            .iter()
            .map(|(_, v)| v)
            .filter(|v| v.pre.is_empty())
            .max()
            .map(|v| v.clone());

        Self {
            highest,
            highest_stable,
            newest,
        }
    }
}

impl Version {
    /// Returns (dependency, crate dependency name)
    pub fn dependencies(&self, conn: &PgConnection) -> QueryResult<Vec<(Dependency, String)>> {
        Dependency::belonging_to(self)
            .inner_join(crates::table)
            .select((dependencies::all_columns, crates::name))
            .order((dependencies::optional, crates::name))
            .load(conn)
    }

    pub fn record_readme_rendering(version_id_: i32, conn: &PgConnection) -> QueryResult<usize> {
        use crate::schema::readme_renderings::dsl::*;
        use diesel::dsl::now;

        diesel::insert_into(readme_renderings)
            .values(version_id.eq(version_id_))
            .on_conflict(version_id)
            .do_update()
            .set(rendered_at.eq(now))
            .execute(conn)
    }

    /// Gets the User who ran `cargo publish` for this version, if recorded.
    /// Not for use when you have a group of versions you need the publishers for.
    pub fn published_by(&self, conn: &PgConnection) -> Option<User> {
        match self.published_by {
            Some(pb) => users::table.find(pb).first(conn).ok(),
            None => None,
        }
    }
}

impl NewVersion {
    pub fn new(
        crate_id: i32,
        num: &semver::Version,
        features: &HashMap<String, Vec<String>>,
        license: Option<String>,
        license_file: Option<&str>,
        crate_size: i32,
        published_by: i32,
    ) -> AppResult<Self> {
        let features = serde_json::to_value(features)?;

        let mut new_version = NewVersion {
            crate_id,
            num: num.to_string(),
            features,
            license,
            crate_size: Some(crate_size),
            published_by,
        };

        new_version.validate_license(license_file)?;

        Ok(new_version)
    }

    pub fn save(&self, conn: &PgConnection, published_by_email: &str) -> AppResult<Version> {
        use crate::schema::versions::dsl::*;
        use diesel::dsl::exists;
        use diesel::{insert_into, select};

        conn.transaction(|| {
            let already_uploaded = versions
                .filter(crate_id.eq(self.crate_id))
                .filter(num.eq(&self.num));
            if select(exists(already_uploaded)).get_result(conn)? {
                return Err(cargo_err(&format_args!(
                    "crate version `{}` is already \
                     uploaded",
                    self.num
                )));
            }

            let version: Version = insert_into(versions).values(self).get_result(conn)?;

            insert_into(versions_published_by::table)
                .values((
                    versions_published_by::version_id.eq(version.id),
                    versions_published_by::email.eq(published_by_email),
                ))
                .execute(conn)?;
            Ok(version)
        })
    }

    fn validate_license(&mut self, license_file: Option<&str>) -> AppResult<()> {
        if let Some(ref license) = self.license {
            for part in license.split('/') {
                license_exprs::validate_license_expr(part).map_err(|e| {
                    cargo_err(&format_args!(
                        "{}; see http://opensource.org/licenses \
                         for options, and http://spdx.org/licenses/ \
                         for their identifiers",
                        e
                    ))
                })?;
            }
        } else if license_file.is_some() {
            // If no license is given, but a license file is given, flag this
            // crate as having a nonstandard license. Note that we don't
            // actually do anything else with license_file currently.
            self.license = Some(String::from("non-standard"));
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::TopVersions;
    use chrono::NaiveDateTime;

    #[track_caller]
    fn date(str: &str) -> NaiveDateTime {
        str.parse().unwrap()
    }

    #[track_caller]
    fn version(str: &str) -> semver::Version {
        semver::Version::parse(str).unwrap()
    }

    #[test]
    fn top_versions_empty() {
        let versions = vec![];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: None,
                highest_stable: None,
                newest: None,
            }
        );
    }

    #[test]
    fn top_versions_single() {
        let versions = vec![(date("2020-12-03T12:34:56"), "1.0.0".into())];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: Some(version("1.0.0")),
                highest_stable: Some(version("1.0.0")),
                newest: Some(version("1.0.0")),
            }
        );
    }

    #[test]
    fn top_versions_prerelease() {
        let versions = vec![(date("2020-12-03T12:34:56"), "1.0.0-beta.5".into())];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: Some(version("1.0.0-beta.5")),
                highest_stable: None,
                newest: Some(version("1.0.0-beta.5")),
            }
        );
    }

    #[test]
    fn top_versions_multiple() {
        let versions = vec![
            (date("2018-12-03T12:34:56"), "1.0.0".into()),
            (date("2019-12-03T12:34:56"), "2.0.0-alpha.1".into()),
            (date("2020-12-01T12:34:56"), "everything is broken".into()),
            (date("2020-12-03T12:34:56"), "1.1.0".into()),
            (date("2020-12-31T12:34:56"), "1.0.4".into()),
        ];
        assert_eq!(
            TopVersions::from_date_version_pairs(versions),
            TopVersions {
                highest: Some(version("2.0.0-alpha.1")),
                highest_stable: Some(version("1.1.0")),
                newest: Some(version("1.0.4")),
            }
        );
    }
}
<|endoftext|>


<commit_before>#![allow(clippy::eq_op)]

use serde::de::IntoDeserializer;
use serde::Deserialize;
use serde_derive::Deserialize;
use serde_yaml::{Number, Value};
use std::f64;

#[test]
fn test_nan() {
    let pos_nan = serde_yaml::from_str::<Value>(".nan").unwrap();
    assert!(pos_nan.is_f64());
    assert_eq!(pos_nan, pos_nan);

    let neg_fake_nan = serde_yaml::from_str::<Value>("-.nan").unwrap();
    assert!(neg_fake_nan.is_string());

    let significand_mask = 0xF_FFFF_FFFF_FFFF;
    let bits = (f64::NAN.to_bits() ^ significand_mask) | 1;
    let different_pos_nan = Value::Number(Number::from(f64::from_bits(bits)));
    assert_eq!(pos_nan, different_pos_nan);
}

#[test]
fn test_digits() {
    let num_string = serde_yaml::from_str::<Value>("01").unwrap();
    assert!(num_string.is_string());
}

#[test]
fn test_into_deserializer() {
    let value = serde_yaml::from_str::<Value>("xyz").unwrap();
    let s = String::deserialize(value.into_deserializer()).unwrap();
    assert_eq!(s, "xyz");

    let value = serde_yaml::from_str::<Value>("- first\n- second\n- third").unwrap();
    let arr = Vec::<String>::deserialize(value.into_deserializer()).unwrap();
    assert_eq!(arr, &["first", "second", "third"]);

    #[derive(Debug, Deserialize, PartialEq)]
    struct Test {
        first: String,
        second: u32,
    }

    let value = serde_yaml::from_str::<Value>("first: abc\nsecond: 99").unwrap();
    let test = Test::deserialize(value.into_deserializer()).unwrap();
    assert_eq!(
        test,
        Test {
            first: "abc".to_string(),
            second: 99
        }
    )
}
<commit_msg>Fix clippy warning<commit_after>#![allow(clippy::eq_op)]

use serde::de::IntoDeserializer;
use serde::Deserialize;
use serde_derive::Deserialize;
use serde_yaml::{Number, Value};
use std::f64;

#[test]
fn test_nan() {
    let pos_nan = serde_yaml::from_str::<Value>(".nan").unwrap();
    assert!(pos_nan.is_f64());
    assert_eq!(pos_nan, pos_nan);

    let neg_fake_nan = serde_yaml::from_str::<Value>("-.nan").unwrap();
    assert!(neg_fake_nan.is_string());

    let significand_mask = 0xF_FFFF_FFFF_FFFF;
    let bits = (f64::NAN.to_bits() ^ significand_mask) | 1;
    let different_pos_nan = Value::Number(Number::from(f64::from_bits(bits)));
    assert_eq!(pos_nan, different_pos_nan);
}

#[test]
fn test_digits() {
    let num_string = serde_yaml::from_str::<Value>("01").unwrap();
    assert!(num_string.is_string());
}

#[test]
fn test_into_deserializer() {
    #[derive(Debug, Deserialize, PartialEq)]
    struct Test {
        first: String,
        second: u32,
    }

    let value = serde_yaml::from_str::<Value>("xyz").unwrap();
    let s = String::deserialize(value.into_deserializer()).unwrap();
    assert_eq!(s, "xyz");

    let value = serde_yaml::from_str::<Value>("- first\n- second\n- third").unwrap();
    let arr = Vec::<String>::deserialize(value.into_deserializer()).unwrap();
    assert_eq!(arr, &["first", "second", "third"]);

    let value = serde_yaml::from_str::<Value>("first: abc\nsecond: 99").unwrap();
    let test = Test::deserialize(value.into_deserializer()).unwrap();
    assert_eq!(
        test,
        Test {
            first: "abc".to_string(),
            second: 99
        }
    )
}
<|endoftext|>


<commit_before>/*! # Reading and loading hyphenation dictionaries

To hyphenate words in a given language, it is first necessary to load
the relevant hyphenation dictionary into memory. This module offers
convenience methods for common retrieval patterns, courtesy of the
[`Load`] trait.

```
use hyphenation::Load;
use hyphenation::{Standard, Language};
```

The primary function of [`Load`] is to deserialize dictionaries from
buffers – usually, file buffers.

```norun
use std::io;
use std::fs::File;

let path_to_dict = "/path/to/english-dictionary.bincode";
let dict_file = File::open(path_to_dict) ?;
let mut reader = io::BufReader::new(dict_file);
let english_us = Standard::from_reader(Language::EnglishUS, &mut reader) ?;
```

Dictionaries can be loaded from the file system rather more succintly with
the [`from_path`] shorthand:

```norun
let path_to_dict = "/path/to/english-dictionary.bincode";
let english_us = Standard::from_path(Language::EnglishUS, path_to_dict) ?;
```

Dictionaries bundled with the `hyphenation` library are copied to Cargo's
output directory at build time. To locate them, look for a `dictionaries`
folder under `target`:

```ignore
$ find target -name "dictionaries"
target/debug/build/hyphenation-33034db3e3b5f3ce/out/dictionaries
```

## Embedding

Optionally, hyphenation dictionaries can be embedded in the compiled
artifact by enabling the `embed_all` feature. Embedded dictionaries can be
accessed directly from memory.

```ignore
use hyphenation::{Standard, Language, Load};

let english_us = Standard::from_embedded(Language::EnglishUS) ?;
```

Note that embeding significantly increases the size of the compiled artifact.

[`Load`]: trait.Load.html
[`from_path`]: trait.Load.html#method.from_path
*/

#[cfg(feature = "embed_all")] use resources::ResourceId;
use bincode as bin;
use std::error;
use std::fmt;
use std::io;
use std::fs::File;
use std::path::Path;
use std::result;

use hyphenation_commons::Language;
use hyphenation_commons::dictionary::{Standard, extended::Extended};

/// Convenience methods for the retrieval of hyphenation dictionaries.
pub trait Load : Sized {
    /// Read and deserialize the dictionary at the given path, verifying that it
    /// effectively belongs to the requested language.
    fn from_path<P>(lang : Language, path : P) -> Result<Self>
    where P : AsRef<Path> {
        let file = File::open(path) ?;
        Self::from_reader(lang, &mut io::BufReader::new(file))
    }

    /// Deserialize a dictionary from the provided reader, verifying that it
    /// effectively belongs to the requested language.
    fn from_reader<R>(lang : Language, reader : &mut R) -> Result<Self>
    where R : io::Read;

    /// Deserialize a dictionary from the provided reader.
    fn any_from_reader<R>(reader : &mut R) -> Result<Self>
    where R : io::Read;

    #[cfg(feature = "embed_all")]
    /// Deserialize the embedded dictionary.
    fn from_embedded(lang : Language) -> Result<Self>;

}

macro_rules! impl_load {
    ($dict:ty, $suffix:expr) => {
        impl Load for $dict {
            fn from_reader<R>(lang : Language, reader : &mut R) -> Result<Self>
            where R : io::Read {
                let dict : Self = bin::config().limit(5_000_000).deserialize_from(reader) ?;
                let (found, expected) = (dict.language, lang);
                if found != expected {
                    Err(Error::LanguageMismatch { expected, found })
                } else { Ok(dict) }
            }

            fn any_from_reader<R>(reader : &mut R) -> Result<Self>
            where R : io::Read {
                let dict : Self = bin::config().limit(5_000_000).deserialize_from(reader) ?;
                Ok(dict)
            }

            #[cfg(feature = "embed_all")]
            fn from_embedded(lang : Language) -> Result<Self> {
                let dict_bytes = retrieve_resource(lang.code(), $suffix) ?;
                let dict = bin::deserialize(dict_bytes) ?;
                Ok(dict)
            }
        }
    }
}

impl_load! { Standard, "standard" }
impl_load! { Extended, "extended" }

#[cfg(feature = "embed_all")]
fn retrieve_resource<'a>(code : &str, suffix : &str) -> Result<&'a [u8]> {
    let name = format!("{}.{}.bincode", code, suffix);
    let res : Option<ResourceId> = ResourceId::from_name(&name);
    match res {
        Some(data) => Ok(data.load()),
        None => Err(Error::Resource)
    }
}

pub type Result<T> = result::Result<T, Error>;

/// Failure modes of dictionary loading.
#[derive(Debug)]
pub enum Error {
    /// The dictionary could not be deserialized.
    Deserialization(bin::Error),
    /// The dictionary could not be read.
    IO(io::Error),
    /// The loaded dictionary is for the wrong language.
    LanguageMismatch { expected : Language, found : Language },
    /// The embedded dictionary could not be retrieved.
    Resource
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Deserialization(ref e) => e.description(),
            Error::IO(ref e) => e.description(),
            Error::LanguageMismatch { .. } => "loaded a dictionary for the wrong language",
            Error::Resource => "embedded dictionary could not be retrieved"
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Deserialization(ref e) => e.fmt(f),
            Error::IO(ref e) => e.fmt(f),
            Error::LanguageMismatch { expected, found } =>
                write!(f, "\
Language mismatch: attempted to load a dictionary for `{}`, but found
a dictionary for `{}` instead.", expected, found),
            Error::Resource => {
                let e = self as &error::Error;
                e.description().fmt(f)
            }
        }
    }
}

impl From<io::Error> for Error {
    fn from(err : io::Error) -> Error { Error::IO(err) }
}

impl From<bin::Error> for Error {
    fn from(err : bin::Error) -> Error { Error::Deserialization(err) }
}
<commit_msg>Review docs<commit_after>/*! # Reading and loading hyphenation dictionaries

To hyphenate words in a given language, it is first necessary to load
the relevant hyphenation dictionary into memory. This module offers
convenience methods for common retrieval patterns, courtesy of the
[`Load`] trait.

```
use hyphenation::Load;
use hyphenation::{Standard, Language};
```

The primary function of [`Load`] is to deserialize dictionaries from
buffers – usually, file buffers.

```norun
use std::io;
use std::fs::File;

let path_to_dict = "/path/to/english-dictionary.bincode";
let dict_file = File::open(path_to_dict) ?;
let mut reader = io::BufReader::new(dict_file);
let english_us = Standard::from_reader(Language::EnglishUS, &mut reader) ?;
```

Dictionaries can be loaded from the file system rather more succintly with
the [`from_path`] shorthand:

```norun
let path_to_dict = "/path/to/english-dictionary.bincode";
let english_us = Standard::from_path(Language::EnglishUS, path_to_dict) ?;
```

Dictionaries bundled with the `hyphenation` library are copied to Cargo's
output directory at build time. To locate them, look for a `dictionaries`
folder under `target`:

```ignore
$ find target -name "dictionaries"
target/debug/build/hyphenation-33034db3e3b5f3ce/out/dictionaries
```

## Embedding

Optionally, hyphenation dictionaries can be embedded in the compiled
artifact by enabling the `embed_all` feature. Embedded dictionaries can be
accessed directly from memory.

```ignore
use hyphenation::{Standard, Language, Load};

let english_us = Standard::from_embedded(Language::EnglishUS) ?;
```

Note that embeding significantly increases the size of the compiled artifact.

[`Load`]: trait.Load.html
[`from_path`]: trait.Load.html#method.from_path
*/

#[cfg(feature = "embed_all")] use resources::ResourceId;
use bincode as bin;
use std::error;
use std::fmt;
use std::io;
use std::fs::File;
use std::path::Path;
use std::result;

use hyphenation_commons::Language;
use hyphenation_commons::dictionary::{Standard, extended::Extended};

/// Convenience methods for the retrieval of hyphenation dictionaries.
pub trait Load : Sized {
    /// Read and deserialize the dictionary at the given path, verifying that it
    /// belongs to the expected language.
    fn from_path<P>(lang : Language, path : P) -> Result<Self>
    where P : AsRef<Path> {
        let file = File::open(path) ?;
        Self::from_reader(lang, &mut io::BufReader::new(file))
    }

    /// Deserialize a dictionary from the provided reader, verifying that it
    /// belongs to the expected language.
    fn from_reader<R>(lang : Language, reader : &mut R) -> Result<Self>
    where R : io::Read;

    /// Deserialize a dictionary from the provided reader.
    fn any_from_reader<R>(reader : &mut R) -> Result<Self>
    where R : io::Read;

    #[cfg(feature = "embed_all")]
    /// Deserialize the embedded dictionary for the given language.
    fn from_embedded(lang : Language) -> Result<Self>;

}

macro_rules! impl_load {
    ($dict:ty, $suffix:expr) => {
        impl Load for $dict {
            fn from_reader<R>(lang : Language, reader : &mut R) -> Result<Self>
            where R : io::Read {
                let dict : Self = bin::config().limit(5_000_000).deserialize_from(reader) ?;
                let (found, expected) = (dict.language, lang);
                if found != expected {
                    Err(Error::LanguageMismatch { expected, found })
                } else { Ok(dict) }
            }

            fn any_from_reader<R>(reader : &mut R) -> Result<Self>
            where R : io::Read {
                let dict : Self = bin::config().limit(5_000_000).deserialize_from(reader) ?;
                Ok(dict)
            }

            #[cfg(feature = "embed_all")]
            fn from_embedded(lang : Language) -> Result<Self> {
                let dict_bytes = retrieve_resource(lang.code(), $suffix) ?;
                let dict = bin::deserialize(dict_bytes) ?;
                Ok(dict)
            }
        }
    }
}

impl_load! { Standard, "standard" }
impl_load! { Extended, "extended" }

#[cfg(feature = "embed_all")]
fn retrieve_resource<'a>(code : &str, suffix : &str) -> Result<&'a [u8]> {
    let name = format!("{}.{}.bincode", code, suffix);
    let res : Option<ResourceId> = ResourceId::from_name(&name);
    match res {
        Some(data) => Ok(data.load()),
        None => Err(Error::Resource)
    }
}

pub type Result<T> = result::Result<T, Error>;

/// Failure modes of dictionary loading.
#[derive(Debug)]
pub enum Error {
    /// The dictionary could not be deserialized.
    Deserialization(bin::Error),
    /// The dictionary could not be read.
    IO(io::Error),
    /// The loaded dictionary is for the wrong language.
    LanguageMismatch { expected : Language, found : Language },
    /// The embedded dictionary could not be retrieved.
    Resource
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Deserialization(ref e) => e.description(),
            Error::IO(ref e) => e.description(),
            Error::LanguageMismatch { .. } => "loaded a dictionary for the wrong language",
            Error::Resource => "embedded dictionary could not be retrieved"
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Deserialization(ref e) => e.fmt(f),
            Error::IO(ref e) => e.fmt(f),
            Error::LanguageMismatch { expected, found } =>
                write!(f, "\
Language mismatch: attempted to load a dictionary for `{}`, but found
a dictionary for `{}` instead.", expected, found),
            Error::Resource => {
                let e = self as &error::Error;
                e.description().fmt(f)
            }
        }
    }
}

impl From<io::Error> for Error {
    fn from(err : io::Error) -> Error { Error::IO(err) }
}

impl From<bin::Error> for Error {
    fn from(err : bin::Error) -> Error { Error::Deserialization(err) }
}
<|endoftext|>


<commit_before>mod config;
mod err;
mod file;
mod process;
mod series;
mod track;

use crate::config::Config;
use crate::err::Result;
use crate::file::{FileType, SaveDir, SaveFile, SaveFileInDir};
use crate::series::local::{EpisodeList, EpisodeMatcher};
use crate::series::remote::anilist::{self, AniList, AniListConfig};
use crate::series::remote::offline::Offline;
use crate::series::remote::{RemoteService, SeriesInfo, Status};
use crate::series::{detect, SeasonInfoList, Series};
use crate::track::SeriesTracker;
use chrono::Utc;
use clap::clap_app;
use serde_derive::{Deserialize, Serialize};
use snafu::ResultExt;
use std::io;
use std::path::PathBuf;

fn main() {
    let args = clap_app!(anup =>
        (version: env!("CARGO_PKG_VERSION"))
        (author: env!("CARGO_PKG_AUTHORS"))
        (@arg series: +takes_value "The name of the series to watch")
        (@arg season: -s --season +takes_value "The season to watch. Meant to be used when playing from a folder that has multiple seasons merged together under one name")
        (@arg matcher: -m --matcher +takes_value "The custom pattern to match episode files with")
        (@arg offline: -o --offline "Run in offline mode")
        (@arg prefetch: --prefetch "Fetch series info from AniList. For use with offline mode")
        (@arg quiet: -q --quiet "Don't print series information")
    )
    .get_matches();

    if let Err(err) = run(&args) {
        err::display_error(err);
        std::process::exit(1);
    }
}

fn run(args: &clap::ArgMatches) -> Result<()> {
    let name = get_series_name(args)?;
    let config = load_config()?;

    let remote: Box<RemoteService> = if args.is_present("offline") {
        Box::new(Offline::new())
    } else {
        Box::new(init_anilist()?)
    };

    let season_num = args
        .value_of("season")
        .and_then(|num_str| num_str.parse().ok())
        .map(|num: usize| num.saturating_sub(1))
        .unwrap_or(0);

    let series = get_series(
        &name,
        &remote,
        &config,
        season_num,
        args.value_of("matcher"),
    )?;

    let mut tracker = SeriesTracker::init(&remote, &series.info, &name)?;
    tracker.begin_watching(&remote, &config)?;

    if !args.is_present("quiet") {
        print_info(&config, &series, &tracker);
    }

    play_episode_loop(remote, &config, &series, &mut tracker)?;
    Ok(())
}

#[derive(Deserialize, Serialize)]
struct SeriesName(String);

impl SeriesName {
    fn new<S>(name: S) -> SeriesName
    where
        S: Into<String>,
    {
        SeriesName(name.into())
    }

    #[inline(always)]
    fn take(self) -> String {
        self.0
    }
}

impl SaveFile for SeriesName {
    fn filename() -> &'static str {
        ".last_watched"
    }

    fn save_dir() -> SaveDir {
        SaveDir::LocalData
    }

    fn file_type() -> FileType {
        FileType::MessagePack
    }
}

fn get_series_name(args: &clap::ArgMatches) -> Result<String> {
    if let Some(name) = args.value_of("series") {
        let sname = SeriesName::new(name);
        sname.save()?;

        return Ok(sname.take());
    }

    match SeriesName::load() {
        Ok(sname) => Ok(sname.take()),
        Err(ref err) if err.is_file_nonexistant() => Err(err::Error::NoSavedSeriesName),
        Err(err) => Err(err),
    }
}

fn load_config() -> Result<Config> {
    match Config::load() {
        Ok(config) => Ok(config),
        Err(ref err) if err.is_file_nonexistant() => {
            // Default base directory: ~/anime/
            let mut dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~/"));
            dir.push("anime");

            let config = Config::new(dir);
            config.save()?;
            Ok(config)
        }
        Err(err) => Err(err),
    }
}

fn load_episode_matcher<S>(keyword: S, matcher: Option<&str>) -> Result<EpisodeMatcher>
where
    S: AsRef<str>,
{
    match EpisodeMatcher::load(&keyword) {
        Ok(matcher) => Ok(matcher),
        Err(ref err) if err.is_file_nonexistant() => match matcher {
            Some(matcher) => {
                let matcher = EpisodeMatcher::with_matcher(matcher)?;
                matcher.save(keyword)?;
                Ok(matcher)
            }
            None => Ok(EpisodeMatcher::new()),
        },
        Err(err) => Err(err),
    }
}

fn init_anilist() -> Result<AniList> {
    use crate::series::remote::anilist::AccessToken;

    let config = match AniListConfig::load() {
        Ok(config) => config,
        Err(ref err) if err.is_file_nonexistant() => {
            println!(
                "need AniList login token\ngo to {}\n\npaste your token:",
                anilist::LOGIN_URL
            );

            let token = {
                let mut buffer = String::new();
                io::stdin().read_line(&mut buffer).context(err::IO)?;
                let buffer = buffer.trim_end();

                AccessToken::new(buffer)
            };

            let config = AniListConfig::new(token);
            config.save()?;
            config
        }
        Err(err) => return Err(err),
    };

    AniList::login(config)
}

fn get_season_list<R, S>(
    remote: R,
    keyword: S,
    season_num: usize,
    dir: &PathBuf,
) -> Result<SeasonInfoList>
where
    R: AsRef<RemoteService>,
    S: AsRef<str>,
{
    let keyword = keyword.as_ref();

    match SeasonInfoList::load(keyword) {
        Ok(mut seasons) => {
            if seasons.add_from_remote_upto(&remote, season_num)? {
                seasons.save(keyword)?;
            }

            Ok(seasons)
        }
        Err(ref err) if err.is_file_nonexistant() => {
            // The directory is more likely to have a complete name, which will likely match
            // better than just a keyword, which could be an abstract identifier
            let dir_name = dir
                .file_name()
                .map(|name| name.to_string_lossy().into_owned())
                .unwrap_or_else(|| keyword.into());

            let info = SeriesInfo::best_matching_from_remote(&remote, dir_name)?;

            let seasons = SeasonInfoList::from_info_and_remote(info, &remote, Some(season_num))?;
            seasons.save(keyword)?;
            Ok(seasons)
        }
        Err(err) => Err(err),
    }
}

fn get_series<R, S>(
    name: S,
    remote: R,
    config: &Config,
    season_num: usize,
    matcher: Option<&str>,
) -> Result<Series>
where
    R: AsRef<RemoteService>,
    S: AsRef<str>,
{
    // TODO: allow overriding with argument
    let dir = detect::best_matching_folder(&name, &config.series_dir)?;

    let episodes = {
        let matcher = load_episode_matcher(&name, matcher)?;
        EpisodeList::parse(&dir, &matcher)?
    };

    let seasons = get_season_list(&remote, &name, season_num, &dir)?;
    Series::from_season_list(seasons, season_num, episodes)
}

fn print_info(config: &Config, series: &Series, tracker: &SeriesTracker) {
    use std::borrow::Cow;

    let repeater = "-".repeat(series.info.title.len() + 2);

    println!("+{}+\n@ {} @\n+{}+", repeater, series.info.title, repeater);
    println!();

    println!(
        "watched: {}/{}",
        tracker.state.watched_eps(),
        series.info.episodes
    );
    println!(
        "score: {}",
        tracker
            .state
            .score()
            .map(|s| Cow::Owned(s.to_string()))
            .unwrap_or_else(|| Cow::Borrowed("none"))
    );

    println!();

    let watch_time =
        series.info.episode_length * (series.info.episodes - tracker.state.watched_eps());
    let minutes_must_watch = series.info.episode_length as f32 * config.episode.pcnt_must_watch;

    println!("time to finish: {}", hms_from_mins(watch_time as f32));
    println!("progress time: {}", ms_from_mins(minutes_must_watch as f32));

    println!();
    println!("+{}+", repeater);
    println!();
}

fn ms_from_mins(mins: f32) -> String {
    let m = mins.floor() as u32;
    let s = (mins * 60.0 % 60.0).floor() as u32;

    format!("{:02}:{:02}", m, s)
}

fn hms_from_mins(mins: f32) -> String {
    let h = (mins / 60.0).floor() as u32;
    let m = (mins % 60.0).floor() as u32;
    let s = m * 60 % 60;

    format!("{:02}:{:02}:{:02}", h, m, s)
}

#[derive(PartialEq)]
enum PlayResult {
    Continue,
    Finished,
}

fn play_episode<R>(
    remote: R,
    config: &Config,
    series: &Series,
    tracker: &mut SeriesTracker,
) -> Result<PlayResult>
where
    R: AsRef<RemoteService>,
{
    let ep_num = tracker.state.watched_eps() + 1;
    let start_time = Utc::now();

    series.play_episode(ep_num)?;

    let end_time = Utc::now();

    let mins_watched = {
        let watch_time = end_time - start_time;
        watch_time.num_seconds() as f32 / 60.0
    };

    let mins_must_watch = series.info.episode_length as f32 * config.episode.pcnt_must_watch;

    if mins_watched < mins_must_watch {
        println!("did not watch episode long enough");
        return Ok(PlayResult::Finished);
    }

    tracker.episode_completed(&remote, config)?;

    match tracker.state.status() {
        Status::Completed => {
            println!("completed!");
            Ok(PlayResult::Finished)
        }
        _ => {
            println!("{}/{} completed", ep_num, series.info.episodes);
            Ok(PlayResult::Continue)
        }
    }
}

fn play_episode_loop<R>(
    remote: R,
    config: &Config,
    series: &Series,
    tracker: &mut SeriesTracker,
) -> Result<()>
where
    R: AsRef<RemoteService>,
{
    use std::thread;
    use std::time::Duration;

    loop {
        if let PlayResult::Finished = play_episode(&remote, config, series, tracker)? {
            break Ok(());
        }

        if config.episode.seconds_before_next > 0.0 {
            let millis = (config.episode.seconds_before_next * 1000.0) as u64;
            let duration = Duration::from_millis(millis);
            thread::sleep(duration);
        }
    }
}
<commit_msg>[main] Add oneshot argument<commit_after>mod config;
mod err;
mod file;
mod process;
mod series;
mod track;

use crate::config::Config;
use crate::err::Result;
use crate::file::{FileType, SaveDir, SaveFile, SaveFileInDir};
use crate::series::local::{EpisodeList, EpisodeMatcher};
use crate::series::remote::anilist::{self, AniList, AniListConfig};
use crate::series::remote::offline::Offline;
use crate::series::remote::{RemoteService, SeriesInfo, Status};
use crate::series::{detect, SeasonInfoList, Series};
use crate::track::SeriesTracker;
use chrono::Utc;
use clap::clap_app;
use serde_derive::{Deserialize, Serialize};
use snafu::ResultExt;
use std::io;
use std::path::PathBuf;

fn main() {
    let args = clap_app!(anup =>
        (version: env!("CARGO_PKG_VERSION"))
        (author: env!("CARGO_PKG_AUTHORS"))
        (@arg series: +takes_value "The name of the series to watch")
        (@arg season: -s --season +takes_value "The season to watch. Meant to be used when playing from a folder that has multiple seasons merged together under one name")
        (@arg matcher: -m --matcher +takes_value "The custom pattern to match episode files with")
        (@arg offline: -o --offline "Run in offline mode")
        (@arg prefetch: --prefetch "Fetch series info from AniList. For use with offline mode")
        (@arg oneshot: --oneshot "Play the next episode and exit")
        (@arg quiet: -q --quiet "Don't print series information")
    )
    .get_matches();

    if let Err(err) = run(&args) {
        err::display_error(err);
        std::process::exit(1);
    }
}

fn run(args: &clap::ArgMatches) -> Result<()> {
    let name = get_series_name(args)?;
    let config = load_config()?;

    let remote: Box<RemoteService> = if args.is_present("offline") {
        Box::new(Offline::new())
    } else {
        Box::new(init_anilist()?)
    };

    let season_num = args
        .value_of("season")
        .and_then(|num_str| num_str.parse().ok())
        .map(|num: usize| num.saturating_sub(1))
        .unwrap_or(0);

    let series = get_series(
        &name,
        &remote,
        &config,
        season_num,
        args.value_of("matcher"),
    )?;

    let mut tracker = SeriesTracker::init(&remote, &series.info, &name)?;
    tracker.begin_watching(&remote, &config)?;

    if !args.is_present("quiet") {
        print_info(&config, &series, &tracker);
    }

    if args.is_present("oneshot") {
        play_episode(remote, &config, &series, &mut tracker)?;
    } else {
        play_episode_loop(remote, &config, &series, &mut tracker)?;
    }

    Ok(())
}

#[derive(Deserialize, Serialize)]
struct SeriesName(String);

impl SeriesName {
    fn new<S>(name: S) -> SeriesName
    where
        S: Into<String>,
    {
        SeriesName(name.into())
    }

    #[inline(always)]
    fn take(self) -> String {
        self.0
    }
}

impl SaveFile for SeriesName {
    fn filename() -> &'static str {
        ".last_watched"
    }

    fn save_dir() -> SaveDir {
        SaveDir::LocalData
    }

    fn file_type() -> FileType {
        FileType::MessagePack
    }
}

fn get_series_name(args: &clap::ArgMatches) -> Result<String> {
    if let Some(name) = args.value_of("series") {
        let sname = SeriesName::new(name);
        sname.save()?;

        return Ok(sname.take());
    }

    match SeriesName::load() {
        Ok(sname) => Ok(sname.take()),
        Err(ref err) if err.is_file_nonexistant() => Err(err::Error::NoSavedSeriesName),
        Err(err) => Err(err),
    }
}

fn load_config() -> Result<Config> {
    match Config::load() {
        Ok(config) => Ok(config),
        Err(ref err) if err.is_file_nonexistant() => {
            // Default base directory: ~/anime/
            let mut dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("~/"));
            dir.push("anime");

            let config = Config::new(dir);
            config.save()?;
            Ok(config)
        }
        Err(err) => Err(err),
    }
}

fn load_episode_matcher<S>(keyword: S, matcher: Option<&str>) -> Result<EpisodeMatcher>
where
    S: AsRef<str>,
{
    match EpisodeMatcher::load(&keyword) {
        Ok(matcher) => Ok(matcher),
        Err(ref err) if err.is_file_nonexistant() => match matcher {
            Some(matcher) => {
                let matcher = EpisodeMatcher::with_matcher(matcher)?;
                matcher.save(keyword)?;
                Ok(matcher)
            }
            None => Ok(EpisodeMatcher::new()),
        },
        Err(err) => Err(err),
    }
}

fn init_anilist() -> Result<AniList> {
    use crate::series::remote::anilist::AccessToken;

    let config = match AniListConfig::load() {
        Ok(config) => config,
        Err(ref err) if err.is_file_nonexistant() => {
            println!(
                "need AniList login token\ngo to {}\n\npaste your token:",
                anilist::LOGIN_URL
            );

            let token = {
                let mut buffer = String::new();
                io::stdin().read_line(&mut buffer).context(err::IO)?;
                let buffer = buffer.trim_end();

                AccessToken::new(buffer)
            };

            let config = AniListConfig::new(token);
            config.save()?;
            config
        }
        Err(err) => return Err(err),
    };

    AniList::login(config)
}

fn get_season_list<R, S>(
    remote: R,
    keyword: S,
    season_num: usize,
    dir: &PathBuf,
) -> Result<SeasonInfoList>
where
    R: AsRef<RemoteService>,
    S: AsRef<str>,
{
    let keyword = keyword.as_ref();

    match SeasonInfoList::load(keyword) {
        Ok(mut seasons) => {
            if seasons.add_from_remote_upto(&remote, season_num)? {
                seasons.save(keyword)?;
            }

            Ok(seasons)
        }
        Err(ref err) if err.is_file_nonexistant() => {
            // The directory is more likely to have a complete name, which will likely match
            // better than just a keyword, which could be an abstract identifier
            let dir_name = dir
                .file_name()
                .map(|name| name.to_string_lossy().into_owned())
                .unwrap_or_else(|| keyword.into());

            let info = SeriesInfo::best_matching_from_remote(&remote, dir_name)?;

            let seasons = SeasonInfoList::from_info_and_remote(info, &remote, Some(season_num))?;
            seasons.save(keyword)?;
            Ok(seasons)
        }
        Err(err) => Err(err),
    }
}

fn get_series<R, S>(
    name: S,
    remote: R,
    config: &Config,
    season_num: usize,
    matcher: Option<&str>,
) -> Result<Series>
where
    R: AsRef<RemoteService>,
    S: AsRef<str>,
{
    // TODO: allow overriding with argument
    let dir = detect::best_matching_folder(&name, &config.series_dir)?;

    let episodes = {
        let matcher = load_episode_matcher(&name, matcher)?;
        EpisodeList::parse(&dir, &matcher)?
    };

    let seasons = get_season_list(&remote, &name, season_num, &dir)?;
    Series::from_season_list(seasons, season_num, episodes)
}

fn print_info(config: &Config, series: &Series, tracker: &SeriesTracker) {
    use std::borrow::Cow;

    let repeater = "-".repeat(series.info.title.len() + 2);

    println!("+{}+\n@ {} @\n+{}+", repeater, series.info.title, repeater);
    println!();

    println!(
        "watched: {}/{}",
        tracker.state.watched_eps(),
        series.info.episodes
    );
    println!(
        "score: {}",
        tracker
            .state
            .score()
            .map(|s| Cow::Owned(s.to_string()))
            .unwrap_or_else(|| Cow::Borrowed("none"))
    );

    println!();

    let watch_time =
        series.info.episode_length * (series.info.episodes - tracker.state.watched_eps());
    let minutes_must_watch = series.info.episode_length as f32 * config.episode.pcnt_must_watch;

    println!("time to finish: {}", hms_from_mins(watch_time as f32));
    println!("progress time: {}", ms_from_mins(minutes_must_watch as f32));

    println!();
    println!("+{}+", repeater);
    println!();
}

fn ms_from_mins(mins: f32) -> String {
    let m = mins.floor() as u32;
    let s = (mins * 60.0 % 60.0).floor() as u32;

    format!("{:02}:{:02}", m, s)
}

fn hms_from_mins(mins: f32) -> String {
    let h = (mins / 60.0).floor() as u32;
    let m = (mins % 60.0).floor() as u32;
    let s = m * 60 % 60;

    format!("{:02}:{:02}:{:02}", h, m, s)
}

#[derive(PartialEq)]
enum PlayResult {
    Continue,
    Finished,
}

fn play_episode<R>(
    remote: R,
    config: &Config,
    series: &Series,
    tracker: &mut SeriesTracker,
) -> Result<PlayResult>
where
    R: AsRef<RemoteService>,
{
    let ep_num = tracker.state.watched_eps() + 1;
    let start_time = Utc::now();

    series.play_episode(ep_num)?;

    let end_time = Utc::now();

    let mins_watched = {
        let watch_time = end_time - start_time;
        watch_time.num_seconds() as f32 / 60.0
    };

    let mins_must_watch = series.info.episode_length as f32 * config.episode.pcnt_must_watch;

    if mins_watched < mins_must_watch {
        println!("did not watch episode long enough");
        return Ok(PlayResult::Finished);
    }

    tracker.episode_completed(&remote, config)?;

    match tracker.state.status() {
        Status::Completed => {
            println!("completed!");
            Ok(PlayResult::Finished)
        }
        _ => {
            println!("{}/{} completed", ep_num, series.info.episodes);
            Ok(PlayResult::Continue)
        }
    }
}

fn play_episode_loop<R>(
    remote: R,
    config: &Config,
    series: &Series,
    tracker: &mut SeriesTracker,
) -> Result<()>
where
    R: AsRef<RemoteService>,
{
    use std::thread;
    use std::time::Duration;

    loop {
        if let PlayResult::Finished = play_episode(&remote, config, series, tracker)? {
            break Ok(());
        }

        if config.episode.seconds_before_next > 0.0 {
            let millis = (config.episode.seconds_before_next * 1000.0) as u64;
            let duration = Duration::from_millis(millis);
            thread::sleep(duration);
        }
    }
}
<|endoftext|>


<commit_before>#![feature(start, slice_patterns)]

#[macro_use]
#[cfg(target_os = "android")]
extern crate android_glue;

#[macro_use]
extern crate lazy_static;

extern crate cgmath;
extern crate collision;
extern crate libc;
extern crate noise;
#[cfg(target_os = "linux")]
extern crate png;
extern crate time;

#[cfg(target_os = "android")]
use std::sync::mpsc;
#[cfg(target_os = "android")]
use std::sync::mpsc::TryRecvError;

#[cfg(target_os = "android")]
use android_glue::{AssetError, Event};
#[cfg(target_os = "android")]
use egl_context::EglContext;
use engine::Engine;
#[cfg(target_os = "linux")]
use program::Program;
#[cfg(target_os = "linux")]
use x11::{Event, XWindow};

#[macro_use]
mod log;

#[cfg(target_os = "android")]
mod egl;
#[cfg(target_os = "android")]
mod egl_context;
mod engine;
mod fov;
mod gl;
mod mesh;
mod perlin;
mod program;
mod world;
#[cfg(target_os = "linux")]
mod x11;

#[cfg(target_os = "android")]
android_start!(main);

/**
 * This is the main entry point of a native application that is using android_native_app_glue.
 * It runs in its own thread, with its own event loop for receiving input events and doing other
 * things.
 */
#[cfg(target_os = "android")]
pub fn main() {
  log!("-------------------------------------------------------------------");

  // TODO: Implement restoring / saving state in android-rust-glue.
  let mut engine = Engine::new();

  let (event_tx, event_rx) = mpsc::channel::<Event>();
  android_glue::add_sender_missing(event_tx);
  'event: loop {
    match event_rx.try_recv() {
      Ok(ev) => {
        log!("----- Event: {:?}", ev);
        match ev {
          Event::InitWindow => init_display(&mut engine),
          Event::GainedFocus => engine.gained_focus(),
          Event::Pause => engine.lost_focus(),
          Event::TermWindow => engine.term(),
          _ => (),
        }
      },
      Err(TryRecvError::Empty) => (),
      Err(TryRecvError::Disconnected) => {
        panic!("----- Failed to get next event, channel disconnected")
      },
    }
    engine.update_draw();

    let app = android_glue::get_app();
    if app.destroyRequested != 0 {
      engine.term();  // Double-termination is fine.
      break 'event;
    }
  }
}

/// Initialize EGL context for the current display.
#[cfg(target_os = "android")]
fn init_display(engine: &mut Engine) {
  log!("*** Renderer initializing...");
  let start_ns = time::precise_time_ns();
  let app = android_glue::get_app();
  let window = app.window as *mut android_glue::ffi::ANativeWindow;
  let egl_context = Box::new(EglContext::new(window));
  let texture_atlas_bytes = load_asset("atlas.png");
  engine.init(egl_context, &texture_atlas_bytes);
  let elapsed_ms = (time::precise_time_ns() - start_ns) as f32 / 1000000.0;
  log!("*** Renderer initialized, {:.3}ms", elapsed_ms);
}

#[cfg(target_os = "android")]
fn load_asset(filename: &str) -> Vec<u8> {
  match android_glue::load_asset(filename) {
    Ok(v) => v,
    Err(e) => {
      let mess = match e {
        AssetError::AssetMissing => "asset missing",
        AssetError::EmptyBuffer => "couldn't read asset",
      };
      panic!("Loading atlas.png failed: {}", mess)
    },
  }
}

#[cfg(target_os = "linux")]
pub fn main() {
  log!("-------------------------------------------------------------------");
  let window = XWindow::new("Rusty Cardboard");

  // Compile and link vertex and fragment shaders.
  let program = match Program::new() {
    Ok(p) => p,
    Err(e) => panic!("Program failed: {:?}", e),
  };

  let mut engine = Engine::new(window, program);
  engine.init(TEXTURE_ATLAS);

  while !engine.is_closed() {
    engine.update_draw();
    handle_events(&mut engine);
   }
}

#[cfg(target_os = "linux")]
static TEXTURE_ATLAS: &'static [u8] = include_bytes!("../assets/atlas.png");

/// Process X11 events while there are any queued.
#[cfg(target_os = "linux")]
fn handle_events(engine: &mut Engine) {
 enum FocusChange {
    Gained,
    Lost,
  }

  let mut focus_change: Option<FocusChange> = None;
  let mut resized_to: Option<(u32, u32)> = None;
  for e in engine.poll_events() {
    match e {
      Event::MouseMoved(_) => (),  // too much spam
      Event::Resized(w, h) => {
        log!("{:?}", Event::Resized(w, h));
        resized_to = Some((w, h));
      },
      Event::Focused(f) => {
        log!("{:?}", Event::Focused(f));
        if f {
          focus_change = Some(FocusChange::Gained);
        } else {
          focus_change = Some(FocusChange::Lost);
        }
      },
      e => log!("{:?}", e),
    }
  }

  match focus_change {
    Some(FocusChange::Gained) => engine.gained_focus(),
    Some(FocusChange::Lost) => engine.lost_focus(),
    None => (),
  }
  if let Some((w, h)) = resized_to {
    engine.set_viewport(w as i32, h as i32);
  }
}
<commit_msg>Refactor: Extract handle_event() on Android.<commit_after>#![feature(start, slice_patterns)]

#[macro_use]
#[cfg(target_os = "android")]
extern crate android_glue;

#[macro_use]
extern crate lazy_static;

extern crate cgmath;
extern crate collision;
extern crate libc;
extern crate noise;
#[cfg(target_os = "linux")]
extern crate png;
extern crate time;

#[cfg(target_os = "android")]
use std::sync::mpsc;
#[cfg(target_os = "android")]
use std::sync::mpsc::{Receiver, TryRecvError};

#[cfg(target_os = "android")]
use android_glue::{AssetError, Event};
#[cfg(target_os = "android")]
use egl_context::EglContext;
use engine::Engine;
#[cfg(target_os = "linux")]
use program::Program;
#[cfg(target_os = "linux")]
use x11::{Event, XWindow};

#[macro_use]
mod log;

#[cfg(target_os = "android")]
mod egl;
#[cfg(target_os = "android")]
mod egl_context;
mod engine;
mod fov;
mod gl;
mod mesh;
mod perlin;
mod program;
mod world;
#[cfg(target_os = "linux")]
mod x11;

#[cfg(target_os = "android")]
android_start!(main);

/**
 * This is the main entry point of a native application that is using android_native_app_glue.
 * It runs in its own thread, with its own event loop for receiving input events and doing other
 * things.
 */
#[cfg(target_os = "android")]
pub fn main() {
  log!("-------------------------------------------------------------------");

  // TODO: Implement restoring / saving state in android-rust-glue.
  let mut engine = Engine::new();

  let (event_tx, event_rx) = mpsc::channel::<Event>();
  android_glue::add_sender_missing(event_tx);
  'event: loop {
    handle_event(&event_rx, &mut engine);
    engine.update_draw();

    let app = android_glue::get_app();
    if app.destroyRequested != 0 {
      engine.term();  // Double-termination is fine.
      break 'event;
    }
  }
}

/// Handle a single event if present.
#[cfg(target_os = "android")]
fn handle_event(rx: &Receiver<Event>, engine: &mut Engine) {
  match rx.try_recv() {
    Ok(ev) => {
      log!("----- Event: {:?}", ev);
      match ev {
        Event::InitWindow => init_display(engine),
        Event::GainedFocus => engine.gained_focus(),
        Event::Pause => engine.lost_focus(),
        Event::TermWindow => engine.term(),
        _ => (),
      }
    },
    Err(TryRecvError::Empty) => (),
    Err(TryRecvError::Disconnected) => {
      panic!("----- Failed to get next event, channel disconnected")
    },
  }
}

/// Initialize EGL context for the current display.
#[cfg(target_os = "android")]
fn init_display(engine: &mut Engine) {
  log!("*** Renderer initializing...");
  let start_ns = time::precise_time_ns();
  let app = android_glue::get_app();
  let window = app.window as *mut android_glue::ffi::ANativeWindow;
  let egl_context = Box::new(EglContext::new(window));
  let texture_atlas_bytes = load_asset("atlas.png");
  engine.init(egl_context, &texture_atlas_bytes);
  let elapsed_ms = (time::precise_time_ns() - start_ns) as f32 / 1000000.0;
  log!("*** Renderer initialized, {:.3}ms", elapsed_ms);
}

#[cfg(target_os = "android")]
fn load_asset(filename: &str) -> Vec<u8> {
  match android_glue::load_asset(filename) {
    Ok(v) => v,
    Err(e) => {
      let mess = match e {
        AssetError::AssetMissing => "asset missing",
        AssetError::EmptyBuffer => "couldn't read asset",
      };
      panic!("Loading atlas.png failed: {}", mess)
    },
  }
}

#[cfg(target_os = "linux")]
pub fn main() {
  log!("-------------------------------------------------------------------");
  let window = XWindow::new("Rusty Cardboard");

  // Compile and link vertex and fragment shaders.
  let program = match Program::new() {
    Ok(p) => p,
    Err(e) => panic!("Program failed: {:?}", e),
  };

  let mut engine = Engine::new(window, program);
  engine.init(TEXTURE_ATLAS);

  while !engine.is_closed() {
    engine.update_draw();
    handle_events(&mut engine);
   }
}

#[cfg(target_os = "linux")]
static TEXTURE_ATLAS: &'static [u8] = include_bytes!("../assets/atlas.png");

/// Process X11 events while there are any queued.
#[cfg(target_os = "linux")]
fn handle_events(engine: &mut Engine) {
 enum FocusChange {
    Gained,
    Lost,
  }

  let mut focus_change: Option<FocusChange> = None;
  let mut resized_to: Option<(u32, u32)> = None;
  for e in engine.poll_events() {
    match e {
      Event::MouseMoved(_) => (),  // too much spam
      Event::Resized(w, h) => {
        log!("{:?}", Event::Resized(w, h));
        resized_to = Some((w, h));
      },
      Event::Focused(f) => {
        log!("{:?}", Event::Focused(f));
        if f {
          focus_change = Some(FocusChange::Gained);
        } else {
          focus_change = Some(FocusChange::Lost);
        }
      },
      e => log!("{:?}", e),
    }
  }

  match focus_change {
    Some(FocusChange::Gained) => engine.gained_focus(),
    Some(FocusChange::Lost) => engine.lost_focus(),
    None => (),
  }
  if let Some((w, h)) = resized_to {
    engine.set_viewport(w as i32, h as i32);
  }
}
<|endoftext|>


<commit_before>#![feature(collections, io, slice_patterns)]
use std::fs;
use std::io;
use std::io::{Read, Write};
use std::path::Path;

#[derive(Debug)]
enum ParserResult {
    Something(Op),
    Nothing,
    EOF
}

use ParserResult::*;

#[derive(Debug)]
enum Op {
    Add(u8),
    Mov(isize),
    In,
    Out,
    Loop(OpStream),

    Clear,
    ClearAdd(isize),
    ClearSub(isize)
}

use Op::*;

#[derive(Debug)]
struct OpStream {
    ops: Vec<Op>
}

impl OpStream {
    fn add(&mut self, op: Op) {
        self.ops.push(op);
    }

    fn optimize(&mut self) {
        let mut i = 0;
        while i < self.ops.len() {
            match &self.ops[i..] {
                [Add(a), Add(b), ..] => {
                    self.ops[i] = Add(a.wrapping_add(b));
                    self.ops.remove(i + 1);
                }
                [Mov(a), Mov(b), ..] => {
                    self.ops[i] = Mov(a + b);
                    self.ops.remove(i + 1);
                }
                [Add(0), ..] | [Mov(0), ..] => {
                    self.ops.remove(i);
                }
                [Loop(_), ..] => {
                    let mut maybe_new_op: Option<Op> = None;

                    if let &mut Loop(ref mut stream) = &mut self.ops[i] {
                        stream.optimize();
                        match &stream.ops[..] {
                            [Add(x)] if x % 2 != 0 => {
                                maybe_new_op = Some(Clear);
                            }
                            [Add(a), Mov(x), Add(b), Mov(y)] if a.wrapping_add(b) == 0 && a % 2 != 0 && x == -y => {
                                maybe_new_op = Some(ClearAdd(x))
                            }
                            [Add(a), Mov(x), Add(b), Mov(y)] if a == b && a % 2 != 0 && x == -y => {
                                maybe_new_op = Some(ClearSub(x))
                            }
                            _ => ()
                        }
                    }

                    if let Some(new_op) = maybe_new_op {
                        self.ops[i] = new_op;
                    } else {
                        i += 1
                    }
                }
                _ => i += 1
            }
        }
    }

    fn get(&self) -> &[Op] {
        &self.ops[..]
    }

    fn new() -> OpStream {
        OpStream { ops: Vec::new() }
    }
}

static ZERO: u8 = 0;

struct State {
    index: usize,
    memory: Vec<u8>
}

impl State {
    fn new() -> State {
        State { index: 0, memory: vec![] }
    }

    fn rel_index(&self, relative: isize) -> usize {
        (self.index as isize + relative) as usize
    }

    fn step(&mut self, op: &Op) -> bool {
        match op {
            &Add(i) => {
                self[0] = self[0].wrapping_add(i);
            }
            &Mov(n) => {
                self.index = self.rel_index(n);
            }
            &In => {
                let mut c = vec![0u8];
                if io::stdin().read(&mut c).unwrap() == 0 {
                    return false;
                }
                self[0] = c[0];
            }
            &Out => {
                io::stdout().write(&vec![self[0]]).unwrap();
            }
            &Loop(ref ops) => {
                while self[0] != 0 {
                    if !self.run(ops.get()) {
                        return false;
                    }
                }
            }
            &Clear => {
                self[0] = 0;
            }
            &ClearAdd(offset) => {
                if self[0] != 0 {
                    self[offset] = self[offset].wrapping_add(self[0]);
                    self[0] = 0;
                }
            }
            &ClearSub(offset) => {
                if self[0] != 0 {
                    self[offset] = self[offset].wrapping_sub(self[0]);
                    self[0] = 0;
                }
            }
        }
        return true;
    }

    fn run(&mut self, ops: &[Op]) -> bool {
        for op in ops {
            if !self.step(op) {
                return false;
            }
        }
        return true;
    }
}

impl std::ops::Index<isize> for State {
    type Output = u8;
    fn index(&self, _index: isize) -> &u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            &ZERO
        } else {
            &self.memory[idx]
        }
    }
}

impl std::ops::IndexMut<isize> for State {
    fn index_mut(&mut self, _index: isize) -> &mut u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            self.memory.resize(idx * 2 + 1, 0);
        }
        &mut self.memory[idx]
    }
}

fn main() {
    let filenames: Vec<String> = std::iter::FromIterator::from_iter(std::env::args());

    for filename in &filenames[1..] {
        match reader(&Path::new(filename)) {
            Ok(reader) => {
                let mut chars = reader.chars().peekable();

                let mut opstream = OpStream::new();
                loop {
                    match parse(&mut chars) {
                        Something(op) => opstream.add(op),
                        EOF => break,
                        _ => {}
                    }
                }

                opstream.optimize();

                State::new().run(opstream.get());
            }
            Err(e) => {
                writeln!(&mut io::stderr(), "Error while processing {}: {}", filename, e).unwrap();
            }
        }
    }
}

fn parse<T: io::Read>(mut chars: &mut std::iter::Peekable<io::Chars<T>>) -> ParserResult {
    match chars.next() {
        Some(Ok(c)) => match c {
            '+' => Something(Add(0x01)),
            '-' => Something(Add(0xff)),
            '<' => Something(Mov(-1)),
            '>' => Something(Mov(1)),
            ',' => Something(In),
            '.' => Something(Out),
            '[' => {
                let mut childstream = OpStream::new();
                while match chars.peek() {
                    Some(&Ok(c)) => c != ']',
                    _ => false
                } {
                    match parse(&mut chars) {
                        Something(op) => childstream.add(op),
                        Nothing => {},
                        EOF => panic!()
                    }
                }
                chars.next();
                Something(Loop(childstream))
            },
            _ => Nothing  // other characters
        },
        _ => EOF  // XXX: really?
    }
}

fn reader(path: &Path) -> Result<io::BufReader<fs::File>, io::Error> {
    Ok(io::BufReader::new(try!(fs::File::open(path))))
}

#[cfg(test)]
mod tests {
    use std::io;
    use std::io::Read;
    use super::{parse, ParserResult, State, OpStream};
    use super::Op::*;

    macro_rules! assert_let(
        ($test:pat, $value:expr, $then:block) => (
            if let $test = $value {
                $then
            } else {
                panic!("{:?} doesn't match {}", $value, stringify!($test))
            }
        );
        ($test:pat, $value:expr) => (
            assert_let!($test, $value, {})
        );
    );

    #[test]
    fn test_state_index() {
        let mut state = State { index: 0, memory: vec![23, 0, 0, 0, 0, 42] };
        assert_eq!(23, state[0]);
        state.index = 5;
        assert_eq!(42, state[0]);
    }

    #[test]
    fn test_state_index_mut() {
        let mut state = State::new();
        state[0] = 23;
        assert_eq!(23, state.memory[state.index]);
        state.index = 5;
        state[0] = 42;
        assert_eq!(42, state.memory[state.index]);
    }

    #[test]
    fn test_state_step_add() {
        let mut state = State::new();
        state.step(&Add(23));
        assert_eq!(23, state[0]);
        state.step(&Add(42));
        assert_eq!(65, state[0]);
        state.step(&Add(190));
        assert_eq!(255, state[0]);
    }

    #[test]
    fn test_state_step_mov() {
        let mut state = State::new();
        state.step(&Mov(1));
        assert_eq!(1, state.index);
        state.step(&Mov(42));
        assert_eq!(43, state.index);
        state.step(&Mov(-1));
        assert_eq!(42, state.index);
    }

    #[test]
    fn test_state_step_loop() {
        let mut state = State::new();
        state[0] = 23;
        state.step(&Loop(OpStream { ops: vec![Add(1)] }));
        assert_eq!(0, state[0]);
    }

    #[test]
    fn test_opstream_new_empty() {
        let opstream = OpStream::new();
        assert_eq!(0, opstream.ops.len());
    }

    #[test]
    fn test_opstream_optimize() {
        let mut opstream = OpStream::new();
        opstream.add(Mov(1));
        opstream.add(Mov(1));
        opstream.add(Add(0x01));
        opstream.add(Add(0xff));
        opstream.add(Add(0xff));
        opstream.add(Mov(1));
        opstream.add(Mov(-1));
        let mut opstream2 = OpStream::new();
        opstream2.add(Mov(2));
        opstream2.add(Mov(3));
        opstream.add(Loop(opstream2));
        opstream.optimize();

        assert_let!([Mov(2), Add(0xff), Loop(ref s2)], &opstream.ops[..], {
            assert_let!([Mov(5)], &s2.ops[..]);
        });
    }

    #[test]
    fn test_parse() {
        let input = b"+-[+.,]+";
        let mut chars = io::BufReader::new(&input[..]).chars().peekable();
        assert_let!(ParserResult::Something(Add(0x01)), parse(&mut chars));
        assert_let!(ParserResult::Something(Add(0xff)), parse(&mut chars));
        assert_let!(ParserResult::Something(Loop(loop_op)), parse(&mut chars), {
            assert_let!([Add(1), Out, In], &loop_op.ops[..]);
        });
        assert_let!(ParserResult::Something(Add(1)), parse(&mut chars));
    }
}
<commit_msg>feat: generalize existing optimizations<commit_after>#![feature(collections, io, slice_patterns)]
use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::io::{Read, Write};
use std::path::Path;

#[derive(Debug)]
enum ParserResult {
    Something(Op),
    Nothing,
    EOF
}

use ParserResult::*;

#[derive(Debug)]
enum Op {
    Add(u8),
    Mov(isize),
    In,
    Out,
    Loop(OpStream),

    // extra optimized ops
    Transfer(u8, Vec<(isize, u8)>)
}

use Op::*;

#[derive(Debug)]
struct OpStream {
    ops: Vec<Op>
}

impl OpStream {
    fn add(&mut self, op: Op) {
        self.ops.push(op);
    }

    fn optimize(&mut self) {
        let mut i = 0;
        while i < self.ops.len() {
            match &self.ops[i..] {
                [Add(a), Add(b), ..] => {
                    self.ops[i] = Add(a.wrapping_add(b));
                    self.ops.remove(i + 1);
                }
                [Mov(a), Mov(b), ..] => {
                    self.ops[i] = Mov(a + b);
                    self.ops.remove(i + 1);
                }
                [Add(0), ..] | [Mov(0), ..] => {
                    self.ops.remove(i);
                }
                [Loop(_), ..] => {
                    let mut maybe_new_op = None;

                    if let &mut Loop(ref mut stream) = &mut self.ops[i] {
                        stream.optimize();
                        maybe_new_op = stream.find_alternative();
                    }

                    if let Some(new_op) = maybe_new_op {
                        self.ops[i] = new_op;
                    }

                    i += 1
                }
                _ => i += 1
            }
        }
    }

    fn find_alternative(&self) -> Option<Op> {
        let mut map: BTreeMap<isize, u8> = BTreeMap::new();
        let mut rel_index = 0;

        for op in &self.ops[..] {
            match op {
                &Add(x) => {
                    let new_val = map.get(&rel_index).map_or(0, |v| *v).wrapping_add(x);
                    map.insert(rel_index, new_val);
                }
                &Mov(x) => {
                    rel_index += x;
                }
                _ => {
                    return None;
                }
            }
        }

        if rel_index != 0 {
            return None;
        }

        match map.remove(&0) {
            Some(summand) => Some(Transfer(summand, map.into_iter().collect())),
            None => None
        }
    }

    fn get(&self) -> &[Op] {
        &self.ops[..]
    }

    fn new() -> OpStream {
        OpStream { ops: Vec::new() }
    }
}

static ZERO: u8 = 0;

struct State {
    index: usize,
    memory: Vec<u8>
}

impl State {
    fn new() -> State {
        State { index: 0, memory: vec![] }
    }

    fn rel_index(&self, relative: isize) -> usize {
        (self.index as isize + relative) as usize
    }

    fn step(&mut self, op: &Op) -> bool {
        match op {
            &Add(i) => {
                self[0] = self[0].wrapping_add(i);
            }
            &Mov(n) => {
                self.index = self.rel_index(n);
            }
            &In => {
                let mut c = vec![0u8];
                if io::stdin().read(&mut c).unwrap() == 0 {
                    return false;
                }
                self[0] = c[0];
            }
            &Out => {
                io::stdout().write(&vec![self[0]]).unwrap();
            }
            &Loop(ref ops) => {
                while self[0] != 0 {
                    if !self.run(ops.get()) {
                        return false;
                    }
                }
            }
            &Transfer(d, ref map) => {
                let n = match (self[0], d) {
                    (0, _) => 0,
                    (x, 0xff) => x,
                    (x, 0x01) => 0u8.wrapping_sub(x),
                    (x, _) => {
                        let mut _v = x;
                        let mut _n = 0;
                        while _v != 0 {
                            _v = _v.wrapping_add(d);
                            _n += 1
                        }
                        _n
                    }
                };

                if n > 0 {
                    self[0] = 0;
                    for &(k, v) in &map[..] {
                        self[k] = self[k].wrapping_add(v.wrapping_mul(n));
                    }
                }
            }
        }
        return true;
    }

    fn run(&mut self, ops: &[Op]) -> bool {
        for op in ops {
            if !self.step(op) {
                return false;
            }
        }
        return true;
    }
}

impl std::ops::Index<isize> for State {
    type Output = u8;
    fn index(&self, _index: isize) -> &u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            &ZERO
        } else {
            &self.memory[idx]
        }
    }
}

impl std::ops::IndexMut<isize> for State {
    fn index_mut(&mut self, _index: isize) -> &mut u8 {
        let idx = self.rel_index(_index);
        if idx >= self.memory.len() {
            self.memory.resize(idx * 2 + 1, 0);
        }
        &mut self.memory[idx]
    }
}

fn main() {
    let filenames: Vec<String> = std::iter::FromIterator::from_iter(std::env::args());

    for filename in &filenames[1..] {
        match reader(&Path::new(filename)) {
            Ok(reader) => {
                let mut chars = reader.chars().peekable();

                let mut opstream = OpStream::new();
                loop {
                    match parse(&mut chars) {
                        Something(op) => opstream.add(op),
                        EOF => break,
                        _ => {}
                    }
                }

                opstream.optimize();

                State::new().run(opstream.get());
            }
            Err(e) => {
                writeln!(&mut io::stderr(), "Error while processing {}: {}", filename, e).unwrap();
            }
        }
    }
}

fn parse<T: io::Read>(mut chars: &mut std::iter::Peekable<io::Chars<T>>) -> ParserResult {
    match chars.next() {
        Some(Ok(c)) => match c {
            '+' => Something(Add(0x01)),
            '-' => Something(Add(0xff)),
            '<' => Something(Mov(-1)),
            '>' => Something(Mov(1)),
            ',' => Something(In),
            '.' => Something(Out),
            '[' => {
                let mut childstream = OpStream::new();
                while match chars.peek() {
                    Some(&Ok(c)) => c != ']',
                    _ => false
                } {
                    match parse(&mut chars) {
                        Something(op) => childstream.add(op),
                        Nothing => {},
                        EOF => panic!()
                    }
                }
                chars.next();
                Something(Loop(childstream))
            },
            _ => Nothing  // other characters
        },
        _ => EOF  // XXX: really?
    }
}

fn reader(path: &Path) -> Result<io::BufReader<fs::File>, io::Error> {
    Ok(io::BufReader::new(try!(fs::File::open(path))))
}

#[cfg(test)]
mod tests {
    use std::io;
    use std::io::Read;
    use super::{parse, ParserResult, State, OpStream};
    use super::Op::*;

    macro_rules! assert_let(
        ($test:pat, $value:expr, $then:block) => (
            if let $test = $value {
                $then
            } else {
                panic!("{:?} doesn't match {}", $value, stringify!($test))
            }
        );
        ($test:pat, $value:expr) => (
            assert_let!($test, $value, {})
        );
    );

    #[test]
    fn test_state_index() {
        let mut state = State { index: 0, memory: vec![23, 0, 0, 0, 0, 42] };
        assert_eq!(23, state[0]);
        state.index = 5;
        assert_eq!(42, state[0]);
    }

    #[test]
    fn test_state_index_mut() {
        let mut state = State::new();
        state[0] = 23;
        assert_eq!(23, state.memory[state.index]);
        state.index = 5;
        state[0] = 42;
        assert_eq!(42, state.memory[state.index]);
    }

    #[test]
    fn test_state_step_add() {
        let mut state = State::new();
        state.step(&Add(23));
        assert_eq!(23, state[0]);
        state.step(&Add(42));
        assert_eq!(65, state[0]);
        state.step(&Add(190));
        assert_eq!(255, state[0]);
    }

    #[test]
    fn test_state_step_mov() {
        let mut state = State::new();
        state.step(&Mov(1));
        assert_eq!(1, state.index);
        state.step(&Mov(42));
        assert_eq!(43, state.index);
        state.step(&Mov(-1));
        assert_eq!(42, state.index);
    }

    #[test]
    fn test_state_step_loop() {
        let mut state = State::new();
        state[0] = 23;
        state.step(&Loop(OpStream { ops: vec![Add(1)] }));
        assert_eq!(0, state[0]);
    }

    #[test]
    fn test_opstream_new_empty() {
        let opstream = OpStream::new();
        assert_eq!(0, opstream.ops.len());
    }

    #[test]
    fn test_opstream_optimize() {
        let mut opstream = OpStream::new();
        opstream.add(Mov(1));
        opstream.add(Mov(1));
        opstream.add(Add(0x01));
        opstream.add(Add(0xff));
        opstream.add(Add(0xff));
        opstream.add(Mov(1));
        opstream.add(Mov(-1));
        let mut opstream2 = OpStream::new();
        opstream2.add(Mov(2));
        opstream2.add(Mov(3));
        opstream.add(Loop(opstream2));
        opstream.optimize();

        assert_let!([Mov(2), Add(0xff), Loop(ref s2)], &opstream.ops[..], {
            assert_let!([Mov(5)], &s2.ops[..]);
        });
    }

    #[test]
    fn test_parse() {
        let input = b"+-[+.,]+";
        let mut chars = io::BufReader::new(&input[..]).chars().peekable();
        assert_let!(ParserResult::Something(Add(0x01)), parse(&mut chars));
        assert_let!(ParserResult::Something(Add(0xff)), parse(&mut chars));
        assert_let!(ParserResult::Something(Loop(loop_op)), parse(&mut chars), {
            assert_let!([Add(1), Out, In], &loop_op.ops[..]);
        });
        assert_let!(ParserResult::Something(Add(1)), parse(&mut chars));
    }
}
<|endoftext|>