<commit_before>//! # Aligned media and text format for language-learning software
//!
//! This is a Rust implementation of the [aligned media specification][spec]
//! for language-learning software.
//!
//! [spec]: https://github.com/language-learners/aligned-media-spec

#[warn(missing_docs)]

#[macro_use]
extern crate failure;
extern crate isolang;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::Error as DeError;
use std::collections::HashMap;
use std::result;

/// Errors which can be returned by this crate.
#[derive(Debug, Fail)]
pub enum Error {
    /// We could not parse the input data.
    #[fail(display = "could not parse metadata")]
    CouldNotParseMetadata {
        #[fail(cause)]
        err: serde_json::Error,
    },

    /// We encountered an invalid path.
    #[fail(display = "path {:?} is not allowed", path)]
    InvalidPath {
        path: String,
    },

    /// We encountered an invalid span.
    #[fail(display = "beginning of time span {},{} is greater than end", begin, end)]
    InvalidSpan {
        /// The beginning of the invalid span.
        begin: f32,
        /// The end of the invalid span.
        end: f32,
    },

    /// We encountered an unknown track type that didn't begin with "x-".
    #[fail(display = "unsupported track type {:?} (did you want to prefix it with \"x-\"?)", value)]
    UnsupportedTrackType {
        /// The unknown track type.
        value: String,
    },
}

/// Type alias for results returned by our crate.
pub type Result<T> = result::Result<T, Error>;

/// A single media file, typically an episode of a TV series, a film, an chapter
/// of an audiobook. It might also be something more exotic, like a PDF of a
/// graphic novel.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
    /// The primary media track for this `MediaFile`. This is used as the "time
    /// base" for all `Alignment`s. This may be omitted if no timed media is
    /// available, as would be in the case of two texts aligned against each
    /// other.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_track: Option<Track>,

    /// Optional other tracks associated with this file.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tracks: Vec<Track>,

    /// A list of synchronized sentences, subtitles, or other linguistic
    /// content.
    pub alignments: Vec<Alignment>,

    /// Application-specific extension data.
    #[serde(default, skip_serializing_if = "ExtensionData::is_empty")]
    pub ext: ExtensionData,

    /// Placeholder to allow for future extensibility without breaking the API.
    #[serde(default, skip_serializing)]
    _placeholder: (),
}

impl Metadata {
    /// Parse `metadata.json` represented as raw bytes. This will be interpreted
    /// as UTF-8, because the format is strict.
    pub fn from_bytes(data: &[u8]) -> Result<Metadata> {
        serde_json::from_slice(data).map_err(|err| {
            Error::CouldNotParseMetadata { err }
        })
    }

    /// Parse `metadata.json` represented as a UTF-8 Rust string.
    pub fn from_str(data: &str) -> Result<Metadata> {
        Self::from_bytes(data.as_bytes())
    }
}

#[test]
fn parse_metadata() {
    let examples = &[
        include_str!("../fixtures/examples/book_example.aligned/metadata.json"),
        //include_str!("../fixtures/examples/subtitle_example.aligned/metadata.json"),
        //include_str!("../fixtures/examples/subtitle_extracted_example.aligned/metadata.json"),
    ];
    for example in examples {
        Metadata::from_str(example)
            .expect("failed to parse example metadata");
    }
}

/// An individual "track" of context. This might be a single subtitle in a
/// single language, or a still image taken from a video
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Track {
    // The kind of data stored in this track.
    #[serde(rename = "type")]
    pub track_type: TrackType,

    /// The language stored in this track, represented as a two-letter ISO 639-1
    /// code when possible, and a three-letter 639-3 code for languages not
    /// included in ISO 639-1. If this is omitted, then programs may assume that
    /// this track might be something like a still image from a video or an
    /// illustration, that provides context but contains no linguistic data.
    #[serde(default, skip_serializing_if = "Option::is_none", with = "iso_short_code_serialization::opt")]
    pub lang: Option<isolang::Language>,

    // The actual underlying file on disk, if any. Either this or `html` should
    // be present, but not both.
    file: Option<FilePath>,

    // TODO: Do we want a `fileSpan: Span` element, to select only a portion of
    // a media file?

    /// Textual context, which should be valid HTML 5, optionally with embedded
    /// tags like `<b>`, `<i>` and `<br>`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub html: Option<String>,

    /// Application-specific extension data.
    #[serde(default, skip_serializing_if = "ExtensionData::is_empty")]
    pub ext: ExtensionData,

    /// Placeholder to allow for future extensibility without breaking the API.
    #[serde(default, skip_serializing)]
    _placeholder: (),
}

impl Track {
    /// Create a new track with the specified type. This is pretty much useless
    /// unless you also set some additional fields manually.
    pub fn with_type(track_type: TrackType) -> Track {
        Track {
            track_type: track_type,
            lang: None,
            file: None,
            html: None,
            ext: ExtensionData::default(),
            _placeholder: (),
        }
    }

    /// Create a new HTML track with specified language and content.
    pub fn html<S: Into<String>>(lang: isolang::Language, html: S) -> Track {
        Track {
            track_type: TrackType::Html,
            lang: Some(lang),
            file: None,
            html: Some(html.into()),
            ext: ExtensionData::default(),
            _placeholder: (),
        }
    }
}

/// Different possible track types.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum TrackType {
    /// This track contains HTML data.
    Html,
    /// This track contains audio or video data.
    Media,
    /// This track contains an image.
    Image,
    /// This track contains a non-standard form of data. When serialized, it
    /// will be named starting with `"x-"`, followed by the `String` value.
    Ext(String),
}

impl<'de> Deserialize<'de> for TrackType {
    fn deserialize<D: Deserializer<'de>>(d: D) -> result::Result<Self, D::Error> {
        let value: &str = Deserialize::deserialize(d)?;
        match value {
            "html" => Ok(TrackType::Html),
            "media" => Ok(TrackType::Media),
            "image" => Ok(TrackType::Image),
            other if other.starts_with("x-") => {
                Ok(TrackType::Ext(other[2..].to_owned()))
            }
            other => {
                Err(D::Error::custom(Error::UnsupportedTrackType {
                    value: other.to_owned(),
                }))
            }
        }
    }
}

impl Serialize for TrackType {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            TrackType::Html => "html".serialize(serializer),
            TrackType::Media => "media".serialize(serializer),
            TrackType::Image => "image".serialize(serializer),
            TrackType::Ext(ref name) => {
                format!("x-{}", name).serialize(serializer)
            }
        }
    }
}

/// The smallest unit of alignment or synchronization. This might be a subtitle,
/// a sentence, or perhaps multiple sentences if that's the best the aligning
/// application can do.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Alignment {
    /// The time span associated with this alignment, relative to
    /// `MediaFile.baseTrack`. If `MediaFile.baseTrack` was not specified, this
    /// element must be omitted.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    span: Option<TimeSpan>,

    /// One or more representations of the `Alignment`. For example, subtitle
    /// text in one or more languages, or an image, or a short audio clip.
    ///
    /// Normally this does **not** include any version of the
    /// `MediaFile.baseTrack` track, because we can already use
    /// `MediaFile.baseTrack` and `Alignment.span` to figure out what portion
    /// of the base track corresponds to this alignment.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tracks: Vec<Track>,

    /// Application-specific extension data.
    #[serde(default, skip_serializing_if = "ExtensionData::is_empty")]
    pub ext: ExtensionData,

    /// Placeholder to allow for future extensibility without breaking the API.
    #[serde(default, skip_serializing)]
    _placeholder: (),
}

/// A span of time, measured in floating-point seconds.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TimeSpan {
    /// The starting time, in seconds (inclusive).
    begin: f32,
    /// The ending time, in seconds (exclusive).
    end: f32,
}

impl TimeSpan {
    /// Construct a new `TimeSpan`.
    pub fn new(begin: f32, end: f32) -> Result<TimeSpan> {
        if begin < end {
            Ok(TimeSpan { begin, end })
        } else {
            Err(Error::InvalidSpan { begin, end })
        }
    }

    /// The beginning of this time span.
    pub fn begin(&self) -> f32 {
        self.begin
    }

    /// The end of the this time span.
    pub fn end(&self) -> f32 {
        self.end
    }
}

impl<'de> Deserialize<'de> for TimeSpan {
    fn deserialize<D: Deserializer<'de>>(d: D) -> result::Result<Self, D::Error> {
        let (begin, end) = Deserialize::deserialize(d)?;
        TimeSpan::new(begin, end).map_err(D::Error::custom)
    }
}

impl Serialize for TimeSpan {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        (self.begin, self.end).serialize(serializer)
    }
}

/// A portable, relative path in Unix notation. These are highly restricted to
/// help prevent directory traversal attacks. Specifically, they:
///
/// - Must not begin with "/".
/// - Must not contain path segments with the values "", "." or "..".
/// - Must contain valid UTF-8 data.
///
/// These paths will be interpreted as relative to the `"file/"` sudirectory
/// located in the same directory as the `metadata.json` file.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct FilePath {
    path: String,
}

impl FilePath {
    /// Construct a relative path from a string, being sure to validate it.
    pub fn new<S: Into<String>>(path: S) -> Result<FilePath> {
        let path = path.into();
        for component in path.split("/") {
            if component == "" || component == "." || component == ".."
                || component.contains("\\")
            {
                return Err(Error::InvalidPath { path: path.clone() });
            }
        }
        Ok(FilePath { path })
    }
}

#[test]
fn file_path_checks_validity() {
    assert!(FilePath::new("good.txt").is_ok());
    assert!(FilePath::new("dir/good.txt").is_ok());

    assert!(FilePath::new("").is_err());
    assert!(FilePath::new(".").is_err());
    assert!(FilePath::new("..").is_err());
    assert!(FilePath::new("dir/..").is_err());
    assert!(FilePath::new("/absolute").is_err());
    assert!(FilePath::new("trailing/").is_err());
    assert!(FilePath::new("dir//file").is_err());
    assert!(FilePath::new("back\\slash").is_err());
}

impl<'de> Deserialize<'de> for FilePath {
    fn deserialize<D: Deserializer<'de>>(d: D) -> result::Result<Self, D::Error> {
        let path: String = Deserialize::deserialize(d)?;
        FilePath::new(path).map_err(D::Error::custom)
    }
}

impl Serialize for FilePath {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.path.serialize(serializer)
    }
}

/// Custom extension data which hasn't been standardized. All custom fields
/// **must** go in a block of this type, and should generally have names like
/// `myapp-attrname`, where `myapp` is the application that uses them. This is a
/// map with string keys and arbitrary JSON values.
pub type ExtensionData = HashMap<String, serde_json::Value>;

/// Routines for serialzing and deserialzing optional ISO 639-1 and 639-3 codes,
/// represented as `isolang::Language`. These are intended for use with
/// `serde_derive`'s `with =` argument.
pub mod iso_short_code_serialization {
    /// Serialize and deserialize `Option<isolang::Language>`.
    pub mod opt {
        use isolang::Language;
        use serde::{Deserialize, Deserializer, Serialize, Serializer};
        use serde::de::Error;
        use std::result::Result;

        // Deserialize an ISO 639-1 or 639-3 code.
        pub fn deserialize<'de, D>(d: D) -> Result<Option<Language>, D::Error>
        where
            D: Deserializer<'de>,
        {
            // We need to do this manually because of
            // https://github.com/humenda/isolang-rs/issues/7
            let code: Option<&str> = Deserialize::deserialize(d)?;
            match code {
                None => Ok(None),
                Some(c) => {
                    let lang = Language::from_639_3(c)
                        .or_else(|| Language::from_639_1(c))
                        .ok_or_else(|| {
                            D::Error::unknown_variant(
                                c,
                                &["an ISO 639-1 or 639-3 language code"],
                            )
                        })?;
                    Ok(Some(lang))
                }
            }
        }

        /// Serialize an `isolang::Language` as a 2-letter code if possible, or
        /// a 3-letter code otherwise.
        pub fn serialize<S>(
            lang: &Option<Language>,
            serializer: S,
        ) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let code = lang.map(|l| l.to_639_1().unwrap_or_else(|| l.to_639_3()));
            code.serialize(serializer)
        }
    }
}

<commit_msg>aligned_media: Return more use JSON parse errors<commit_after>//! # Aligned media and text format for language-learning software
//!
//! This is a Rust implementation of the [aligned media specification][spec]
//! for language-learning software.
//!
//! [spec]: https://github.com/language-learners/aligned-media-spec

#[warn(missing_docs)]

#[macro_use]
extern crate failure;
extern crate isolang;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use failure::ResultExt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::Error as DeError;
use std::collections::HashMap;
use std::result;

/// Errors which can be returned by this crate.
#[derive(Debug, Fail)]
pub enum Error {
    /// We could not parse the input data.
    #[fail(display = "could not parse metadata")]
    CouldNotParseMetadata,

    /// We encountered an invalid path.
    #[fail(display = "path {:?} is not allowed", path)]
    InvalidPath {
        path: String,
    },

    /// We encountered an invalid span.
    #[fail(display = "beginning of time span {},{} is greater than end", begin, end)]
    InvalidSpan {
        /// The beginning of the invalid span.
        begin: f32,
        /// The end of the invalid span.
        end: f32,
    },

    /// We encountered an unknown track type that didn't begin with "x-".
    #[fail(display = "unsupported track type {:?} (did you want to prefix it with \"x-\"?)", value)]
    UnsupportedTrackType {
        /// The unknown track type.
        value: String,
    },
}

/// Type alias for results returned by our crate.
pub type Result<T> = result::Result<T, Error>;

/// A single media file, typically an episode of a TV series, a film, an chapter
/// of an audiobook. It might also be something more exotic, like a PDF of a
/// graphic novel.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
    /// The primary media track for this `MediaFile`. This is used as the "time
    /// base" for all `Alignment`s. This may be omitted if no timed media is
    /// available, as would be in the case of two texts aligned against each
    /// other.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_track: Option<Track>,

    /// Optional other tracks associated with this file.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tracks: Vec<Track>,

    /// A list of synchronized sentences, subtitles, or other linguistic
    /// content.
    pub alignments: Vec<Alignment>,

    /// Application-specific extension data.
    #[serde(default, skip_serializing_if = "ExtensionData::is_empty")]
    pub ext: ExtensionData,

    /// Placeholder to allow for future extensibility without breaking the API.
    #[serde(default, skip_serializing)]
    _placeholder: (),
}

impl Metadata {
    /// Parse `metadata.json` represented as raw bytes. This will be interpreted
    /// as UTF-8, because the format is strict.
    pub fn from_bytes(data: &[u8]) -> result::Result<Metadata, failure::Error> {
        Ok(serde_json::from_slice(data).context(Error::CouldNotParseMetadata)?)
    }

    /// Parse `metadata.json` represented as a UTF-8 Rust string.
    pub fn from_str(data: &str) -> result::Result<Metadata, failure::Error> {
        Self::from_bytes(data.as_bytes())
    }
}

#[test]
fn parse_metadata() {
    let examples = &[
        include_str!("../fixtures/examples/book_example.aligned/metadata.json"),
        include_str!("../fixtures/examples/subtitle_example.aligned/metadata.json"),
        include_str!("../fixtures/examples/subtitle_extracted_example.aligned/metadata.json"),
    ];
    for example in examples {
        Metadata::from_str(example)
            .expect("failed to parse example metadata");
    }
}

/// An individual "track" of context. This might be a single subtitle in a
/// single language, or a still image taken from a video
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Track {
    // The kind of data stored in this track.
    #[serde(rename = "type")]
    pub track_type: TrackType,

    /// The language stored in this track, represented as a two-letter ISO 639-1
    /// code when possible, and a three-letter 639-3 code for languages not
    /// included in ISO 639-1. If this is omitted, then programs may assume that
    /// this track might be something like a still image from a video or an
    /// illustration, that provides context but contains no linguistic data.
    #[serde(default, skip_serializing_if = "Option::is_none", with = "iso_short_code_serialization::opt")]
    pub lang: Option<isolang::Language>,

    // The actual underlying file on disk, if any. Either this or `html` should
    // be present, but not both.
    file: Option<FilePath>,

    // TODO: Do we want a `fileSpan: Span` element, to select only a portion of
    // a media file?

    /// Textual context, which should be valid HTML 5, optionally with embedded
    /// tags like `<b>`, `<i>` and `<br>`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub html: Option<String>,

    /// Application-specific extension data.
    #[serde(default, skip_serializing_if = "ExtensionData::is_empty")]
    pub ext: ExtensionData,

    /// Placeholder to allow for future extensibility without breaking the API.
    #[serde(default, skip_serializing)]
    _placeholder: (),
}

impl Track {
    /// Create a new track with the specified type. This is pretty much useless
    /// unless you also set some additional fields manually.
    pub fn with_type(track_type: TrackType) -> Track {
        Track {
            track_type: track_type,
            lang: None,
            file: None,
            html: None,
            ext: ExtensionData::default(),
            _placeholder: (),
        }
    }

    /// Create a new HTML track with specified language and content.
    pub fn html<S: Into<String>>(lang: isolang::Language, html: S) -> Track {
        Track {
            track_type: TrackType::Html,
            lang: Some(lang),
            file: None,
            html: Some(html.into()),
            ext: ExtensionData::default(),
            _placeholder: (),
        }
    }
}

/// Different possible track types.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum TrackType {
    /// This track contains HTML data.
    Html,
    /// This track contains audio or video data.
    Media,
    /// This track contains an image.
    Image,
    /// This track contains a non-standard form of data. When serialized, it
    /// will be named starting with `"x-"`, followed by the `String` value.
    Ext(String),
}

impl<'de> Deserialize<'de> for TrackType {
    fn deserialize<D: Deserializer<'de>>(d: D) -> result::Result<Self, D::Error> {
        let value: &str = Deserialize::deserialize(d)?;
        match value {
            "html" => Ok(TrackType::Html),
            "media" => Ok(TrackType::Media),
            "image" => Ok(TrackType::Image),
            other if other.starts_with("x-") => {
                Ok(TrackType::Ext(other[2..].to_owned()))
            }
            other => {
                Err(D::Error::custom(Error::UnsupportedTrackType {
                    value: other.to_owned(),
                }))
            }
        }
    }
}

impl Serialize for TrackType {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            TrackType::Html => "html".serialize(serializer),
            TrackType::Media => "media".serialize(serializer),
            TrackType::Image => "image".serialize(serializer),
            TrackType::Ext(ref name) => {
                format!("x-{}", name).serialize(serializer)
            }
        }
    }
}

/// The smallest unit of alignment or synchronization. This might be a subtitle,
/// a sentence, or perhaps multiple sentences if that's the best the aligning
/// application can do.
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Alignment {
    /// The time span associated with this alignment, relative to
    /// `MediaFile.baseTrack`. If `MediaFile.baseTrack` was not specified, this
    /// element must be omitted.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    span: Option<TimeSpan>,

    /// One or more representations of the `Alignment`. For example, subtitle
    /// text in one or more languages, or an image, or a short audio clip.
    ///
    /// Normally this does **not** include any version of the
    /// `MediaFile.baseTrack` track, because we can already use
    /// `MediaFile.baseTrack` and `Alignment.span` to figure out what portion
    /// of the base track corresponds to this alignment.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub tracks: Vec<Track>,

    /// Application-specific extension data.
    #[serde(default, skip_serializing_if = "ExtensionData::is_empty")]
    pub ext: ExtensionData,

    /// Placeholder to allow for future extensibility without breaking the API.
    #[serde(default, skip_serializing)]
    _placeholder: (),
}

/// A span of time, measured in floating-point seconds.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TimeSpan {
    /// The starting time, in seconds (inclusive).
    begin: f32,
    /// The ending time, in seconds (exclusive).
    end: f32,
}

impl TimeSpan {
    /// Construct a new `TimeSpan`.
    pub fn new(begin: f32, end: f32) -> Result<TimeSpan> {
        if begin < end {
            Ok(TimeSpan { begin, end })
        } else {
            Err(Error::InvalidSpan { begin, end })
        }
    }

    /// The beginning of this time span.
    pub fn begin(&self) -> f32 {
        self.begin
    }

    /// The end of the this time span.
    pub fn end(&self) -> f32 {
        self.end
    }
}

impl<'de> Deserialize<'de> for TimeSpan {
    fn deserialize<D: Deserializer<'de>>(d: D) -> result::Result<Self, D::Error> {
        let (begin, end) = Deserialize::deserialize(d)?;
        TimeSpan::new(begin, end).map_err(D::Error::custom)
    }
}

impl Serialize for TimeSpan {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        (self.begin, self.end).serialize(serializer)
    }
}

/// A portable, relative path in Unix notation. These are highly restricted to
/// help prevent directory traversal attacks. Specifically, they:
///
/// - Must not begin with "/".
/// - Must not contain path segments with the values "", "." or "..".
/// - Must contain valid UTF-8 data.
///
/// These paths will be interpreted as relative to the `"file/"` sudirectory
/// located in the same directory as the `metadata.json` file.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct FilePath {
    path: String,
}

impl FilePath {
    /// Construct a relative path from a string, being sure to validate it.
    pub fn new<S: Into<String>>(path: S) -> Result<FilePath> {
        let path = path.into();
        for component in path.split("/") {
            if component == "" || component == "." || component == ".."
                || component.contains("\\")
            {
                return Err(Error::InvalidPath { path: path.clone() });
            }
        }
        Ok(FilePath { path })
    }
}

#[test]
fn file_path_checks_validity() {
    assert!(FilePath::new("good.txt").is_ok());
    assert!(FilePath::new("dir/good.txt").is_ok());

    assert!(FilePath::new("").is_err());
    assert!(FilePath::new(".").is_err());
    assert!(FilePath::new("..").is_err());
    assert!(FilePath::new("dir/..").is_err());
    assert!(FilePath::new("/absolute").is_err());
    assert!(FilePath::new("trailing/").is_err());
    assert!(FilePath::new("dir//file").is_err());
    assert!(FilePath::new("back\\slash").is_err());
}

impl<'de> Deserialize<'de> for FilePath {
    fn deserialize<D: Deserializer<'de>>(d: D) -> result::Result<Self, D::Error> {
        let path: String = Deserialize::deserialize(d)?;
        FilePath::new(path).map_err(D::Error::custom)
    }
}

impl Serialize for FilePath {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.path.serialize(serializer)
    }
}

/// Custom extension data which hasn't been standardized. All custom fields
/// **must** go in a block of this type, and should generally have names like
/// `myapp-attrname`, where `myapp` is the application that uses them. This is a
/// map with string keys and arbitrary JSON values.
pub type ExtensionData = HashMap<String, serde_json::Value>;

/// Routines for serialzing and deserialzing optional ISO 639-1 and 639-3 codes,
/// represented as `isolang::Language`. These are intended for use with
/// `serde_derive`'s `with =` argument.
pub mod iso_short_code_serialization {
    /// Serialize and deserialize `Option<isolang::Language>`.
    pub mod opt {
        use isolang::Language;
        use serde::{Deserialize, Deserializer, Serialize, Serializer};
        use serde::de::Error;
        use std::result::Result;

        // Deserialize an ISO 639-1 or 639-3 code.
        pub fn deserialize<'de, D>(d: D) -> Result<Option<Language>, D::Error>
        where
            D: Deserializer<'de>,
        {
            // We need to do this manually because of
            // https://github.com/humenda/isolang-rs/issues/7
            let code: Option<&str> = Deserialize::deserialize(d)?;
            match code {
                None => Ok(None),
                Some(c) => {
                    let lang = Language::from_639_3(c)
                        .or_else(|| Language::from_639_1(c))
                        .ok_or_else(|| {
                            D::Error::unknown_variant(
                                c,
                                &["an ISO 639-1 or 639-3 language code"],
                            )
                        })?;
                    Ok(Some(lang))
                }
            }
        }

        /// Serialize an `isolang::Language` as a 2-letter code if possible, or
        /// a 3-letter code otherwise.
        pub fn serialize<S>(
            lang: &Option<Language>,
            serializer: S,
        ) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let code = lang.map(|l| l.to_639_1().unwrap_or_else(|| l.to_639_3()));
            code.serialize(serializer)
        }
    }
}

<|endoftext|>


<commit_before>extern crate eve;

use eve::map::*;

#[allow(dead_code)]
fn main() {
    let mut map = Map::new();
    for i in (0..10) {
        map.insert(i, 1);
        println!("{:?}", map);
    }
}<commit_msg>Really dumb benchmark<commit_after>#![feature(test)]

extern crate test;
extern crate eve;

use test::Bencher;
use std::collections::BTreeMap;

use eve::map::*;

#[bench]
fn bench_insert_map(b: &mut Bencher) {
    b.iter(|| {
        let mut map = Map::new();
        for i in (0..1000000) {
            map.insert(i, 1);
        }
    });
}

#[bench]
fn bench_insert_btree(b: &mut Bencher) {
    b.iter(|| {
        let mut map = BTreeMap::new();
        for i in (0..1000000) {
            map.insert(i, 1);
        }
    });
}

#[allow(dead_code)]
fn main() {
    let mut map = Map::new();
    for i in (0..10) {
        map.insert(i, 1);
        println!("{:?}", map);
    }
}<|endoftext|>


<commit_before>use ::Service;
use botocore::{Member, Operation, Shape, ShapeType};
use inflector::Inflector;
use regex::{Captures, Regex};
use super::generate_field_name;

// Add request headers for any shape members marked as headers
pub fn generate_headers(service: &Service, operation: &Operation) -> Option<String> {
    if operation.input.is_none() {
        return None;
    }

    let shape = service.get_shape(&operation.input.as_ref().unwrap().shape).unwrap();

    Some(shape.members
        .as_ref()
        .unwrap()
        .iter()
        .filter_map(|(member_name, member)| {
            if member.location.is_none() {
                return None;
            }
            match &member.location.as_ref().unwrap()[..] {
                "header" => {
                    if shape.required(member_name) {
                        Some(format!("request.add_header(\"{location_name}\",
                                      &input.{field_name});",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    } else {
                        Some(format!("
                        if let Some(ref {field_name}) =
                                      input.{field_name} {{
                                      request.add_header(\"{location_name}\",
                                      &{field_name}.to_string());
                        }}",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    }
                },
                "headers" => {
                    if shape.required(member_name) {
                        Some(format!("for (header_name, header_value) in input.{field_name}.iter() {{
                                              let header = format!(\"{location_name}{{}}\", header_name);
                                              request.add_header(header,
                                                                 header_value);
                                          }}
                                      ",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    } else {
                        Some(format!("
                        if let Some(ref {field_name}) =
                                      input.{field_name} {{
                                          for (header_name, header_value) in {field_name}.iter() {{
                                              let header = format!(\"{location_name}{{}}\", header_name);
                                              request.add_header(header,
                                                                 header_value);
                                          }}

                        }}",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    }
                }
                _ => None,
            }
        })
        .collect::<Vec<String>>()
        .join("\n"))
}

pub fn generate_params_loading_string(service: &Service, operation: &Operation) -> Option<String> {
    if operation.input.is_none() {
        return None;
    }

    let input_type = operation.input_shape();
    let input_shape = service.get_shape(input_type).unwrap();

    // Construct a list of strings which will be used to load request
    // parameters from the input struct into a `Params` vec, which will
    // then be added to the request.
    let mut param_strings = generate_shape_member_param_strings(service, input_shape);
    param_strings.append(&mut generate_static_param_strings(operation));

    let load_params = match param_strings.len() {
        0 => "".to_owned(),
        _ => {
            format!("let mut params = Params::new();
                {param_strings}
                request.set_params(params);",
                    param_strings = param_strings.join("\n"))
        }
    };

    Some(load_params)
}

pub fn generate_uri_formatter(request_uri: &str,
                              service: &Service,
                              operation: &Operation)
                              -> Option<String> {
    if operation.input.is_some() {
        let input_shape = &service.get_shape(operation.input_shape()).unwrap();
        let uri_strings = generate_shape_member_uri_strings(input_shape);

        if !uri_strings.is_empty() {
            return Some(format!("let request_uri = format!(\"{request_uri}\", {uri_strings});",
                                request_uri = generate_snake_case_uri(request_uri),
                                uri_strings = uri_strings.join(", ")));

        }
    }

    Some(format!("let request_uri = \"{request_uri}\";",
                 request_uri = request_uri))
}

fn generate_shape_member_uri_strings(shape: &Shape) -> Vec<String> {
    shape.members
        .as_ref()
        .unwrap()
        .iter()
        .filter_map(|(member_name, member)| {
            generate_member_format_string(&generate_field_name(member_name), member)
        })
        .collect::<Vec<String>>()
}

fn generate_member_format_string(member_name: &str, member: &Member) -> Option<String> {
    match member.location {
        Some(ref x) if x == "uri" => {
            match member.location_name {
                Some(ref loc_name) => {
                    Some(format!(
                        "{member_name} = input.{field_name}",
                        field_name = member_name,
                        member_name = loc_name.to_snake_case(),
                    ))
                }
                None => {
                    Some(format!(
                        "{member_name} = input.{field_name}",
                        field_name = member_name,
                        member_name = member_name.to_snake_case(),
                    ))
                }
            }
        }
        Some(_) | None => None,
    }
}

fn generate_static_param_strings(operation: &Operation) -> Vec<String> {

    // botocore includes + for greedy parameters and we don't care about it
    let (_, maybe_params) = parse_query_string(&operation.http.request_uri);

    let mut static_params = vec![];

    // Sometimes the key has the query param already set, like "list-type=2"
    // These need to get parsed out of the URI and added to the params map
    // with the other "queryparam" members
    if let Some(ref key) = maybe_params {
        match key.find('=') {
            Some(_) => {
                let key_val_vec: Vec<&str> = key.split('=').collect();
                static_params.push(format!("params.put(\"{}\", \"{}\");",
                                           key_val_vec[0],
                                           key_val_vec[1]))
            }
            None => static_params.push(format!("params.put_key(\"{}\");", key)),
        }
    };

    static_params
}

fn generate_snake_case_uri(request_uri: &str) -> String {
    lazy_static! {
        static ref URI_ARGS_REGEX: Regex = Regex::new(r"\{([\w\d]+)\}").unwrap();
    }

    URI_ARGS_REGEX.replace_all(request_uri, |caps: &Captures| {
            format!("{{{}}}",
                    caps.get(1).map(|c| Inflector::to_snake_case(c.as_str())).unwrap())
        })
        .to_string()
}

fn generate_shape_member_param_strings(service: &Service, shape: &Shape) -> Vec<String> {
    shape.members
        .as_ref()
        .unwrap()
        .iter()
        .filter_map(|(member_name, member)| {
            member.location.as_ref().and_then(|loc| {
                if !member.deprecated() && loc == "querystring" {
                    let member_shape = service.shape_for_member(member).unwrap();
                    Some(generate_param_load_string(member_name,
                                                    member_shape,
                                                    member,
                                                    shape.required(member_name)))
                } else {
                    None
                }
            })
        })
        .collect::<Vec<String>>()
}

fn generate_param_load_string(member_name: &str,
                              member_shape: &Shape,
                              member: &Member,
                              is_required: bool)
                              -> String {
    let field_name = generate_field_name(member_name);
    let param_name = match member.location_name {
        Some(ref location) => location.to_owned(),
        None => member_name.to_owned(),
    };
    match (member_shape.shape_type, is_required) {
        (ShapeType::List, true) => {
            format!("for item in input.{field_name}.iter() {{
                     params.put(\"{param_name}\", item);
                }}",
                    param_name = param_name,
                    field_name = field_name)
        }
        (ShapeType::List, false) => {
            format!(
                "if let Some(ref x) = input.{field_name} {{
                    for item in x.iter() {{
                        params.put(\"{param_name}\", item);
                    }}
                }}",
                param_name = param_name,
                field_name = field_name,
            )
        }
        (ShapeType::Map, true) => {
            format!("for (key, val) in input.{field_name}.iter() {{
                     params.put(key, val);
                }}",
                    field_name = field_name)
        }
        (ShapeType::Map, false) => {
            format!(
                "if let Some(ref x) = input.{field_name} {{
                    for (key, val) in x.iter() {{
                        params.put(key, val);
                    }}
                }}",
                field_name = field_name,
            )
        }
        (_, true) => {
            format!("params.put(\"{param_name}\", &input.{field_name});",
                    param_name = param_name,
                    field_name = field_name)
        }
        (_, false) => {
            format!(
                "if let Some(ref x) = input.{field_name} {{
                    params.put(\"{param_name}\", x);
                }}",
                param_name = param_name,
                field_name = field_name,
            )
        }
    }
}

pub fn parse_query_string(uri: &str) -> (String, Option<String>) {
    // botocore query strings for S3 are variations on "/{Bucket}/{Key+}?foobar"
    // the query string needs to be split out and put in the params hash,
    // and the + isn't useful information for us
    let base_uri = uri.replace("+", "");
    let parts: Vec<&str> = base_uri.split('?').collect();

    match parts.len() {
        1 => (parts[0].to_owned(), None),
        2 => (parts[0].to_owned(), Some(parts[1].to_owned())),
        _ => panic!("Unknown uri structure {}", uri),
    }
}
<commit_msg>Fix URI regex for REST service codegen.<commit_after>use ::Service;
use botocore::{Member, Operation, Shape, ShapeType};
use inflector::Inflector;
use regex::Regex;
use super::generate_field_name;

// Add request headers for any shape members marked as headers
pub fn generate_headers(service: &Service, operation: &Operation) -> Option<String> {
    if operation.input.is_none() {
        return None;
    }

    let shape = service.get_shape(&operation.input.as_ref().unwrap().shape).unwrap();

    Some(shape.members
        .as_ref()
        .unwrap()
        .iter()
        .filter_map(|(member_name, member)| {
            if member.location.is_none() {
                return None;
            }
            match &member.location.as_ref().unwrap()[..] {
                "header" => {
                    if shape.required(member_name) {
                        Some(format!("request.add_header(\"{location_name}\",
                                      &input.{field_name});",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    } else {
                        Some(format!("
                        if let Some(ref {field_name}) =
                                      input.{field_name} {{
                                      request.add_header(\"{location_name}\",
                                      &{field_name}.to_string());
                        }}",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    }
                },
                "headers" => {
                    if shape.required(member_name) {
                        Some(format!("for (header_name, header_value) in input.{field_name}.iter() {{
                                              let header = format!(\"{location_name}{{}}\", header_name);
                                              request.add_header(header,
                                                                 header_value);
                                          }}
                                      ",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    } else {
                        Some(format!("
                        if let Some(ref {field_name}) =
                                      input.{field_name} {{
                                          for (header_name, header_value) in {field_name}.iter() {{
                                              let header = format!(\"{location_name}{{}}\", header_name);
                                              request.add_header(header,
                                                                 header_value);
                                          }}

                        }}",
                                     location_name = member.location_name.as_ref().unwrap(),
                                     field_name = generate_field_name(member_name)))
                    }
                }
                _ => None,
            }
        })
        .collect::<Vec<String>>()
        .join("\n"))
}

pub fn generate_params_loading_string(service: &Service, operation: &Operation) -> Option<String> {
    if operation.input.is_none() {
        return None;
    }

    let input_type = operation.input_shape();
    let input_shape = service.get_shape(input_type).unwrap();

    // Construct a list of strings which will be used to load request
    // parameters from the input struct into a `Params` vec, which will
    // then be added to the request.
    let mut param_strings = generate_shape_member_param_strings(service, input_shape);
    param_strings.append(&mut generate_static_param_strings(operation));

    let load_params = match param_strings.len() {
        0 => "".to_owned(),
        _ => {
            format!("let mut params = Params::new();
                {param_strings}
                request.set_params(params);",
                    param_strings = param_strings.join("\n"))
        }
    };

    Some(load_params)
}

pub fn generate_uri_formatter(request_uri: &str,
                              service: &Service,
                              operation: &Operation)
                              -> Option<String> {
    if operation.input.is_some() {
        let input_shape = &service.get_shape(operation.input_shape()).unwrap();
        let uri_strings = generate_shape_member_uri_strings(input_shape);

        if !uri_strings.is_empty() {
            return Some(format!("let request_uri = format!(\"{request_uri}\", {uri_strings});",
                                request_uri = generate_snake_case_uri(request_uri),
                                uri_strings = uri_strings.join(", ")));

        }
    }

    Some(format!("let request_uri = \"{request_uri}\";",
                 request_uri = request_uri))
}

fn generate_shape_member_uri_strings(shape: &Shape) -> Vec<String> {
    shape.members
        .as_ref()
        .unwrap()
        .iter()
        .filter_map(|(member_name, member)| {
            generate_member_format_string(&generate_field_name(member_name), member)
        })
        .collect::<Vec<String>>()
}

fn generate_member_format_string(member_name: &str, member: &Member) -> Option<String> {
    match member.location {
        Some(ref x) if x == "uri" => {
            match member.location_name {
                Some(ref loc_name) => {
                    Some(format!(
                        "{member_name} = input.{field_name}",
                        field_name = member_name,
                        member_name = loc_name.to_snake_case(),
                    ))
                }
                None => {
                    Some(format!(
                        "{member_name} = input.{field_name}",
                        field_name = member_name,
                        member_name = member_name.to_snake_case(),
                    ))
                }
            }
        }
        Some(_) | None => None,
    }
}

fn generate_static_param_strings(operation: &Operation) -> Vec<String> {

    // botocore includes + for greedy parameters and we don't care about it
    let (_, maybe_params) = parse_query_string(&operation.http.request_uri);

    let mut static_params = vec![];

    // Sometimes the key has the query param already set, like "list-type=2"
    // These need to get parsed out of the URI and added to the params map
    // with the other "queryparam" members
    if let Some(ref key) = maybe_params {
        match key.find('=') {
            Some(_) => {
                let key_val_vec: Vec<&str> = key.split('=').collect();
                static_params.push(format!("params.put(\"{}\", \"{}\");",
                                           key_val_vec[0],
                                           key_val_vec[1]))
            }
            None => static_params.push(format!("params.put_key(\"{}\");", key)),
        }
    };

    static_params
}

// This is a bit messy and could use refactoring.
fn generate_snake_case_uri(request_uri: &str) -> String {
    lazy_static! {
        static ref URI_ARGS_SNAKE_REGEX: Regex = Regex::new(r"\{([\w\d]+)\}").unwrap();
        static ref URI_ARGS_DASH_TO_UNDERSCORE_REGEX: Regex = Regex::new(r"\{\w+\-\w+\}").unwrap();
    }
    let mut snake: String = request_uri.to_string().clone();
    // convert fooBar to foo_bar
    for caps in URI_ARGS_SNAKE_REGEX.captures_iter(request_uri) {
        let to_find = caps.get(0).expect("nothing captured").as_str();
        // this silliness is because {fooBar} gets converted to {foo_bar_} and sometimes {_foo_bar}.
        let replacement = Inflector::to_snake_case(caps.get(0).unwrap().as_str())
            .replace("_}", "}")
            .replace("{_", "{");
        snake = snake.replace(to_find, &replacement);
    }

    // convert foo-bar to foo_bar
    let temp_snake = snake.clone();
    for caps in URI_ARGS_DASH_TO_UNDERSCORE_REGEX.captures_iter(&temp_snake) {
        let to_find = caps.get(0).unwrap().as_str();
        let replacement = caps.get(0).unwrap().as_str().replace("-", "_");
        snake = snake.replace(to_find, &replacement);
    }

    snake
}

fn generate_shape_member_param_strings(service: &Service, shape: &Shape) -> Vec<String> {
    shape.members
        .as_ref()
        .unwrap()
        .iter()
        .filter_map(|(member_name, member)| {
            member.location.as_ref().and_then(|loc| {
                if !member.deprecated() && loc == "querystring" {
                    let member_shape = service.shape_for_member(member).unwrap();
                    Some(generate_param_load_string(member_name,
                                                    member_shape,
                                                    member,
                                                    shape.required(member_name)))
                } else {
                    None
                }
            })
        })
        .collect::<Vec<String>>()
}

fn generate_param_load_string(member_name: &str,
                              member_shape: &Shape,
                              member: &Member,
                              is_required: bool)
                              -> String {
    let field_name = generate_field_name(member_name);
    let param_name = match member.location_name {
        Some(ref location) => location.to_owned(),
        None => member_name.to_owned(),
    };
    match (member_shape.shape_type, is_required) {
        (ShapeType::List, true) => {
            format!("for item in input.{field_name}.iter() {{
                     params.put(\"{param_name}\", item);
                }}",
                    param_name = param_name,
                    field_name = field_name)
        }
        (ShapeType::List, false) => {
            format!(
                "if let Some(ref x) = input.{field_name} {{
                    for item in x.iter() {{
                        params.put(\"{param_name}\", item);
                    }}
                }}",
                param_name = param_name,
                field_name = field_name,
            )
        }
        (ShapeType::Map, true) => {
            format!("for (key, val) in input.{field_name}.iter() {{
                     params.put(key, val);
                }}",
                    field_name = field_name)
        }
        (ShapeType::Map, false) => {
            format!(
                "if let Some(ref x) = input.{field_name} {{
                    for (key, val) in x.iter() {{
                        params.put(key, val);
                    }}
                }}",
                field_name = field_name,
            )
        }
        (_, true) => {
            format!("params.put(\"{param_name}\", &input.{field_name});",
                    param_name = param_name,
                    field_name = field_name)
        }
        (_, false) => {
            format!(
                "if let Some(ref x) = input.{field_name} {{
                    params.put(\"{param_name}\", x);
                }}",
                param_name = param_name,
                field_name = field_name,
            )
        }
    }
}

pub fn parse_query_string(uri: &str) -> (String, Option<String>) {
    // botocore query strings for S3 are variations on "/{Bucket}/{Key+}?foobar"
    // the query string needs to be split out and put in the params hash,
    // and the + isn't useful information for us
    let base_uri = uri.replace("+", "");
    let parts: Vec<&str> = base_uri.split('?').collect();

    match parts.len() {
        1 => (parts[0].to_owned(), None),
        2 => (parts[0].to_owned(), Some(parts[1].to_owned())),
        _ => panic!("Unknown uri structure {}", uri),
    }
}

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

    #[test]
    fn uri_snakeification_works() {
        let basic = "application-id";
        assert_eq!("application-id", generate_snake_case_uri(&basic));

        let simple_uri = "/v1/apps/{application-id}";
        assert_eq!("/v1/apps/{application_id}", generate_snake_case_uri(&simple_uri));

        let two_items_uri = "/v1/ap-ps/{application-id}/endpoints/{endpoint-id}";
        assert_eq!("/v1/ap-ps/{application_id}/endpoints/{endpoint_id}", generate_snake_case_uri(&two_items_uri));

        // existing behavior
        let snake_case = "{usageplanId}";
        assert_eq!("{usageplan_id}", generate_snake_case_uri(&snake_case));

        let lower_snake_case = "{api_Key}";
        assert_eq!("{api_key}", generate_snake_case_uri(&lower_snake_case));

        let no_touching_non_params = "/2017-03-25/distributionsByWebACLId/{WebACLId}";
        assert_eq!("/2017-03-25/distributionsByWebACLId/{web_acl_id}", generate_snake_case_uri(&no_touching_non_params));
    }

}<|endoftext|>


<commit_before>use crate::backtrace::{Backtrace, BacktraceFrame, BacktraceSymbol};
// use serde::ser::{Serialize, SerializeSeq, Serializer};
// use serde_json;
use std::collections::HashMap;

#[derive(Debug, Serialize, Clone)]
pub struct NoticeFrame {
    pub file: String,
    pub function: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub line: Option<u32>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<HashMap<i32, String>>,
}

impl NoticeFrame {
    /// A single BacktraceFrame can contain multiple "symbols" which are effectively
    /// different scopes. The frame is a context of a single line, but that
    /// single line may have multiple scopes. The Backtrace/Airbrake relationship
    /// is 1-to-1 between a Backtrace Symbols (not Backtrace Frame) and a Airbrake Frame.
    fn unroll_frame_symbols(frame: &BacktraceFrame) -> Vec<NoticeFrame> {
        frame.symbols().iter().map(NoticeFrame::from).collect()
    }

    pub fn from_backtrace(backtrace: &Backtrace) -> Vec<NoticeFrame> {
        let mut frames: Vec<NoticeFrame> = vec![];
        for f in backtrace.frames() {
            frames.append(&mut NoticeFrame::unroll_frame_symbols(&f));
        }
        frames
    }
}

impl From<&BacktraceSymbol> for NoticeFrame {
    fn from(symbol: &BacktraceSymbol) -> NoticeFrame {
        let filename = match symbol.filename() {
            Some(f) => f
                .to_str()
                .expect("Backtrace frame's filename was not valid unicode")
                .to_string(),
            None => "(anonymous)".to_string(),
        };
        let function_name = symbol
            .name()
            .expect("Backtrace frame doesn't have a function name")
            .to_string();

        NoticeFrame {
            file: filename,
            line: symbol.lineno(),
            function: function_name,
            code: None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::NoticeFrame;
    use crate::backtrace::Backtrace;

    #[test]
    fn backtrace_contains_current_function_frame() {
        let function_name: String = "backtrace_contains_current_function_frame".to_string();
        // This test builds a new backtrace object and asserts that
        // the current function exists somewhere in the resulting
        // list of frames.
        let backtrace = Backtrace::new();
        let selected_frames: Vec<NoticeFrame> = NoticeFrame::from_backtrace(&backtrace)
            .into_iter()
            .filter(|f| f.function.contains(&function_name))
            .collect();

        assert_gt!(selected_frames.len(), 0);
    }

    #[test]
    fn backtrace_unrolls_multiple_symboles() {
        let function_name: String = "backtrace_unrolls_multiple_symboles".to_string();
        let nested_frame_line: u32 = 85;
        // This backtrace is generated from within a nested enclosure so
        // that the backtraces creates a single frame with two symboles
        let fn_backtrace = || (|| Backtrace::new())();
        let backtrace = fn_backtrace();
        let selected_frames: Vec<NoticeFrame> = NoticeFrame::from_backtrace(&backtrace)
            .into_iter()
            .filter(|f| f.function.contains(&function_name) && f.line == Some(nested_frame_line))
            .collect();

        assert_gt!(selected_frames.len(), 1);
    }
}
<commit_msg>Removed NoticeFrame expects<commit_after>use crate::backtrace::{Backtrace, BacktraceFrame, BacktraceSymbol};
use std::collections::HashMap;

#[derive(Debug, Serialize, Clone)]
pub struct NoticeFrame {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub file: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub line: Option<u32>,

    /// TODO: Support for column number not yet implemented
    #[serde(skip_serializing_if = "Option::is_none")]
    pub column: Option<u32>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub function: Option<String>,

    /// TODO: Support for surrounding code not implemented
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code: Option<HashMap<i32, String>>,
}

impl NoticeFrame {
    /// A single BacktraceFrame can contain multiple "symbols" which are effectively
    /// different scopes. The frame is a context of a single line, but that
    /// single line may have multiple scopes. The Backtrace/Airbrake relationship
    /// is 1-to-1 between a Backtrace Symbols (not Backtrace Frame) and a Airbrake Frame.
    fn unroll_frame_symbols(frame: &BacktraceFrame) -> Vec<NoticeFrame> {
        frame.symbols().iter().map(NoticeFrame::from).collect()
    }

    pub fn from_backtrace(backtrace: &Backtrace) -> Vec<NoticeFrame> {
        let mut frames: Vec<NoticeFrame> = vec![];
        for f in backtrace.frames() {
            frames.append(&mut NoticeFrame::unroll_frame_symbols(&f));
        }
        frames
    }
}

impl From<&BacktraceSymbol> for NoticeFrame {
    fn from(symbol: &BacktraceSymbol) -> NoticeFrame {
        let filename = symbol
            .filename()
            .map(|sfn| sfn.to_str())
            .map(|sfn| sfn.unwrap().to_string());
        let function_name = symbol.name().map(|sn| sn.to_string());

        NoticeFrame {
            file: filename,
            line: symbol.lineno(),
            column: None,
            function: function_name,
            code: None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::NoticeFrame;
    use crate::backtrace::Backtrace;

    #[test]
    fn backtrace_contains_current_function_frame() {
        let function_name: String = "backtrace_contains_current_function_frame".to_string();
        // This test builds a new backtrace object and asserts that
        // the current function exists somewhere in the resulting
        // list of frames.
        let backtrace = Backtrace::new();
        let selected_frames: Vec<NoticeFrame> = NoticeFrame::from_backtrace(&backtrace)
            .into_iter()
            .filter(|frame| match frame.function.as_ref() {
                Some(inner) => inner.contains(&function_name),
                None => false,
            })
            .collect();

        assert_gt!(selected_frames.len(), 0);
    }

    #[test]
    fn backtrace_unrolls_multiple_symboles() {
        let function_name: String = "backtrace_unrolls_multiple_symboles".to_string();
        let nested_frame_line: u32 = 89;
        // This backtrace is generated from within a nested enclosure so
        // that the backtraces creates a single frame with two symboles
        let fn_backtrace = || (|| Backtrace::new())();
        let backtrace = fn_backtrace();
        let selected_frames: Vec<NoticeFrame> = NoticeFrame::from_backtrace(&backtrace)
            .into_iter()
            .filter(|frame| match (frame.function.as_ref(), frame.line) {
                (Some(f), Some(l)) => f.contains(&function_name) && l == nested_frame_line,
                _ => false,
            })
            .collect();

        assert_gt!(selected_frames.len(), 1);
    }
}
<|endoftext|>


<commit_before>#[cfg(target_os = "linux")]
use core_affinity;

#[cfg(not(target_os = "linux"))]
use num_cpus;

use super::{ActorCell, context};
use futures::future::Executor;
use futures::prelude::*;
use futures::sync::mpsc;
use rand;
use rand::Rng;
use std::iter;
use std::sync::{Arc, Mutex};
use std::thread;
use tokio_core::reactor::{Core, Remote};

enum ThreadMessage {
    ProcessActor(ActorCell),
}

struct ThreadHandle {
    sender: mpsc::Sender<ThreadMessage>,
    handle: thread::JoinHandle<()>,
    remote: Remote,
}

impl ThreadHandle {
    pub fn join(self) {
        self.handle.join().expect("Shutdown failed")
    }
}

pub struct Dispatcher {
    handles: Vec<ThreadHandle>,
    thread_i: usize,
}

impl Dispatcher {
    pub fn new() -> Self {
        Self {
            handles: Vec::new(),
            thread_i: 0,
        }
    }

    pub fn start(&mut self) {
        self.handles = self.create_threads();
    }

    pub fn join(self) {
        self.handles.into_iter().for_each(ThreadHandle::join)
    }

    pub fn dispatch(&self, actor: ActorCell) {
        let thread_handle = self.next_thread();
        let f = thread_handle
            .sender
            .clone()
            .send(ThreadMessage::ProcessActor(actor))
            .map(|_| ())
            .map_err(|_| ());
        thread_handle.remote.execute(f).unwrap();
    }

    #[cfg(target_os = "linux")]
    fn create_threads(&self) -> Vec<ThreadHandle> {
        core_affinity::get_core_ids()
            .unwrap()
            .into_iter()
            .map(|core_id| self.create_thread(core_id))
            .collect::<Vec<ThreadHandle>>()
    }

    #[cfg(target_os = "linux")]
    fn create_thread(&self, core_id: core_affinity::CoreId) -> ThreadHandle {
        let (sender, receiver) = mpsc::channel(100);
        let (remote, handle) = DispatcherThread::new(receiver).run_with_affinity(core_id);
        ThreadHandle {
            sender: sender,
            handle: handle,
            remote: remote,
        }
    }

    #[cfg(not(target_os = "linux"))]
    fn create_threads(&self) -> Vec<ThreadHandle> {
        iter::repeat(())
            .take(num_cpus::get())
            .map(|_| self.create_thread())
            .collect::<Vec<ThreadHandle>>()
    }

    #[cfg(not(target_os = "linux"))]
    fn create_thread(&self) -> ThreadHandle {
        let (sender, receiver) = mpsc::channel(100);
        let (remote, handle) = DispatcherThread::new(receiver).run();
        ThreadHandle {
            sender: sender,
            handle: handle,
            remote: remote,
        }
    }

    fn next_thread(&self) -> &ThreadHandle {
        let mut rng = rand::thread_rng();
        let handle = rng.choose(&self.handles);
        handle.unwrap()
    }
}

struct DispatcherThread {
    receiver: mpsc::Receiver<ThreadMessage>,
}

impl DispatcherThread {
    pub fn new(receiver: mpsc::Receiver<ThreadMessage>) -> Self {
        Self { receiver: receiver }
    }

    pub fn run(self) -> (Remote, thread::JoinHandle<()>) {
        let arc_remote = Arc::new(Mutex::new(None));
        let cloned_arc_remote = arc_remote.clone();
        let handle = thread::spawn(move || {
            println!("Starting thread: {:?}", thread::current().id());
            let stream = self.receiver
                .for_each(|message| {
                              handle_message(message);
                              Ok(())
                          });
            let mut core = Core::new().expect("Failed to start dispatcher thread");
            let handle = core.handle();
            *cloned_arc_remote.lock().unwrap() = Some(core.remote());
            context::set_thread_context(context::ThreadContext { handle: handle });
            core.run(stream).expect("Dispatcher failure");
        });
        // Need to extract the Remote from the new thread
        loop {
            if arc_remote.lock().unwrap().is_some() {
                break;
            }
            thread::sleep_ms(10);
        }
        let remote = arc_remote.lock().unwrap().as_ref().unwrap().clone();
        (remote, handle)
    }

    #[cfg(target_os = "linux")]
    pub fn run_with_affinity(self,
                             core_id: core_affinity::CoreId)
                             -> (Remote, thread::JoinHandle<()>) {
        let arc_remote = Arc::new(Mutex::new(None));
        let cloned_arc_remote = arc_remote.clone();
        let handle = thread::spawn(move || {
            println!("Starting thread: {:?}", thread::current().id());
            core_affinity::set_for_current(core_id);
            let stream = self.receiver.for_each(|message| handle_message(message));
            let mut core = Core::new().expect("Failed to start dispatcher thread");
            let handle = core.handle();
            *cloned_arc_remote.lock().unwrap() = Some(core.remote());
            context::set_thread_context(context::ThreadContext { handle: handle });
            core.run(stream).expect("Dispatcher failure");
        });
        // Need to extract the Remote from the new thread
        loop {
            if arc_remote.lock().unwrap().is_some() {
                break;
            }
            thread::sleep_ms(10);
        }
        let remote = arc_remote.lock().unwrap().as_ref().unwrap().clone();
        (remote, handle)
    }
}

fn handle_message(message: ThreadMessage) {
    match message {
        ThreadMessage::ProcessActor(mut actor_cell) => {
            actor_cell.process_messages(10);
            actor_cell.set_idle_or_dispatch();
        }
    }
}
<commit_msg>Fix linux build<commit_after>#[cfg(target_os = "linux")]
use core_affinity;

#[cfg(not(target_os = "linux"))]
use num_cpus;

use super::{ActorCell, context};
use futures::future::Executor;
use futures::prelude::*;
use futures::sync::mpsc;
use rand;
use rand::Rng;
use std::iter;
use std::sync::{Arc, Mutex};
use std::thread;
use tokio_core::reactor::{Core, Remote};

enum ThreadMessage {
    ProcessActor(ActorCell),
}

struct ThreadHandle {
    sender: mpsc::Sender<ThreadMessage>,
    handle: thread::JoinHandle<()>,
    remote: Remote,
}

impl ThreadHandle {
    pub fn join(self) {
        self.handle.join().expect("Shutdown failed")
    }
}

pub struct Dispatcher {
    handles: Vec<ThreadHandle>,
    thread_i: usize,
}

impl Dispatcher {
    pub fn new() -> Self {
        Self {
            handles: Vec::new(),
            thread_i: 0,
        }
    }

    pub fn start(&mut self) {
        self.handles = self.create_threads();
    }

    pub fn join(self) {
        self.handles.into_iter().for_each(ThreadHandle::join)
    }

    pub fn dispatch(&self, actor: ActorCell) {
        let thread_handle = self.next_thread();

        let f = thread_handle
            .sender
            .clone()
            .send(ThreadMessage::ProcessActor(actor))
            .map(|_| ())
            .map_err(|_| ());
        thread_handle.remote.execute(f).unwrap();
    }

    #[cfg(target_os = "linux")]
    fn create_threads(&self) -> Vec<ThreadHandle> {
        core_affinity::get_core_ids()
            .unwrap()
            .into_iter()
            .map(|core_id| self.create_thread(core_id))
            .collect::<Vec<ThreadHandle>>()
    }

    #[cfg(target_os = "linux")]
    fn create_thread(&self, core_id: core_affinity::CoreId) -> ThreadHandle {
        let (sender, receiver) = mpsc::channel(100);
        let (remote, handle) = DispatcherThread::new(receiver).run_with_affinity(core_id);
        ThreadHandle {
            sender: sender,
            handle: handle,
            remote: remote,
        }
    }

    #[cfg(not(target_os = "linux"))]
    fn create_threads(&self) -> Vec<ThreadHandle> {
        iter::repeat(())
            .take(num_cpus::get())
            .map(|_| self.create_thread())
            .collect::<Vec<ThreadHandle>>()
    }

    #[cfg(not(target_os = "linux"))]
    fn create_thread(&self) -> ThreadHandle {
        let (sender, receiver) = mpsc::channel(100);
        let (remote, handle) = DispatcherThread::new(receiver).run();
        ThreadHandle {
            sender: sender,
            handle: handle,
            remote: remote,
        }
    }

    fn next_thread(&self) -> &ThreadHandle {
        let mut rng = rand::thread_rng();
        let handle = rng.choose(&self.handles);
        handle.unwrap()
    }
}

struct DispatcherThread {
    receiver: mpsc::Receiver<ThreadMessage>,
}

impl DispatcherThread {
    pub fn new(receiver: mpsc::Receiver<ThreadMessage>) -> Self {
        Self { receiver: receiver }
    }

    pub fn run(self) -> (Remote, thread::JoinHandle<()>) {
        let arc_remote = Arc::new(Mutex::new(None));
        let cloned_arc_remote = arc_remote.clone();
        let handle = thread::spawn(move || {
            println!("Starting thread: {:?}", thread::current().id());
            let stream = self.receiver
                .for_each(|message| {
                              handle_message(message);
                              Ok(())
                          });
            let mut core = Core::new().expect("Failed to start dispatcher thread");
            let handle = core.handle();
            *cloned_arc_remote.lock().unwrap() = Some(core.remote());
            context::set_thread_context(context::ThreadContext { handle: handle });
            core.run(stream).expect("Dispatcher failure");
        });
        // Need to extract the Remote from the new thread
        loop {
            if arc_remote.lock().unwrap().is_some() {
                break;
            }
            thread::sleep_ms(10);
        }
        let remote = arc_remote.lock().unwrap().as_ref().unwrap().clone();
        (remote, handle)
    }

    #[cfg(target_os = "linux")]
    pub fn run_with_affinity(self,
                             core_id: core_affinity::CoreId)
                             -> (Remote, thread::JoinHandle<()>) {
        let arc_remote = Arc::new(Mutex::new(None));
        let cloned_arc_remote = arc_remote.clone();
        let handle = thread::spawn(move || {
            println!("Starting thread: {:?}", thread::current().id());
            core_affinity::set_for_current(core_id);
            let stream = self.receiver
                .for_each(|message| {
                              handle_message(message);
                              Ok(())
                          });
            let mut core = Core::new().expect("Failed to start dispatcher thread");
            let handle = core.handle();
            *cloned_arc_remote.lock().unwrap() = Some(core.remote());
            context::set_thread_context(context::ThreadContext { handle: handle });
            core.run(stream).expect("Dispatcher failure");
        });
        // Need to extract the Remote from the new thread
        loop {
            if arc_remote.lock().unwrap().is_some() {
                break;
            }
            thread::sleep_ms(10);
        }
        let remote = arc_remote.lock().unwrap().as_ref().unwrap().clone();
        (remote, handle)
    }
}

fn handle_message(message: ThreadMessage) {
    match message {
        ThreadMessage::ProcessActor(mut actor_cell) => {
            actor_cell.process_messages(10);
            actor_cell.set_idle_or_dispatch();
        }
    }
}
<|endoftext|>


<commit_before>use seed::prelude::*;
use crate::Msg;

use num_traits::{pow,ToPrimitive};
use num_bigint::{BigUint,ToBigUint};

mod perfects_utils {
    use seed::prelude::*;
    use crate::Msg;

	pub struct Perfect<'a> {
		pub n: u64,
		pub p: u64,
		pub digits: u64,
		pub discovery: &'a str,
	}

	pub fn perfects<'a>() -> Vec<Perfect<'a>> {
        vec![
			/*Perfect {n: 51, p: 82589934, digits: 49724095, discovery: "2018 Laroche, Woltman, Kurowski, Blosser, et al."},
			Perfect {n: 50, p: 77232917, digits: 46498850, discovery: "2017 Jonathan Pace, George Woltman, Scott Kurowski, Aaron Blosser, et al.." },
			Perfect {n: 49, p: 74207281, digits: 44677235, discovery: "2016 Cooper, Woltman, Kurowski, Blosser et al." },
			Perfect {n: 48, p: 57885161, digits: 34850340, discovery: "2013 Cooper, Woltman, Kurowski, et al." },
			Perfect {n: 47, p: 43112609, digits: 25956377, discovery: "2008 Smith, Woltman, Kurowski, et al." },
			Perfect {n: 46, p: 42643801, digits: 42643801, discovery: "2009 Strindmo, Woltman, Kurowski, et al." },
			Perfect {n: 45, p: 37156667, digits: 22370543, discovery: "2008 Elvenich, Woltman, Kurowski, et al." },
			Perfect {n: 44, p: 32582657, digits: 19616714, discovery: "2006 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 43, p: 30402457, digits: 18304103, discovery: "2005 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 42, p: 25964951, digits: 15632458, discovery: "2005 Nowak, Woltman, Kurowski, et. al." },
			Perfect {n: 41, p: 24036583, digits: 14471465, discovery: "2004 Findley, Woltman, Kurowski, et. al." },
			Perfect {n: 40, p: 20996011, digits: 12640858, discovery: "2003 Shafer, Woltman, Kurowski, et. al." },
			Perfect {n: 39, p: 13466917, digits: 8107892, discovery: "2001 Cameron, Woltman, Kurowski, et. al." },
			Perfect {n: 38, p: 6972593, digits: 4197919, discovery: "1999 Hajratwala, Woltman, Kurowski, et. al." },
			Perfect {n: 37, p: 3021377, digits: 1819050, discovery: "1998 Clarkson, Woltman, Kurowski, et. al." },
			Perfect {n: 36, p: 2976221, digits: 1791864, discovery: "1997 Spence, Woltman, et. al." },
			Perfect {n: 35, p: 1398269, digits: 841842, discovery: "1996 Armengaud, Woltman, et. al." },
			Perfect {n: 34, p: 1257787, digits: 757263, discovery: "1996 Slowinski&Gage" },
			Perfect {n: 33, p: 859433, digits: 517430, discovery: "1994 Slowinski&Gage" },
			Perfect {n: 32, p: 756839, digits: 455663, discovery: "1992 Slowinski&Gage" },
			Perfect {n: 31, p: 216091, digits: 130100, discovery: "1985 Slowinski" },*/
			Perfect {n: 30, p: 132049, digits: 79502, discovery: "1983 Slowinski" },
			Perfect {n: 29, p: 110503, digits: 66530, discovery: "1988 Colquitt&Welsh" },
			Perfect {n: 28, p: 86243, digits: 51924, discovery: "1982 Slowinski" },
			Perfect {n: 27, p: 44497, digits: 26790, discovery: "1979 Nelson&Slowinski" },
			Perfect {n: 26, p: 23209, digits: 13973, discovery: "1979 Noll" },
			Perfect {n: 25, p: 21701, digits: 13066, discovery: "1978 Noll&Nickel" },
			Perfect {n: 24, p: 19937, digits: 12003, discovery: "1971 Tuckerman" },
			Perfect {n: 23, p: 11213, digits: 6751, discovery: "1963 Gillies" },
			Perfect {n: 22, p: 9941, digits: 5985, discovery: "1963 Gillies" },
			Perfect {n: 21, p: 9689, digits: 5834, discovery: "1963 Gillies" },
			Perfect {n: 20, p: 4423, digits: 2663, discovery: "1961 Hurwitz" },
			Perfect {n: 19, p: 4253, digits: 2561, discovery: "1961 Hurwitz" },
			Perfect {n: 18, p: 3217, digits: 1937, discovery: "1957 Riesel" },
			Perfect {n: 17, p: 2281, digits: 1373, discovery: "1952 Robinson" },
			Perfect {n: 16, p: 2203, digits: 1327, discovery: "1952 Robinson" },
			Perfect {n: 15, p: 1279, digits: 770, discovery: "1952 Robinson" },
			Perfect {n: 14, p: 607, digits: 366, discovery: "1952 Robinson" },
			Perfect {n: 13, p: 521, digits: 314, discovery: "1952 Robinson" },
			Perfect {n: 12, p: 127, digits: 77, discovery: "1876 Lucas" },
			Perfect {n: 11, p: 107, digits: 65, discovery: "1914 Powers" },
			Perfect {n: 10, p: 89, digits: 54, discovery: "1911 Powers" },
			Perfect {n: 9, p: 61, digits: 37, discovery: "1883 Pervushin" },
			Perfect {n: 8, p: 31, digits: 19, discovery: "1772 Euler" },
			Perfect {n: 7, p: 19, digits: 12, discovery: "1588 Cataldi" },
			Perfect {n: 6, p: 17, digits: 10, discovery: "1588 Cataldi" },
			Perfect {n: 5, p: 13, digits: 8, discovery: "1456 ?" },
			Perfect {n: 4, p: 7, digits: 4, discovery: "?" },
			Perfect {n: 3, p: 5, digits: 3, discovery: "?" },
			Perfect {n: 2, p: 3, digits: 2, discovery: "?" },
			Perfect {n: 1, p: 3, digits: 1, discovery: "?" },
		]
	}

	pub fn save_as_file(filename:String, filecontent:String) -> seed::dom_types::Node<Msg> {
        let href:String = vec!["data:text/plain,",&filecontent].into_iter().collect();
        a![attrs!{At::Download => &filename, At::Href => &href}, "TXT"]
    }
}

pub fn render() -> seed::dom_types::Node<Msg> {
    let mut html = vec![];
	let two:BigUint = 2.to_biguint().unwrap();

    let perfects = perfects_utils::perfects();

    for n in 0..perfects.len() {
		let download_filename:String = format!("P{}.txt",&perfects[n].n.to_string());
        let download_txt:String = format!("https://static.bigprimes.net/archive/perfect/{}.txt",&perfects[n].n.to_string());

		let equation:String = format!("2{} × (2{}-1)",&(perfects[n].p-1).to_string(),&(perfects[n].p).to_string());

		let p:usize = perfects[n].p.to_usize().unwrap();
		let power:BigUint = pow(two.clone(), p-1);
        let perfect_value:BigUint = power.clone() * ((power.clone()*two.clone()) -1.to_biguint().unwrap()) ;

        html.push(
            tr![
                td![perfects[n].n.to_string()],//rank
				td![El::from_html(&equation)],//perfect number as a formula

                td![perfects[n].digits.to_string()],//digits in length
                td![perfects[n].discovery],//disocvery
                td![a![attrs!{At::Href => download_txt},"TXT"]],//downloads
				td![perfects_utils::save_as_file(String::from(download_filename),perfect_value.to_string())],
            ]
        );
    }

    div![
        h1!["The Perfect Numbers"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "perfecttable text"},
            tbody![
                tr![
                    td![
                        b!["No."]
                    ],
                    td![
                        b!["Prime"]
                    ],
                    td![
                        b!["Digits"]
                    ],
                    td![
                        b!["Discovered"]
                    ],
                    td![
                        b!["Download"]
                    ]
                ],
                html
            ]
        ]
    ]
}

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

    #[test]
    fn perfect_test<'a>() {
        let mut perfects:Vec<perfects_utils::Perfect<'a>> = perfects_utils::perfects();
		perfects.reverse();
        assert_eq!(perfects[49].digits, 46498850);
        assert_eq!(perfects[39].digits, 12640858);
    }
}<commit_msg>WIP: Experimenting with generating perfects for downloads<commit_after>use seed::prelude::*;
use crate::Msg;

use num_traits::{pow,ToPrimitive};
use num_bigint::{BigUint,ToBigUint};

const PERFECTS_COUNT: usize = 36;

mod perfects_utils {
    use seed::prelude::*;
    use crate::Msg;

	pub struct Perfect<'a> {
		pub n: u64,
		pub p: u64,
		pub digits: u64,
		pub discovery: &'a str,
	}

	pub fn perfects<'a>() -> Vec<Perfect<'a>> {
        vec![
			/*Perfect {n: 51, p: 82589934, digits: 49724095, discovery: "2018 Laroche, Woltman, Kurowski, Blosser, et al."},
			Perfect {n: 50, p: 77232917, digits: 46498850, discovery: "2017 Jonathan Pace, George Woltman, Scott Kurowski, Aaron Blosser, et al.." },
			Perfect {n: 49, p: 74207281, digits: 44677235, discovery: "2016 Cooper, Woltman, Kurowski, Blosser et al." },
			Perfect {n: 48, p: 57885161, digits: 34850340, discovery: "2013 Cooper, Woltman, Kurowski, et al." },
			Perfect {n: 47, p: 43112609, digits: 25956377, discovery: "2008 Smith, Woltman, Kurowski, et al." },
			Perfect {n: 46, p: 42643801, digits: 42643801, discovery: "2009 Strindmo, Woltman, Kurowski, et al." },
			Perfect {n: 45, p: 37156667, digits: 22370543, discovery: "2008 Elvenich, Woltman, Kurowski, et al." },
			Perfect {n: 44, p: 32582657, digits: 19616714, discovery: "2006 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 43, p: 30402457, digits: 18304103, discovery: "2005 Cooper, Boone, Woltman, Kurowski, et al." },
			Perfect {n: 42, p: 25964951, digits: 15632458, discovery: "2005 Nowak, Woltman, Kurowski, et. al." },
			Perfect {n: 41, p: 24036583, digits: 14471465, discovery: "2004 Findley, Woltman, Kurowski, et. al." },
			Perfect {n: 40, p: 20996011, digits: 12640858, discovery: "2003 Shafer, Woltman, Kurowski, et. al." },
			Perfect {n: 39, p: 13466917, digits: 8107892, discovery: "2001 Cameron, Woltman, Kurowski, et. al." },
			Perfect {n: 38, p: 6972593, digits: 4197919, discovery: "1999 Hajratwala, Woltman, Kurowski, et. al." },
			Perfect {n: 37, p: 3021377, digits: 1819050, discovery: "1998 Clarkson, Woltman, Kurowski, et. al." },*/
			Perfect {n: 36, p: 2976221, digits: 1791864, discovery: "1997 Spence, Woltman, et. al." },
			Perfect {n: 35, p: 1398269, digits: 841842, discovery: "1996 Armengaud, Woltman, et. al." },
			Perfect {n: 34, p: 1257787, digits: 757263, discovery: "1996 Slowinski&Gage" },
			Perfect {n: 33, p: 859433, digits: 517430, discovery: "1994 Slowinski&Gage" },
			Perfect {n: 32, p: 756839, digits: 455663, discovery: "1992 Slowinski&Gage" },
			Perfect {n: 31, p: 216091, digits: 130100, discovery: "1985 Slowinski" },
			Perfect {n: 30, p: 132049, digits: 79502, discovery: "1983 Slowinski" },
			Perfect {n: 29, p: 110503, digits: 66530, discovery: "1988 Colquitt&Welsh" },
			Perfect {n: 28, p: 86243, digits: 51924, discovery: "1982 Slowinski" },
			Perfect {n: 27, p: 44497, digits: 26790, discovery: "1979 Nelson&Slowinski" },
			Perfect {n: 26, p: 23209, digits: 13973, discovery: "1979 Noll" },
			Perfect {n: 25, p: 21701, digits: 13066, discovery: "1978 Noll&Nickel" },
			Perfect {n: 24, p: 19937, digits: 12003, discovery: "1971 Tuckerman" },
			Perfect {n: 23, p: 11213, digits: 6751, discovery: "1963 Gillies" },
			Perfect {n: 22, p: 9941, digits: 5985, discovery: "1963 Gillies" },
			Perfect {n: 21, p: 9689, digits: 5834, discovery: "1963 Gillies" },
			Perfect {n: 20, p: 4423, digits: 2663, discovery: "1961 Hurwitz" },
			Perfect {n: 19, p: 4253, digits: 2561, discovery: "1961 Hurwitz" },
			Perfect {n: 18, p: 3217, digits: 1937, discovery: "1957 Riesel" },
			Perfect {n: 17, p: 2281, digits: 1373, discovery: "1952 Robinson" },
			Perfect {n: 16, p: 2203, digits: 1327, discovery: "1952 Robinson" },
			Perfect {n: 15, p: 1279, digits: 770, discovery: "1952 Robinson" },
			Perfect {n: 14, p: 607, digits: 366, discovery: "1952 Robinson" },
			Perfect {n: 13, p: 521, digits: 314, discovery: "1952 Robinson" },
			Perfect {n: 12, p: 127, digits: 77, discovery: "1876 Lucas" },
			Perfect {n: 11, p: 107, digits: 65, discovery: "1914 Powers" },
			Perfect {n: 10, p: 89, digits: 54, discovery: "1911 Powers" },
			Perfect {n: 9, p: 61, digits: 37, discovery: "1883 Pervushin" },
			Perfect {n: 8, p: 31, digits: 19, discovery: "1772 Euler" },
			Perfect {n: 7, p: 19, digits: 12, discovery: "1588 Cataldi" },
			Perfect {n: 6, p: 17, digits: 10, discovery: "1588 Cataldi" },
			Perfect {n: 5, p: 13, digits: 8, discovery: "1456 ?" },
			Perfect {n: 4, p: 7, digits: 4, discovery: "?" },
			Perfect {n: 3, p: 5, digits: 3, discovery: "?" },
			Perfect {n: 2, p: 3, digits: 2, discovery: "?" },
			Perfect {n: 1, p: 2, digits: 1, discovery: "?" },
		]
	}

	pub fn save_as_file(filename:String, filecontent:String) -> seed::dom_types::Node<Msg> {
        let href:String = vec!["data:text/plain,",&filecontent].into_iter().collect();
        a![attrs!{At::Download => &filename, At::Href => &href}, "TXT"]
    }
}

pub fn render() -> seed::dom_types::Node<Msg> {
    let mut html = vec![];
	let two:BigUint = 2.to_biguint().unwrap();

    let mut perfects = perfects_utils::perfects();
	perfects.reverse();
	let mut power_shifted:BigUint = 2.to_biguint().unwrap();
	let mut perfects_value:Vec<BigUint> = vec![2.to_biguint().unwrap(); PERFECTS_COUNT];
	for n in 0..PERFECTS_COUNT {
		let p:usize = perfects[n].p.to_usize().unwrap();
		perfects_value[n] = power_shifted.clone();
		let previous_p:usize = if n > 0 { perfects[n-1].p.to_usize().unwrap() } else { 0 };
		power_shifted <<= p-previous_p-1;//todo this needs to be the difference between p and previous p
	}
	perfects.reverse();

	perfects_value[PERFECTS_COUNT-1] = 2.to_biguint().unwrap();
	perfects_value[PERFECTS_COUNT-2] = 4.to_biguint().unwrap();

	perfects_value.reverse();

    for n in 0..PERFECTS_COUNT {
		let download_filename:String = format!("P{}.txt",&perfects[n].n.to_string());
        let download_txt:String = format!("https://static.bigprimes.net/archive/perfect/{}.txt",&perfects[n].n.to_string());

		let equation:String = format!("2{} × (2{}-1)",&(perfects[n].p-1).to_string(),&(perfects[n].p).to_string());

		//let p:usize = perfects[n].p.to_usize().unwrap();
		//let power:BigUint = two.clone() << (p-1-1);
        //let perfect_value:BigUint = power.clone() * ((power.clone()*two.clone()) -1.to_biguint().unwrap());

		let shifted_perfect_value:BigUint = perfects_value[n].clone() * ((perfects_value[n].clone() *two.clone()) -1.to_biguint().unwrap());

        html.push(
            tr![
                td![perfects[n].n.to_string()],//rank
				td![El::from_html(&equation)],//perfect number as a formula

                td![perfects[n].digits.to_string()],//digits in length
                td![perfects[n].discovery],//disocvery
                td![a![attrs!{At::Href => download_txt},"TXT"]],//downloads
				//td![perfects_utils::save_as_file(String::from(&download_filename),perfect_value.to_string())],

				td![perfects_utils::save_as_file(String::from(&download_filename),shifted_perfect_value.to_string())],
            ]
        );
    }

    div![
        h1!["The Perfect Numbers"],
        br![],
        br![],
        br![],
        table![
            attrs!{At::Class => "perfecttable text"},
            tbody![
                tr![
                    td![
                        b!["No."]
                    ],
                    td![
                        b!["Prime"]
                    ],
                    td![
                        b!["Digits"]
                    ],
                    td![
                        b!["Discovered"]
                    ],
                    td![
                        b!["Download"]
                    ]
                ],
                html
            ]
        ]
    ]
}

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

    #[test]
    fn perfect_test<'a>() {
        let mut perfects:Vec<perfects_utils::Perfect<'a>> = perfects_utils::perfects();
		perfects.reverse();
        assert_eq!(perfects[49].digits, 46498850);
        assert_eq!(perfects[39].digits, 12640858);
    }
}<|endoftext|>