<commit_before>//
// Copyright:: Copyright (c) 2016 Chef Software, Inc.
// License:: Apache License, Version 2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use errors::{DeliveryError, Kind};
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::prelude::*;
use utils::{self, read_file};
use utils::say::{say, sayln};
use utils::path_ext::is_file;
use git;
use project;
use regex::Regex;

#[derive(Debug, Clone)]
pub struct MetadataVersion {
    pub major: usize,
    pub minor: usize,
    pub patch: usize,
}

impl MetadataVersion {
    pub fn to_string(&self) -> String {
        [
            self.major.to_string(), ".".to_string(),
            self.minor.to_string(), ".".to_string(),
            self.patch.to_string()
        ].concat()
    }
}

// Bump the metadata version, only if:
// * The project is a cookbook
// * The version hasn't been updated
//
// @param p_root [&PathBuf] The project root path
// @param pipeline [&str] Pipeline the change is targeting to
// @return () if success
pub fn bump_version(p_root: &PathBuf, pipeline: &str) -> Result<(), DeliveryError> {
    if is_cookbook(&p_root) {
        let project = try!(project::project_from_cwd());
        say("white", "Project ");
        say("yellow", &project);
        sayln("white", " is a cookbook");
        sayln("white", "Validating version in metadata");

        let meta_f_p = PathBuf::from(metadata_file(&p_root));
        let current_meta_v = try!(metadata_version_from(&try!(read_file(&meta_f_p))));
        let current_v = current_meta_v.to_string();
        let mut t_file = pipeline.to_string();
        t_file.push_str(":metadata.rb");
        let pipeline_meta = try!(git::git_command(&["show", &t_file], &p_root));
        let pipeline_meta_v = try!(metadata_version_from(&pipeline_meta.stdout));
        let pipeline_v = pipeline_meta_v.to_string();

        if current_v == pipeline_v {
            say("yellow", "The version hasn't been updated (");
            say("red", &pipeline_v);
            sayln("yellow", ")");
            let new_meta_version = try!(bump_patchset(pipeline_meta_v.clone()));
            let new_version = new_meta_version.to_string();
            say("white", "Bumping version to: ");
            sayln("green", &new_version);
            try!(save_version(&meta_f_p, new_version));
        } else {
            say("white", "Version already updated (");
            say("magenta", &pipeline_v);
            say("white", "/");
            say("green", &current_v);
            sayln("white", ")");
        }
    }
    Ok(())
}

// @Private

// Return the path to the metadata.rb file
fn metadata_file(path: &PathBuf) -> String {
    let mut metadata = path.to_str().unwrap().to_string();
    metadata.push_str("/metadata.rb");
    return metadata
}

// Verify if the provided path is a cookbook, or not
fn is_cookbook(path: &PathBuf) -> bool {
    let meta_f = metadata_file(path);
    is_file(&Path::new(&meta_f))
}

// Extract the cookbook version from the provided metadata content
//
// This function expects you to read the metadata in advance and
// pass just the content of the file. From there it will read every
// line and search for the version to return it.
//
// There are two valid version formats:
// a) 'x.y.z' - The normal semantic version. (mayor.minor.patch)
// b) 'x.y'   - Where the patchset will be 0 by default. (mayor.minor.0)
fn metadata_version_from(content: &String) -> Result<MetadataVersion, DeliveryError> {
    for l in content.lines() {
        let r_m_m_p = Regex::new(r"version\s+'(?P<major>\d*)\.(?P<minor>\d*)\.(?P<patch>\d*)'").unwrap();
        if let Some(version) = r_m_m_p.captures(l) {
            let ma: usize = try!(version.name("major").unwrap().parse());
            let mi: usize = try!(version.name("minor").unwrap().parse());
            let pa: usize = try!(version.name("patch").unwrap().parse());
            return Ok(MetadataVersion { major: ma, minor: mi, patch: pa });
        }
        let r_m_m = Regex::new(r"version\s+'(?P<major>\d+)\.(?P<minor>\d+)'").unwrap();
        if let Some(version) = r_m_m.captures(l) {
            let ma: usize = try!(version.name("major").unwrap().parse());
            let mi: usize = try!(version.name("minor").unwrap().parse());
            let pa: usize = 0;
            return Ok(MetadataVersion { major: ma, minor: mi, patch: pa })
        }
    };
    return Err(DeliveryError{ kind: Kind::MissingMetadataVersion, detail: None })
}

// Bump the patchset of the provided version
fn bump_patchset(mut version: MetadataVersion) -> Result<MetadataVersion, DeliveryError> {
    version = MetadataVersion { patch: version.patch + 1, .. version };
    Ok(version)
}

// Saves the new version to the metadata and commit the changes
fn save_version(metadata: &PathBuf, version: String) -> Result<(), DeliveryError> {
    let current_meta = try!(read_file(metadata));
    let current_meta_version = try!(metadata_version_from(&current_meta));
    let current_version = current_meta_version.to_string();
    let new_metadata = current_meta.replace(&*current_version, &*version);

    // Recreate the file and dump the processed contents to it
    let mut recreate_meta = try!(File::create(metadata));
    try!(recreate_meta.write(new_metadata.as_bytes()));

    // Commit the changes made to the metadata
    let mut commit_msg = String::from("Bump version to ");
    commit_msg.push_str(&version);
    try!(git::git_command(&["add", metadata.to_str().unwrap()], &utils::cwd()));
    try!(git::git_command(&["commit", "-m", &commit_msg], &utils::cwd()));
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;
    use cookbook::*;

    #[test]
    fn verify_version_bumping_using_bump_patchset() {
        let version = MetadataVersion { major: 1, minor: 2, patch: 3 };
        let MetadataVersion { major: ma, minor: mi, patch: pa } = super::bump_patchset(version).unwrap();
        assert_eq!(ma, 1);
        assert_eq!(mi, 2);
        assert_eq!(pa, 4);
    }

    #[test]
    fn return_the_metadata_file_path() {
        let project_path = PathBuf::from("/cookbook");
        assert_eq!(String::from("/cookbook/metadata.rb"), super::metadata_file(&project_path));
    }

    #[test]
    fn verify_happy_metadata_version_from_content() {
        let happy_version = "1.2.3";
        let happy_metadata_content = metadata_from_version(&happy_version);
        let happy_metadata_version = super::metadata_version_from(&happy_metadata_content).unwrap();
        assert_eq!(String::from(happy_version), happy_metadata_version.to_string());

        let valid_version = "1.2";
        let valid_metadata_content = metadata_from_version(&valid_version);
        let valid_metadata_version = super::metadata_version_from(&valid_metadata_content).unwrap();
        assert_eq!(String::from("1.2.0"), valid_metadata_version.to_string());

        let awesome_version = "123.123.123";
        let awesome_metadata_content = metadata_from_version(&awesome_version);
        let awesome_metadata_version = super::metadata_version_from(&awesome_metadata_content).unwrap();
        assert_eq!(String::from(awesome_version), awesome_metadata_version.to_string());
    }

    #[test]
    fn verify_sad_metadata_version_from_content() {
        let sad_version = "1..";
        let sad_metadata =  metadata_from_version(&sad_version);
        match super::metadata_version_from(&sad_metadata) {
            Ok(_) => assert!(false),
            Err(_) => assert!(true)
        }

        let typo_version = "1.2.";
        let typo_metadata =  metadata_from_version(&typo_version);
        match super::metadata_version_from(&typo_metadata) {
            Ok(_) => assert!(false),
            Err(_) => assert!(true)
        }

        let no_version = "";
        let no_metadata =  metadata_from_version(&no_version);
        match super::metadata_version_from(&no_metadata) {
            Ok(_) => assert!(false),
            Err(_) => assert!(true)
        }
    }

    // Quick helper to render a dummy metadata from a provided version
    fn metadata_from_version(version: &str) -> String {
        String::from(["version '", version, "'"].concat())
    }
}
<commit_msg>Add MetadataVersion::new constructor and generator<commit_after>//
// Copyright:: Copyright (c) 2016 Chef Software, Inc.
// License:: Apache License, Version 2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use errors::{DeliveryError, Kind};
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::prelude::*;
use utils::{self, read_file};
use utils::say::{say, sayln};
use utils::path_ext::is_file;
use git;
use project;
use regex::Regex;
use regex::Captures;

#[derive(Debug, Clone)]
pub struct MetadataVersion {
    pub major: usize,
    pub minor: usize,
    pub patch: usize,
}

impl MetadataVersion {
    pub fn new(ma: Option<usize>, mi: Option<usize>, pa: Option<usize>) -> MetadataVersion {
        MetadataVersion {
            major: ma.unwrap_or_default(),
            minor: mi.unwrap_or_default(),
            patch: pa.unwrap_or_default()
        }
    }

    pub fn to_string(&self) -> String {
        [
            self.major.to_string(), ".".to_string(),
            self.minor.to_string(), ".".to_string(),
            self.patch.to_string()
        ].concat()
    }
}

// Bump the metadata version, only if:
// * The project is a cookbook
// * The version hasn't been updated
//
// @param p_root [&PathBuf] The project root path
// @param pipeline [&str] Pipeline the change is targeting to
// @return () if success
pub fn bump_version(p_root: &PathBuf, pipeline: &str) -> Result<(), DeliveryError> {
    if is_cookbook(&p_root) {
        let project = try!(project::project_from_cwd());
        say("white", "Project ");
        say("yellow", &project);
        sayln("white", " is a cookbook");
        sayln("white", "Validating version in metadata");

        let meta_f_p = PathBuf::from(metadata_file(&p_root));
        let meta_f_c = try!(read_file(&meta_f_p));
        let current_meta_v = try!(metadata_version_from(&meta_f_c));
        let current_v = current_meta_v.to_string();
        let mut t_file = pipeline.to_string();
        t_file.push_str(":metadata.rb");
        let pipeline_meta = try!(git::git_command(&["show", &t_file], &p_root));
        let pipeline_meta_v = try!(metadata_version_from(&pipeline_meta.stdout));
        let pipeline_v = pipeline_meta_v.to_string();

        if current_v == pipeline_v {
            say("yellow", "The version hasn't been updated (");
            say("red", &pipeline_v);
            sayln("yellow", ")");
            let new_meta_version = try!(bump_patchset(pipeline_meta_v.clone()));
            let new_version = new_meta_version.to_string();
            say("white", "Bumping version to: ");
            sayln("green", &new_version);
            try!(save_version(&meta_f_p, new_version));
        } else {
            say("white", "Version already updated (");
            say("magenta", &pipeline_v);
            say("white", "/");
            say("green", &current_v);
            sayln("white", ")");
        }
    }
    Ok(())
}

// @Private

// Return the path to the metadata.rb file
fn metadata_file(path: &PathBuf) -> String {
    let mut metadata = path.to_str().unwrap().to_string();
    metadata.push_str("/metadata.rb");
    return metadata
}

// Verify if the provided path is a cookbook, or not
fn is_cookbook(path: &PathBuf) -> bool {
    let meta_f = metadata_file(path);
    is_file(&Path::new(&meta_f))
}

// Extract the cookbook version from the provided metadata content
//
// This function expects you to read the metadata in advance and
// pass just the content of the file. From there it will read every
// line and search for the version to return it.
//
// There are two valid version formats:
// a) 'x.y.z' - The normal semantic version. (major.minor.patch)
// b) 'x.y'   - Where the patchset will be 0 by default. (major.minor.0)
fn metadata_version_from(content: &str) -> Result<MetadataVersion, DeliveryError> {
    for l in content.lines() {
        let r_m_m_p = Regex::new(r"version\s+'(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)'").unwrap();
        if let Some(version) = r_m_m_p.captures(l) {
            return generate_metadata_version(version);
        }
        let r_m_m = Regex::new(r"version\s+'(?P<major>\d+)\.(?P<minor>\d+)'").unwrap();
        if let Some(version) = r_m_m.captures(l) {
            return generate_metadata_version(version);
        }
    };
    return Err(DeliveryError{ kind: Kind::MissingMetadataVersion, detail: None })
}

fn generate_metadata_version(metadata: Captures) -> Result<MetadataVersion, DeliveryError> {
    let mut ma = None;
    let mut mi = None;
    let mut pa = None;
    if let Some(major) = metadata.name("major") {
        ma = major.parse::<usize>().ok();
    };
    if let Some(minor) =  metadata.name("minor") {
        mi = minor.parse::<usize>().ok();
    };
    if let Some(patch) = metadata.name("patch") {
        pa = patch.parse::<usize>().ok();
    };
    Ok(MetadataVersion::new(ma, mi, pa))
}

// Bump the patchset of the provided version
fn bump_patchset(mut version: MetadataVersion) -> Result<MetadataVersion, DeliveryError> {
    version = MetadataVersion { patch: version.patch + 1, .. version };
    Ok(version)
}

// Saves the new version to the metadata and commit the changes
fn save_version(metadata: &PathBuf, version: String) -> Result<(), DeliveryError> {
    let current_meta = try!(read_file(metadata));
    let current_meta_version = try!(metadata_version_from(&current_meta));
    let current_version = current_meta_version.to_string();
    let new_metadata = current_meta.replace(&*current_version, &*version);

    // Recreate the file and dump the processed contents to it
    let mut recreate_meta = try!(File::create(metadata));
    try!(recreate_meta.write(new_metadata.as_bytes()));

    // Commit the changes made to the metadata
    let mut commit_msg = String::from("Bump version to ");
    commit_msg.push_str(&version);
    try!(git::git_command(&["add", metadata.to_str().unwrap()], &utils::cwd()));
    try!(git::git_command(&["commit", "-m", &commit_msg], &utils::cwd()));
    Ok(())
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;
    use cookbook::*;

    #[test]
    fn test_metadata_version_constructor() {
        let version_generator = MetadataVersion::new(None, Some(2), None);
        let MetadataVersion { major: ma, minor: mi, patch: pa } = version_generator;
        assert_eq!(ma, 0);
        assert_eq!(mi, 2);
        assert_eq!(pa, 0);
        assert_eq!("0.2.0", &version_generator.to_string());
    }
    #[test]
    fn verify_version_bumping_using_bump_patchset() {
        let version = MetadataVersion { major: 1, minor: 2, patch: 3 };
        let MetadataVersion { major: ma, minor: mi, patch: pa } = super::bump_patchset(version).unwrap();
        assert_eq!(ma, 1);
        assert_eq!(mi, 2);
        assert_eq!(pa, 4);
    }

    #[test]
    fn return_the_metadata_file_path() {
        let project_path = PathBuf::from("/cookbook");
        assert_eq!(String::from("/cookbook/metadata.rb"), super::metadata_file(&project_path));
    }

    #[test]
    fn verify_happy_metadata_version_from_content() {
        let happy_version = "1.2.3";
        let happy_metadata_content = metadata_from_version(&happy_version);
        let happy_metadata_version = super::metadata_version_from(&happy_metadata_content).unwrap();
        assert_eq!(happy_version, &happy_metadata_version.to_string());

        let valid_version = "1.2";
        let valid_metadata_content = metadata_from_version(&valid_version);
        let valid_metadata_version = super::metadata_version_from(&valid_metadata_content).unwrap();
        assert_eq!("1.2.0", &valid_metadata_version.to_string());

        let awesome_version = "123.123.123";
        let awesome_metadata_content = metadata_from_version(&awesome_version);
        let awesome_metadata_version = super::metadata_version_from(&awesome_metadata_content).unwrap();
        assert_eq!(awesome_version, &awesome_metadata_version.to_string());
    }

    #[test]
    fn verify_sad_metadata_version_from_content() {
        let sad_version = "1..";
        let sad_metadata =  metadata_from_version(&sad_version);
        assert!(super::metadata_version_from(&sad_metadata).is_err());

        let typo_version = "1.2.";
        let typo_metadata =  metadata_from_version(&typo_version);
        assert!(super::metadata_version_from(&typo_metadata).is_err());

        let no_version = "";
        let no_metadata =  metadata_from_version(&no_version);
        assert!(super::metadata_version_from(&no_metadata).is_err());
    }

    // Quick helper to render a dummy metadata from a provided version
    fn metadata_from_version(version: &str) -> String {
        String::from(["version '", version, "'"].concat())
    }
}
<|endoftext|>


<commit_before>use ruma_common::RoomVersionId;

use crate::{Error, Result};

#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
pub enum RoomDisposition {
    /// A room version that has a stable specification.
    Stable,
    /// A room version that is not yet fully specified.
    Unstable,
}

#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum EventFormatVersion {
    /// $id:server event id format
    V1,
    /// MSC1659-style $hash event id format: introduced for room v3
    V2,
    /// MSC1884-style $hash format: introduced for room v4
    V3,
}

#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum StateResolutionVersion {
    /// State resolution for rooms at version 1.
    V1,
    /// State resolution for room at version 2 or later.
    V2,
}

#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct RoomVersion {
    /// The stability of this room.
    pub disposition: RoomDisposition,
    /// The format of the EventId.
    pub event_format: EventFormatVersion,
    /// Which state resolution algorithm is used.
    pub state_res: StateResolutionVersion,
    // FIXME: not sure what this one means?
    pub enforce_key_validity: bool,

    /// `m.room.aliases` had special auth rules and redaction rules
    /// before room version 6.
    ///
    /// before MSC2261/MSC2432,
    pub special_case_aliases_auth: bool,
    /// Strictly enforce canonical json, do not allow:
    /// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
    /// * Floats
    /// * NaN, Infinity, -Infinity
    pub strict_canonicaljson: bool,
    /// Verify notifications key while checking m.room.power_levels.
    ///
    /// bool: MSC2209: Check 'notifications'
    pub limit_notifications_power_levels: bool,
    /// Extra rules when verifying redaction events.
    pub extra_redaction_checks: bool,
    /// Allow knocking in event authentication.
    ///
    /// See [room v7 specification](https://spec.matrix.org/v1.2/rooms/v7/) for more information.
    pub allow_knocking: bool,
    /// Adds support for the restricted join rule.
    ///
    /// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289) for more information.
    pub restricted_join_rules: bool,
}

impl RoomVersion {
    pub const V1: Self = Self {
        disposition: RoomDisposition::Stable,
        event_format: EventFormatVersion::V1,
        state_res: StateResolutionVersion::V1,
        enforce_key_validity: false,
        special_case_aliases_auth: true,
        strict_canonicaljson: false,
        limit_notifications_power_levels: false,
        extra_redaction_checks: false,
        allow_knocking: false,
        restricted_join_rules: false,
    };

    pub const V2: Self = Self { state_res: StateResolutionVersion::V2, ..Self::V1 };

    pub const V3: Self =
        Self { event_format: EventFormatVersion::V2, extra_redaction_checks: true, ..Self::V2 };

    pub const V4: Self = Self { event_format: EventFormatVersion::V3, ..Self::V3 };

    pub const V5: Self = Self { enforce_key_validity: true, ..Self::V4 };

    pub const V6: Self = Self {
        special_case_aliases_auth: false,
        strict_canonicaljson: true,
        limit_notifications_power_levels: true,
        ..Self::V5
    };

    pub const V7: Self = Self {
        // FIXME: once room version 7 is stabilized move this to version 8
        disposition: RoomDisposition::Unstable,
        allow_knocking: true,
        ..Self::V6
    };

    pub const V8: Self = Self { restricted_join_rules: true, ..Self::V7 };

    pub const V9: Self = Self::V8;

    pub fn new(version: &RoomVersionId) -> Result<Self> {
        Ok(match version {
            RoomVersionId::V1 => Self::V1,
            RoomVersionId::V2 => Self::V2,
            RoomVersionId::V3 => Self::V3,
            RoomVersionId::V4 => Self::V4,
            RoomVersionId::V5 => Self::V5,
            RoomVersionId::V6 => Self::V6,
            RoomVersionId::V7 => Self::V7,
            RoomVersionId::V8 => Self::V8,
            RoomVersionId::V9 => Self::V9,
            ver => return Err(Error::Unsupported(format!("found version `{}`", ver.as_str()))),
        })
    }
}
<commit_msg>state-res: Properly declare room version 7 stable<commit_after>use ruma_common::RoomVersionId;

use crate::{Error, Result};

#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
pub enum RoomDisposition {
    /// A room version that has a stable specification.
    Stable,
    /// A room version that is not yet fully specified.
    Unstable,
}

#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum EventFormatVersion {
    /// $id:server event id format
    V1,
    /// MSC1659-style $hash event id format: introduced for room v3
    V2,
    /// MSC1884-style $hash format: introduced for room v4
    V3,
}

#[derive(Debug)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub enum StateResolutionVersion {
    /// State resolution for rooms at version 1.
    V1,
    /// State resolution for room at version 2 or later.
    V2,
}

#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct RoomVersion {
    /// The stability of this room.
    pub disposition: RoomDisposition,
    /// The format of the EventId.
    pub event_format: EventFormatVersion,
    /// Which state resolution algorithm is used.
    pub state_res: StateResolutionVersion,
    // FIXME: not sure what this one means?
    pub enforce_key_validity: bool,

    /// `m.room.aliases` had special auth rules and redaction rules
    /// before room version 6.
    ///
    /// before MSC2261/MSC2432,
    pub special_case_aliases_auth: bool,
    /// Strictly enforce canonical json, do not allow:
    /// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
    /// * Floats
    /// * NaN, Infinity, -Infinity
    pub strict_canonicaljson: bool,
    /// Verify notifications key while checking m.room.power_levels.
    ///
    /// bool: MSC2209: Check 'notifications'
    pub limit_notifications_power_levels: bool,
    /// Extra rules when verifying redaction events.
    pub extra_redaction_checks: bool,
    /// Allow knocking in event authentication.
    ///
    /// See [room v7 specification](https://spec.matrix.org/v1.2/rooms/v7/) for more information.
    pub allow_knocking: bool,
    /// Adds support for the restricted join rule.
    ///
    /// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289) for more information.
    pub restricted_join_rules: bool,
}

impl RoomVersion {
    pub const V1: Self = Self {
        disposition: RoomDisposition::Stable,
        event_format: EventFormatVersion::V1,
        state_res: StateResolutionVersion::V1,
        enforce_key_validity: false,
        special_case_aliases_auth: true,
        strict_canonicaljson: false,
        limit_notifications_power_levels: false,
        extra_redaction_checks: false,
        allow_knocking: false,
        restricted_join_rules: false,
    };

    pub const V2: Self = Self { state_res: StateResolutionVersion::V2, ..Self::V1 };

    pub const V3: Self =
        Self { event_format: EventFormatVersion::V2, extra_redaction_checks: true, ..Self::V2 };

    pub const V4: Self = Self { event_format: EventFormatVersion::V3, ..Self::V3 };

    pub const V5: Self = Self { enforce_key_validity: true, ..Self::V4 };

    pub const V6: Self = Self {
        special_case_aliases_auth: false,
        strict_canonicaljson: true,
        limit_notifications_power_levels: true,
        ..Self::V5
    };

    pub const V7: Self = Self { allow_knocking: true, ..Self::V6 };

    pub const V8: Self = Self { restricted_join_rules: true, ..Self::V7 };

    pub const V9: Self = Self::V8;

    pub fn new(version: &RoomVersionId) -> Result<Self> {
        Ok(match version {
            RoomVersionId::V1 => Self::V1,
            RoomVersionId::V2 => Self::V2,
            RoomVersionId::V3 => Self::V3,
            RoomVersionId::V4 => Self::V4,
            RoomVersionId::V5 => Self::V5,
            RoomVersionId::V6 => Self::V6,
            RoomVersionId::V7 => Self::V7,
            RoomVersionId::V8 => Self::V8,
            RoomVersionId::V9 => Self::V9,
            ver => return Err(Error::Unsupported(format!("found version `{}`", ver.as_str()))),
        })
    }
}
<|endoftext|>


<commit_before>//! Generate the site content.

// Standard library
use std::borrow::Cow::{Borrowed, Owned};
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

// Third party
use glob::{glob, Paths};
use pulldown_cmark as cmark;
use pulldown_cmark::{Event, Parser, Tag};
use syntect::highlighting::ThemeSet;

// First party
use crate::config::{Config, SyntaxOption};

/// Load the `Paths` for all markdown files in the specified content directory.
///
/// If the specified configuration values are wrong, we return a vector of all
/// the errors in the config as far as this is concerned.
///
/// TODO: move that validation elsewhere? Should probably extract a more general
/// site configuration validator.
fn load_markdown_paths(site_directory: &PathBuf, config: &Config) -> Result<Vec<PathBuf>, String> {
    let site = site_directory
        .to_str()
        .ok_or(format!("bad `site`: {:?}", site_directory));

    let directories = config.directories.content.to_str().ok_or(format!(
        "bad content directory: {:?}",
        config.directories.content
    ));

    let (site, directories) = match (site, directories) {
        (Err(s), Err(d)) => Err(s + "\n" + &d),
        (Err(s), Ok(_)) => Err(s),
        (Ok(_), Err(d)) => Err(d),
        (Ok(s), Ok(d)) => Ok((s, d)),
    }?;

    let content_glob = format!("{}/{}/**/*.md", site, directories);

    let paths = glob(&content_glob).map_err(|err| format!("{:?}", err))?;

    let (valid, errors): (Vec<Result<PathBuf, _>>, Vec<Result<_, String>>) = paths
        .map(|glob_result| glob_result.map_err(|err| format!("{:?}", err)))
        .partition(|path| path.is_ok());

    let errors: Vec<String> = errors.into_iter().map(|e| e.unwrap_err()).collect();
    if errors.len() == 0 {
        Ok(valid.into_iter().map(|v| v.unwrap()).collect())
    } else {
        Err(errors.join(",\n"))
    }
}

type LoadTuple<'a> = (&'a PathBuf, Result<String, String>);

fn load_content<'p>(paths: &'p [PathBuf]) -> Result<Vec<(&'p PathBuf, String)>, String> {
    let (contents, errs): (Vec<LoadTuple>, Vec<LoadTuple>) = paths
        .into_iter()
        .map(|path| {
            (
                path,
                std::fs::read_to_string(path).map_err(|e| format!("{:?}", e)),
            )
        })
        .partition(|(_, result)| result.is_ok());

    if errs.len() == 0 {
        Ok(contents
            .into_iter()
            .map(|(path, contents)| (path, contents.unwrap()))
            .collect())
    } else {
        Err(errs
            .into_iter()
            .map(|(_path, err)| err.unwrap_err())
            .collect::<Vec<String>>()
            .join(",\n"))
    }
}

/// Load the templates associated with each taxonomy.
fn load_templates(_site_directory: &PathBuf, _config: &Config) -> Result<Paths, String> {
    unimplemented!()
}

// TODO: put this somewhere else when extracting all the parser logic below into
// its own module.
#[derive(Debug)]
enum ParseState<'s> {
    CodeBlock(&'s syntect::parsing::SyntaxReference),
    PlainTextBlock,
    NonCode,
}

impl<'s> std::default::Default for ParseState<'s> {
    fn default() -> ParseState<'s> {
        ParseState::NonCode
    }
}

/// Generate content from a configuration.
pub fn build(site_directory: PathBuf) -> Result<(), String> {
    // NOTE: this is almost certainly not what we *ultimately* want here, but
    // it'll do as a starting point. There's a lot of `into_iter` and `collect`
    // happening here, which forces us to eagerly materialize a lot of these
    // collections rather than being able to deal with them in a more
    // "streaming" fashion.
    let config = Config::from_file(&PathBuf::from(&site_directory))?;
    let markdown_paths = load_markdown_paths(&site_directory, &config)?;
    let contents = load_content(&markdown_paths)?;

    // TODO: migrate this logic out elsewhere
    // TODO: when doing ^ take into account that we want to be able to avoid a
    // bunch of extra layers of indirection here. If we don't have a theme, that
    // translates to being in `SyntaxOption::Off` or `SyntaxOption::TagOnly`,
    // since the case where we can't *load* the theme is an error. What I want,
    // therefore, is to reflect that in the parsing behavior later.
    let theme = match &config.options.syntax {
        SyntaxOption::Highlight(theme) => {
            let theme_file = PathBuf::from("data").join(theme);
            let theme = ThemeSet::get_theme(theme_file).map_err(|err| format!("{:?}", err))?;
            Some(theme)
        }
        SyntaxOption::Off | SyntaxOption::TagOnly => None,
    };

    // TODO: generate these! Best move: on build, generate a `.packdump`, since
    // that seems to be what Syntect knows how to deal with.
    let syntax_dir = PathBuf::from("data/syntaxes");
    let syntax_set =
        syntect::parsing::SyntaxSet::load_from_folder(syntax_dir).expect("can't load syntaxes");

    let parsed_content: Vec<(&PathBuf, String)> = contents
        .into_iter()
        .map(|(path, content)| {
            let parser = Parser::new_ext(&content, pulldown_cmark::Options::all()).scan(
                ParseState::default(),
                |state, event| match event {
                    Event::Start(Tag::CodeBlock(ref language)) => match state {
                        ParseState::NonCode => {
                            *state = match syntax_set.find_syntax_by_name(&language) {
                                Some(syntax) => ParseState::CodeBlock(syntax),
                                None => ParseState::PlainTextBlock,
                            };

                            // We start every code block with `<pre><code class="...">`
                            // so that we always have semantically correct HTML. However, we
                            // don't add the language class if the language isn't set.
                            let content = if language.len() > 0 {
                                Owned(format!(r#"
"#, language))
                            } else {
                                Borrowed("
")
                            };

                            Some(Event::Html(content))
                        }
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            unreachable!("Bad event/state: {:?} with {:?}", event, state);
                        }
                    },

                    Event::End(Tag::CodeBlock(_)) => match state {
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            *state = ParseState::NonCode;
                            Some(Event::Html(Borrowed("
")))
                        }
                        ParseState::NonCode => {
                            unreachable!("Bad event/state: {:?} with {:?}", event, state);
                        }
                    },

                    // When we are in a text block, we *may* be in a code block,
                    // so we may also need to do whatever highlighting is
                    // specified.
                    Event::Text(s) => {
                        match state {
                            ParseState::CodeBlock(syntax) => match &theme {
                                Some(theme) => {
                                    let highlighted = syntect::html::highlighted_html_for_string(
                                        &s,
                                        &syntax_set,
                                        &syntax,
                                        theme,
                                    );

                                    Some(Event::Text(Owned(highlighted)))
                                }
                                None => {
                                    // TODO: implement tag-only parsing with syntect
                                    unimplemented!()
                                }
                            },
                            ParseState::PlainTextBlock => Some(Event::Text(s)),
                            ParseState::NonCode => Some(Event::Text(s)),
                        }
                    }

                    // TODO: accumulate footnote references and always put them
                    // at the end. Also, generate back-links for them, unless or
                    // until pulldown-cmark does so.
                    Event::FootnoteReference(s) => Some(Event::FootnoteReference(s)),

                    Event::Start(s) => Some(Event::Start(s)),
                    Event::End(e) => Some(Event::End(e)),
                    Event::InlineHtml(s) => Some(Event::InlineHtml(s)),
                    Event::Html(h) => Some(Event::Html(h)),
                    Event::SoftBreak => Some(Event::SoftBreak),
                    Event::HardBreak => Some(Event::HardBreak),
                },
            );

            // These numbers are derived from a brief survey of content in my
            // own website historically, including both code-heavy and
            // prose-heavy posts. It's probably worth exploring other corpora
            // to see if this is basically right.
            let length_bound_estimate = match &config.options.syntax {
                SyntaxOption::Off | SyntaxOption::TagOnly => 2,
                SyntaxOption::Highlight(_) => 8, // TODO: check other pages
            };

            let mut html = String::with_capacity(content.len() * length_bound_estimate);
            cmark::html::push_html(&mut html, parser);

            (path, html)
        })
        .collect();

    // TODO: extract this as part of the writing it out process.
    let output_dir = site_directory.join(&config.directories.output);

    if output_dir.exists() && !output_dir.is_dir() {
        return Err(format!("Output directory {:?} already exists!", output_dir));
    }

    if !output_dir.exists() {
        std::fs::create_dir(&output_dir).expect(&format!(
            "could not create output directory {:?}",
            &output_dir
        ));
    }

    for (path, content) in parsed_content {
        let file_path = path
            .file_name()
            .ok_or(format!("invalid file: {:?}", path))?;

        let dest = output_dir.join(file_path).with_extension("html");

        let mut fd = match File::create(&dest) {
            Ok(file) => file,
            Err(reason) => {
                return Err(format!(
                    "Could not open {} for write: {}",
                    dest.to_string_lossy(),
                    reason
                ));
            }
        };

        let result = write!(fd, "{}", content);
        if let Err(reason) = result {
            return Err(format!("{:?}", reason.kind()));
        }
    }

    Ok(())
}
<commit_msg>improvement: clean up `load_content`.<commit_after>//! Generate the site content.

// Standard library
use std::borrow::Cow::{Borrowed, Owned};
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

// Third party
use glob::{glob, Paths};
use pulldown_cmark as cmark;
use pulldown_cmark::{Event, Parser, Tag};
use syntect::highlighting::ThemeSet;

// First party
use crate::config::{Config, SyntaxOption};

/// Load the `Paths` for all markdown files in the specified content directory.
///
/// If the specified configuration values are wrong, we return a vector of all
/// the errors in the config as far as this is concerned.
///
/// TODO: move that validation elsewhere? Should probably extract a more general
/// site configuration validator.
fn load_markdown_paths(site_directory: &PathBuf, config: &Config) -> Result<Vec<PathBuf>, String> {
    let site = site_directory
        .to_str()
        .ok_or(format!("bad `site`: {:?}", site_directory));

    let directories = config.directories.content.to_str().ok_or(format!(
        "bad content directory: {:?}",
        config.directories.content
    ));

    let (site, directories) = match (site, directories) {
        (Err(s), Err(d)) => Err(s + "\n" + &d),
        (Err(s), Ok(_)) => Err(s),
        (Ok(_), Err(d)) => Err(d),
        (Ok(s), Ok(d)) => Ok((s, d)),
    }?;

    let content_glob = format!("{}/{}/**/*.md", site, directories);

    let paths = glob(&content_glob).map_err(|err| format!("{:?}", err))?;

    let (valid, errors): (Vec<Result<PathBuf, _>>, Vec<Result<_, String>>) = paths
        .map(|glob_result| glob_result.map_err(|err| format!("{:?}", err)))
        .partition(|path| path.is_ok());

    let errors: Vec<String> = errors.into_iter().map(|e| e.unwrap_err()).collect();
    if errors.len() == 0 {
        Ok(valid.into_iter().map(|v| v.unwrap()).collect())
    } else {
        Err(errors.join(",\n"))
    }
}

// TODO: this is an obvious candidate for a `HashMap`, so use that instead.
type LoadTuple<'a> = (&'a PathBuf, Result<String, String>);

fn load_content<'p>(paths: &'p [PathBuf]) -> Result<Vec<(&'p PathBuf, String)>, String> {
    let (contents, errs): (Vec<LoadTuple>, Vec<LoadTuple>) = paths
        .into_iter()
        .map(|path| {
            let content = std::fs::read_to_string(path).map_err(|e| format!("{:?}", e));
            (path, content)
        })
        .partition(|(_, result)| result.is_ok());

    if errs.len() == 0 {
        Ok(contents
            .into_iter()
            .map(|(path, contents)| (path, contents.unwrap()))
            .collect())
    } else {
        Err(errs
            .into_iter()
            .map(|(_path, err)| err.unwrap_err())
            .collect::<Vec<String>>()
            .join(",\n"))
    }
}

/// Load the templates associated with each taxonomy.
fn load_templates(_site_directory: &PathBuf, _config: &Config) -> Result<Paths, String> {
    unimplemented!()
}

// TODO: put this somewhere else when extracting all the parser logic below into
// its own module.
#[derive(Debug)]
enum ParseState<'s> {
    CodeBlock(&'s syntect::parsing::SyntaxReference),
    PlainTextBlock,
    NonCode,
}

impl<'s> std::default::Default for ParseState<'s> {
    fn default() -> ParseState<'s> {
        ParseState::NonCode
    }
}

/// Generate content from a configuration.
pub fn build(site_directory: PathBuf) -> Result<(), String> {
    // NOTE: this is almost certainly not what we *ultimately* want here, but
    // it'll do as a starting point. There's a lot of `into_iter` and `collect`
    // happening here, which forces us to eagerly materialize a lot of these
    // collections rather than being able to deal with them in a more
    // "streaming" fashion.
    let config = Config::from_file(&PathBuf::from(&site_directory))?;
    let markdown_paths = load_markdown_paths(&site_directory, &config)?;
    let contents = load_content(&markdown_paths)?;

    // TODO: migrate this logic out elsewhere
    // TODO: when doing ^ take into account that we want to be able to avoid a
    // bunch of extra layers of indirection here. If we don't have a theme, that
    // translates to being in `SyntaxOption::Off` or `SyntaxOption::TagOnly`,
    // since the case where we can't *load* the theme is an error. What I want,
    // therefore, is to reflect that in the parsing behavior later.
    let theme = match &config.options.syntax {
        SyntaxOption::Highlight(theme) => {
            let theme_file = PathBuf::from("data").join(theme);
            let theme = ThemeSet::get_theme(theme_file).map_err(|err| format!("{:?}", err))?;
            Some(theme)
        }
        SyntaxOption::Off | SyntaxOption::TagOnly => None,
    };

    // TODO: generate these! Best move: on build, generate a `.packdump`, since
    // that seems to be what Syntect knows how to deal with.
    let syntax_dir = PathBuf::from("data/syntaxes");
    let syntax_set =
        syntect::parsing::SyntaxSet::load_from_folder(syntax_dir).expect("can't load syntaxes");

    let parsed_content: Vec<(&PathBuf, String)> = contents
        .into_iter()
        .map(|(path, content)| {
            let parser = Parser::new_ext(&content, pulldown_cmark::Options::all()).scan(
                ParseState::default(),
                |state, event| match event {
                    Event::Start(Tag::CodeBlock(ref language)) => match state {
                        ParseState::NonCode => {
                            *state = match syntax_set.find_syntax_by_name(&language) {
                                Some(syntax) => ParseState::CodeBlock(syntax),
                                None => ParseState::PlainTextBlock,
                            };

                            // We start every code block with `<pre><code class="...">`
                            // so that we always have semantically correct HTML. However, we
                            // don't add the language class if the language isn't set.
                            let content = if language.len() > 0 {
                                Owned(format!(r#"
"#, language))
                            } else {
                                Borrowed("
")
                            };

                            Some(Event::Html(content))
                        }
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            unreachable!("Bad event/state: {:?} with {:?}", event, state);
                        }
                    },

                    Event::End(Tag::CodeBlock(_)) => match state {
                        ParseState::CodeBlock(_) | ParseState::PlainTextBlock => {
                            *state = ParseState::NonCode;
                            Some(Event::Html(Borrowed("
")))
                        }
                        ParseState::NonCode => {
                            unreachable!("Bad event/state: {:?} with {:?}", event, state);
                        }
                    },

                    // When we are in a text block, we *may* be in a code block,
                    // so we may also need to do whatever highlighting is
                    // specified.
                    Event::Text(s) => {
                        match state {
                            ParseState::CodeBlock(syntax) => match &theme {
                                Some(theme) => {
                                    let highlighted = syntect::html::highlighted_html_for_string(
                                        &s,
                                        &syntax_set,
                                        &syntax,
                                        theme,
                                    );

                                    Some(Event::Text(Owned(highlighted)))
                                }
                                None => {
                                    // TODO: implement tag-only parsing with syntect
                                    unimplemented!()
                                }
                            },
                            ParseState::PlainTextBlock => Some(Event::Text(s)),
                            ParseState::NonCode => Some(Event::Text(s)),
                        }
                    }

                    // TODO: accumulate footnote references and always put them
                    // at the end. Also, generate back-links for them, unless or
                    // until pulldown-cmark does so.
                    Event::FootnoteReference(s) => Some(Event::FootnoteReference(s)),

                    Event::Start(s) => Some(Event::Start(s)),
                    Event::End(e) => Some(Event::End(e)),
                    Event::InlineHtml(s) => Some(Event::InlineHtml(s)),
                    Event::Html(h) => Some(Event::Html(h)),
                    Event::SoftBreak => Some(Event::SoftBreak),
                    Event::HardBreak => Some(Event::HardBreak),
                },
            );

            // These numbers are derived from a brief survey of content in my
            // own website historically, including both code-heavy and
            // prose-heavy posts. It's probably worth exploring other corpora
            // to see if this is basically right.
            let length_bound_estimate = match &config.options.syntax {
                SyntaxOption::Off | SyntaxOption::TagOnly => 2,
                SyntaxOption::Highlight(_) => 8, // TODO: check other pages
            };

            let mut html = String::with_capacity(content.len() * length_bound_estimate);
            cmark::html::push_html(&mut html, parser);

            (path, html)
        })
        .collect();

    // TODO: extract this as part of the writing it out process.
    let output_dir = site_directory.join(&config.directories.output);

    if output_dir.exists() && !output_dir.is_dir() {
        return Err(format!("Output directory {:?} already exists!", output_dir));
    }

    if !output_dir.exists() {
        std::fs::create_dir(&output_dir).expect(&format!(
            "could not create output directory {:?}",
            &output_dir
        ));
    }

    for (path, content) in parsed_content {
        let file_path = path
            .file_name()
            .ok_or(format!("invalid file: {:?}", path))?;

        let dest = output_dir.join(file_path).with_extension("html");

        let mut fd = match File::create(&dest) {
            Ok(file) => file,
            Err(reason) => {
                return Err(format!(
                    "Could not open {} for write: {}",
                    dest.to_string_lossy(),
                    reason
                ));
            }
        };

        let result = write!(fd, "{}", content);
        if let Err(reason) = result {
            return Err(format!("{:?}", reason.kind()));
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>#![no_std]

use header::{Tag, TagIter};
pub use boot_loader_name::BootLoaderNameTag;
pub use elf_sections::{ElfSectionsTag, ElfSection, ElfSectionIter, ElfSectionType, ElfSectionFlags};
pub use elf_sections::{ELF_SECTION_WRITABLE, ELF_SECTION_ALLOCATED, ELF_SECTION_EXECUTABLE};
pub use memory_map::{MemoryMapTag, MemoryArea, MemoryAreaIter};
pub use module::{ModuleTag, ModuleIter};

#[macro_use]
extern crate bitflags;

mod header;
mod boot_loader_name;
mod elf_sections;
mod memory_map;
mod module;

pub unsafe fn load(address: usize) -> &'static BootInformation {
    let multiboot = &*(address as *const BootInformation);
    assert!(multiboot.has_valid_end_tag());
    multiboot
}

#[repr(C)]
pub struct BootInformation {
    pub total_size: u32,
    _reserved: u32,
    first_tag: Tag,
}

impl BootInformation {
    pub fn start_address(&self) -> usize {
        self as *const _ as usize
    }

    pub fn end_address(&self) -> usize {
        self.start_address() + self.total_size as usize
    }

    pub fn elf_sections_tag(&self) -> Option<&'static ElfSectionsTag> {
        self.get_tag(9).map(|tag| unsafe{&*(tag as *const Tag as *const ElfSectionsTag)})
    }

    pub fn memory_map_tag(&self) -> Option<&'static MemoryMapTag> {
        self.get_tag(6).map(|tag| unsafe{&*(tag as *const Tag as *const MemoryMapTag)})
    }

    pub fn module_tags(&self) -> ModuleIter {
        ModuleIter{ iter: self.tags() }
    }

    pub fn boot_loader_name_tag(&self) -> Option<&'static BootLoaderNameTag> {
        self.get_tag(2).map(|tag| unsafe{&*(tag as *const Tag as *const BootLoaderNameTag)})
    }

    fn has_valid_end_tag(&self) -> bool {
        const END_TAG: Tag = Tag{typ:0, size:8};

        let self_ptr = self as *const _;
        let end_tag_addr = self_ptr as usize + (self.total_size - END_TAG.size) as usize;
        let end_tag = unsafe{&*(end_tag_addr as *const Tag)};

        end_tag.typ == END_TAG.typ && end_tag.size == END_TAG.size
    }

    fn get_tag(&self, typ: u32) -> Option<&'static Tag> {
        self.tags().find(|tag| tag.typ == typ)
    }

    fn tags(&self) -> TagIter {
        TagIter{current: &self.first_tag as *const _}
    }
}
<commit_msg>Make StringTable public (#16)<commit_after>#![no_std]

use header::{Tag, TagIter};
pub use boot_loader_name::BootLoaderNameTag;
pub use elf_sections::{ElfSectionsTag, ElfSection, ElfSectionIter, ElfSectionType, ElfSectionFlags, StringTable};
pub use elf_sections::{ELF_SECTION_WRITABLE, ELF_SECTION_ALLOCATED, ELF_SECTION_EXECUTABLE};
pub use memory_map::{MemoryMapTag, MemoryArea, MemoryAreaIter};
pub use module::{ModuleTag, ModuleIter};

#[macro_use]
extern crate bitflags;

mod header;
mod boot_loader_name;
mod elf_sections;
mod memory_map;
mod module;

pub unsafe fn load(address: usize) -> &'static BootInformation {
    let multiboot = &*(address as *const BootInformation);
    assert!(multiboot.has_valid_end_tag());
    multiboot
}

#[repr(C)]
pub struct BootInformation {
    pub total_size: u32,
    _reserved: u32,
    first_tag: Tag,
}

impl BootInformation {
    pub fn start_address(&self) -> usize {
        self as *const _ as usize
    }

    pub fn end_address(&self) -> usize {
        self.start_address() + self.total_size as usize
    }

    pub fn elf_sections_tag(&self) -> Option<&'static ElfSectionsTag> {
        self.get_tag(9).map(|tag| unsafe{&*(tag as *const Tag as *const ElfSectionsTag)})
    }

    pub fn memory_map_tag(&self) -> Option<&'static MemoryMapTag> {
        self.get_tag(6).map(|tag| unsafe{&*(tag as *const Tag as *const MemoryMapTag)})
    }

    pub fn module_tags(&self) -> ModuleIter {
        ModuleIter{ iter: self.tags() }
    }

    pub fn boot_loader_name_tag(&self) -> Option<&'static BootLoaderNameTag> {
        self.get_tag(2).map(|tag| unsafe{&*(tag as *const Tag as *const BootLoaderNameTag)})
    }

    fn has_valid_end_tag(&self) -> bool {
        const END_TAG: Tag = Tag{typ:0, size:8};

        let self_ptr = self as *const _;
        let end_tag_addr = self_ptr as usize + (self.total_size - END_TAG.size) as usize;
        let end_tag = unsafe{&*(end_tag_addr as *const Tag)};

        end_tag.typ == END_TAG.typ && end_tag.size == END_TAG.size
    }

    fn get_tag(&self, typ: u32) -> Option<&'static Tag> {
        self.tags().find(|tag| tag.typ == typ)
    }

    fn tags(&self) -> TagIter {
        TagIter{current: &self.first_tag as *const _}
    }
}
<|endoftext|>


<commit_before>/*********************************************************************************
 * TODO:
 * Should query return a result? There is an unwrap there but it will only fail if
 * something goes very very wrong. Probably better to error though since this is a
 * library.
 *
 * Should this be generic over Hasher rather than BuildHasher? BuildHasher keeps it
 * consistent with HashMap but it seems the only reason to have BuildHasher is to
 * allow random initialization of seed values? All BuildHasher implementations should
 * have a Hasher available.
 *
 * Derive Clone? This seems to throw up an error in rustc.
 *
 * Write some tests
 *
 * Add scaleable bloom filter
 *
 * Write Docs
 *
 * Publish on crates.io
 *
 *
 * A good Benchmark / Test suite which shows false positive rate / memory usage / time
 * over a bunch of scenarios
 *
 ********************************************************************************/

/// BloomFilter is a fast, tested and benchmarked bloom filter library.

extern crate bit_vec;

use bit_vec::BitVec;
use std::hash::{Hash, SipHasher, Hasher, BuildHasher};
use std::fmt;

pub struct BloomFilter<H: Hasher> {
    size: u64,
    hash_count: u64,
    bloom: BitVec,
    hashers: [H; 2]
}

#[derive(Clone)]
pub struct SipBuilder {
    k0: u64,
    k1: u64,
}

impl SipBuilder {
    pub fn new() -> SipBuilder{
        SipBuilder { k0: 0, k1: 0 }
    }
}

impl BuildHasher for SipBuilder{
    type Hasher = SipHasher;

    #[inline]
    fn build_hasher(&self) -> SipHasher {
        SipHasher::new_with_keys(self.k0, self.k1)
    }
}

impl<H> fmt::Debug for BloomFilter<H> where H: Hasher + Clone + Default{

    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "BloomFilter: size: {:?}, hash_count: {:?}, fill ratio: {:?}",
               self.size, self.hash_count, self.get_bits_set() as f32/self.size as f32)
    }
}

impl BloomFilter<SipHasher> {

    /// Constructs a new `BloomFilter`
    ///
    /// Takes `size` and `hash_count`. These correspond to m and k in standard Bloom Filter
    /// Descriptions.
    ///
    /// `size`: The size of the bit vector being stored. (m)
    /// `hash_count`: The number of hash functions to use. (k)
    pub fn new(size: u64, hash_count: u64) -> BloomFilter<SipHasher> {
        BloomFilter::<SipHasher>::new_with_hasher(size, hash_count)
    }

    /// Constructs a new `BloomFilter` using desired error rate and number of items
    ///
    /// `n`: The number of items that are going to be stored in the bloom filter.
    /// `p`: The allowable error rate of false positives
    pub fn new_with_params(n: usize, p: f32) -> BloomFilter<SipHasher> {
        let m = ((-(n as f32 * (p.ln()))).ceil() / ((2.0f32).ln().powi(2))) as u64;
        let k = (((2.0f32).ln() * (m as f32/ n as f32)).round()) as u64;

        BloomFilter::<SipHasher>::new_with_hasher(m, k)
    }
}

impl<H> BloomFilter<H> where H: Hasher + Clone + Default {

    pub fn new_with_hasher(size: u64, hash_count: u64) -> BloomFilter<H> {
        BloomFilter {
            size: size,
            hash_count: hash_count,
            bloom: BitVec::from_elem(size as usize, false),
            hashers: [H::default(), H::default()]
        }
    }

    /// Add a Hashable type to the bloom filter.
    pub fn add<T: Hash>(&mut self, val: &T) {
        // Generate two random u64s for each hash and seed SipHaser with that.
        for n in 0..self.hash_count {
            let seed = n as u64;
            let hashed = self.bloom_hash(seed, &val);
            self.bloom.set(hashed, true);
        }
    }

    /// Query the bloom filter for some Hashable value.
    pub fn query<T: Hash>(&self, query_val: &T) -> bool{
        for n in 0..self.hash_count {
            let hashed = self.bloom_hash(n as u64, &query_val);
            if !self.bloom.get(hashed).unwrap() {
                return false;
            }
        }
        true
    }

    // Uses Hash[i] = (hash_64_part_0 * hash_64_part_1 + i) as per
    // http://spyced.blogspot.com/2009/01/all-you-ever-wanted-to-know-about.html
    fn bloom_hash<T: Hash>(&self, n: u64, val: &T) -> usize {
        let mut sip1 = self.hashers[0].clone();
        let mut sip2 = self.hashers[1].clone();
        val.hash(&mut sip1);
        val.hash(&mut sip2);
        let fin1 = sip1.finish();
        let fin2 = sip2.finish();
        (fin1.wrapping_mul(fin2.wrapping_add(n)) % (self.size as u64)) as usize
    }

    /// Returns the number of bits set in the bloom filter.
    ///
    /// This is an expensive operation, there's no reason to use it unless you're
    /// doing something strange
    pub fn get_bits_set(&self) -> usize {
        self.bloom.iter().filter(|x| *x).count()
    }

    /// Clears the bloom filter of all values.
    pub fn clear(&mut self) {
        self.bloom.clear();
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}

<commit_msg>Remove the BuildHasher implementation for SipHasher<commit_after>/*********************************************************************************
 * TODO:
 * Should query return a result? There is an unwrap there but it will only fail if
 * something goes very very wrong. Probably better to error though since this is a
 * library.
 *
 * Derive Clone? This seems to throw up an error in rustc.
 *
 * Write some tests
 *
 * Add scaleable bloom filter
 *
 * Write Docs
 *
 * Publish on crates.io
 *
 *
 * A good Benchmark / Test suite which shows false positive rate / memory usage / time
 * over a bunch of scenarios
 *
 ********************************************************************************/

/// BloomFilter is a fast, tested and benchmarked bloom filter library.

extern crate bit_vec;

use bit_vec::BitVec;
use std::hash::{Hash, SipHasher, Hasher};
use std::fmt;

pub struct BloomFilter<H: Hasher> {
    size: u64,
    hash_count: u64,
    bloom: BitVec,
    hashers: [H; 2]
}

impl<H> fmt::Debug for BloomFilter<H> where H: Hasher + Clone + Default{

    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "BloomFilter: size: {:?}, hash_count: {:?}, fill ratio: {:?}",
               self.size, self.hash_count, self.get_bits_set() as f32/self.size as f32)
    }
}

impl BloomFilter<SipHasher> {

    /// Constructs a new `BloomFilter`
    ///
    /// Takes `size` and `hash_count`. These correspond to m and k in standard Bloom Filter
    /// Descriptions.
    ///
    /// `size`: The size of the bit vector being stored. (m)
    /// `hash_count`: The number of hash functions to use. (k)
    pub fn new(size: u64, hash_count: u64) -> BloomFilter<SipHasher> {
        BloomFilter::<SipHasher>::new_with_hasher(size, hash_count)
    }

    /// Constructs a new `BloomFilter` using desired error rate and number of items
    ///
    /// `n`: The number of items that are going to be stored in the bloom filter.
    /// `p`: The allowable error rate of false positives
    pub fn new_with_params(n: usize, p: f32) -> BloomFilter<SipHasher> {
        let m = ((-(n as f32 * (p.ln()))).ceil() / ((2.0f32).ln().powi(2))) as u64;
        let k = (((2.0f32).ln() * (m as f32/ n as f32)).round()) as u64;

        BloomFilter::<SipHasher>::new_with_hasher(m, k)
    }
}

impl<H> BloomFilter<H> where H: Hasher + Clone + Default {

    pub fn new_with_hasher(size: u64, hash_count: u64) -> BloomFilter<H> {
        BloomFilter {
            size: size,
            hash_count: hash_count,
            bloom: BitVec::from_elem(size as usize, false),
            hashers: [H::default(), H::default()]
        }
    }

    /// Add a Hashable type to the bloom filter.
    pub fn add<T: Hash>(&mut self, val: &T) {
        // Generate two random u64s for each hash and seed SipHaser with that.
        for n in 0..self.hash_count {
            let seed = n as u64;
            let hashed = self.bloom_hash(seed, &val);
            self.bloom.set(hashed, true);
        }
    }

    /// Query the bloom filter for some Hashable value.
    pub fn query<T: Hash>(&self, query_val: &T) -> bool{
        for n in 0..self.hash_count {
            let hashed = self.bloom_hash(n as u64, &query_val);
            if !self.bloom.get(hashed).unwrap() {
                return false;
            }
        }
        true
    }

    // Uses Hash[i] = (hash_64_part_0 * hash_64_part_1 + i) as per
    // http://spyced.blogspot.com/2009/01/all-you-ever-wanted-to-know-about.html
    fn bloom_hash<T: Hash>(&self, n: u64, val: &T) -> usize {
        let mut sip1 = self.hashers[0].clone();
        let mut sip2 = self.hashers[1].clone();
        val.hash(&mut sip1);
        val.hash(&mut sip2);
        let fin1 = sip1.finish();
        let fin2 = sip2.finish();
        (fin1.wrapping_mul(fin2.wrapping_add(n)) % (self.size as u64)) as usize
    }

    /// Returns the number of bits set in the bloom filter.
    ///
    /// This is an expensive operation, there's no reason to use it unless you're
    /// doing something strange
    pub fn get_bits_set(&self) -> usize {
        self.bloom.iter().filter(|x| *x).count()
    }

    /// Clears the bloom filter of all values.
    pub fn clear(&mut self) {
        self.bloom.clear();
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}

<|endoftext|>


<commit_before>extern crate serial;
#[macro_use] extern crate serde_derive;
extern crate serde;

use serde::ser::{Serializer, Serialize};
use serde::de::{self, Deserializer, Deserialize};

pub use serial::Error;

mod enttec;

pub use enttec::{available_enttec_ports, EnttecDmxPort, ENTTEC_NAMESPACE};

/// Trait for the general notion of a DMX port.
/// This enables creation of an "offline" port to slot into place if an API requires an output.
pub trait DmxPort {
    /// Write a DMX frame out to the port.  If the frame is smaller than the minimum universe size,
    /// it will be padded with zeros.  If the frame is larger than the maximum universe size, the
    /// values beyond the max size will be ignored.
    fn write(&mut self, frame: &[u8]) -> Result<(), Error>;

    /// Return the name of this port.  Should only be used for display purposes.
    fn port_name(&self) -> &str;

    /// Return a tuple of (namespace, port_identifier) to be used to try to reopen a port
    /// after deserialization of a saved show or after application restart.
    fn serializable(&self) -> SerializablePort;
}

pub struct OfflineDmxPort {}

const OFFLINE_NAMESPACE: &'static str = "offline";
const OFFLINE_ID: &'static str = "offline";

impl DmxPort for OfflineDmxPort {
    fn write(&mut self, _: &[u8]) -> Result<(), Error> {
        Ok(())
    }
    fn port_name(&self) -> &str {
        OFFLINE_ID
    }
    fn serializable(&self) -> SerializablePort {
        SerializablePort::new(OFFLINE_NAMESPACE, OFFLINE_ID)
    }
}

#[derive(Serialize, Deserialize)]
/// A serializable data structure for persisting a record of a port to disk, also providing
/// for attempted reopening of a port.
pub struct SerializablePort {
    namespace: String,
    id: String,
}

impl SerializablePort {
    fn new<N: Into<String>, I: Into<String>>(namespace: N, id: I) -> Self {
        SerializablePort { namespace: namespace.into(), id: id.into() }
    }
    /// Based on the namespace and id, try to reopen this DMX port.
    /// If we don't know the namespace or the port isn't available, return an offline port.
    fn reopen(self) -> Box<DmxPort> {
        fn offline() -> Box<DmxPort> {
            Box::new(OfflineDmxPort{})
        }
        match self.namespace.as_ref() {
            OFFLINE_NAMESPACE => offline(),
            ENTTEC_NAMESPACE =>
                match EnttecDmxPort::new(self.id) {
                    Ok(port) => Box::new(port),
                    Err(_) => offline()
                },
            _ => offline()
        }
    }
}

// Helper functions to use when serializing and deserializing DmxPort trait objects contained in
// other structs.  This can be done using the serde with attribute.
pub fn serialize<S>(port: &DmxPort, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer
{
    port.serializable().serialize(serializer)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<DmxPort>, D::Error>
    where D: Deserializer<'de>
{
    SerializablePort::deserialize(deserializer).map(SerializablePort::reopen)
}<commit_msg>ENH: add port serialization support through the trait object<commit_after>extern crate serial;
#[macro_use] extern crate serde_derive;
extern crate serde;

use serde::ser::{Serializer, Serialize};
use serde::de::{self, Deserializer, Deserialize};

pub use serial::Error;

mod enttec;

pub use enttec::{available_enttec_ports, EnttecDmxPort, ENTTEC_NAMESPACE};

/// Trait for the general notion of a DMX port.
/// This enables creation of an "offline" port to slot into place if an API requires an output.
pub trait DmxPort {
    /// Write a DMX frame out to the port.  If the frame is smaller than the minimum universe size,
    /// it will be padded with zeros.  If the frame is larger than the maximum universe size, the
    /// values beyond the max size will be ignored.
    fn write(&mut self, frame: &[u8]) -> Result<(), Error>;

    /// Return the name of this port.  Should only be used for display purposes.
    fn port_name(&self) -> &str;

    /// Return a tuple of (namespace, port_identifier) to be used to try to reopen a port
    /// after deserialization of a saved show or after application restart.
    fn serializable(&self) -> SerializablePort;
}

pub struct OfflineDmxPort {}

const OFFLINE_NAMESPACE: &'static str = "offline";
const OFFLINE_ID: &'static str = "offline";

impl DmxPort for OfflineDmxPort {
    fn write(&mut self, _: &[u8]) -> Result<(), Error> {
        Ok(())
    }
    fn port_name(&self) -> &str {
        OFFLINE_ID
    }
    fn serializable(&self) -> SerializablePort {
        SerializablePort::new(OFFLINE_NAMESPACE, OFFLINE_ID)
    }
}

#[derive(Serialize, Deserialize)]
/// A serializable data structure for persisting a record of a port to disk, also providing
/// for attempted reopening of a port.
pub struct SerializablePort {
    namespace: String,
    id: String,
}

impl SerializablePort {
    fn new<N: Into<String>, I: Into<String>>(namespace: N, id: I) -> Self {
        SerializablePort { namespace: namespace.into(), id: id.into() }
    }
    /// Based on the namespace and id, try to reopen this DMX port.
    /// If we don't know the namespace or the port isn't available, return an offline port.
    fn reopen(self) -> Box<DmxPort> {
        fn offline() -> Box<DmxPort> {
            Box::new(OfflineDmxPort{})
        }
        match self.namespace.as_ref() {
            OFFLINE_NAMESPACE => offline(),
            ENTTEC_NAMESPACE =>
                match EnttecDmxPort::new(self.id) {
                    Ok(port) => Box::new(port),
                    Err(_) => offline()
                },
            _ => offline()
        }
    }
}

// Helper functions to use when serializing and deserializing DmxPort trait objects contained in
// other structs.  This can be done using the serde with attribute.
pub fn serialize<S>(port: &Box<DmxPort>, serializer: S) -> Result<S::Ok, S::Error>
    where S: Serializer
{
    port.serializable().serialize(serializer)
}

pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<DmxPort>, D::Error>
    where D: Deserializer<'de>
{
    SerializablePort::deserialize(deserializer).map(SerializablePort::reopen)
}<|endoftext|>


<commit_before>#![deny(missing_docs)]

//! The official Piston window wrapper for the Piston game engine
//!
//! The purpose of this library is to provide an easy-to-use,
//! simple-to-get-started and convenient-for-applications API for Piston.
//!
//! Sets up:
//!
//! - [Gfx](https://github.com/gfx-rs/gfx) with an OpenGL back-end.
//! - [gfx_graphics](https://github.com/pistondevelopers/gfx_graphics)
//! for 2D rendering.
//! - [glutin_window](https://github.com/pistondevelopers/glutin_window)
//! as default window back-end, but this can be swapped (see below).
//!
//! ### Example
//!
//! ```no_run
//! extern crate piston_window;
//!
//! use piston_window::*;
//!
//! fn main() {
//!     let mut window: PistonWindow =
//!         WindowSettings::new("Hello World!", [512; 2])
//!             .build().unwrap();
//!     let mut events = window.events();
//!     while let Some(e) = events.next(&mut window) {
//!         window.draw_2d(&e, |c, g| {
//!             clear([0.5, 0.5, 0.5, 1.0], g);
//!             rectangle([1.0, 0.0, 0.0, 1.0], // red
//!                       [0.0, 0.0, 100.0, 100.0], // rectangle
//!                       c.transform, g);
//!         });
//!     }
//! }
//! ```
//!
//! ### Swap to another window back-end
//!
//! Change the second generic parameter to the window back-end you want to use.
//!
//! ```no_run
//! extern crate piston_window;
//!
//! use piston_window::*;
//!
//! # fn main() {
//!
//! let window: PistonWindow =
//!     WindowSettings::new("title", [512; 2])
//!         .build().unwrap();
//!
//! # }
//! ```
//!
//! ### sRGB
//!
//! The impl of `BuildFromWindowSettings` in this library turns on
//! `WindowSettings::srgb`, because it is required by gfx_graphics.
//!
//! Most images such as those found on the internet uses sRGB,
//! that has a non-linear gamma corrected space.
//! When rendering 3D, make sure textures and colors are in linear gamma space.
//! Alternative is to use `Srgb8` and `Srgba8` formats for textures.
//!
//! For more information about sRGB, see
//! https://github.com/PistonDevelopers/piston/issues/1014
//!
//! ### Library dependencies
//!
//! This library is not meant to be depended on by other libraries.
//! Instead, libraries should depend on the lower abstractions,
//! such as the [Piston core](https://github.com/pistondevelopers/piston).

extern crate piston;
extern crate gfx;
extern crate gfx_device_gl;
extern crate gfx_graphics;
extern crate graphics;
extern crate shader_version;
extern crate glutin_window;

use glutin_window::GlutinWindow;
pub use shader_version::OpenGL;
pub use graphics::*;
pub use piston::window::*;
pub use piston::input::*;
pub use piston::event_loop::*;
pub use gfx_graphics::{ GlyphError, Texture, TextureSettings, Flip };

use gfx_graphics::{ Gfx2d, GfxGraphics };

/// Actual gfx::Stream implementation carried by the window.
pub type GfxEncoder = gfx::Encoder<gfx_device_gl::Resources,
    gfx_device_gl::CommandBuffer>;
/// Glyph cache.
pub type Glyphs = gfx_graphics::GlyphCache<gfx_device_gl::Resources,
    gfx_device_gl::Factory>;
/// 2D graphics.
pub type G2d<'a> = GfxGraphics<'a,
    gfx_device_gl::Resources,
    gfx_device_gl::CommandBuffer>;
/// Texture type compatible with `G2d`.
pub type G2dTexture<'a> = Texture<gfx_device_gl::Resources>;

/// Contains everything required for controlling window, graphics, event loop.
pub struct PistonWindow<T = (), W: Window = GlutinWindow> {
    /// The window.
    pub window: W,
    /// GFX encoder.
    pub encoder: GfxEncoder,
    /// GFX device.
    pub device: gfx_device_gl::Device,
    /// Output frame buffer.
    pub output_color: gfx::handle::RenderTargetView<
        gfx_device_gl::Resources, gfx::format::Srgba8>,
    /// Output stencil buffer.
    pub output_stencil: gfx::handle::DepthStencilView<
        gfx_device_gl::Resources, gfx::format::DepthStencil>,
    /// Gfx2d.
    pub g2d: Gfx2d<gfx_device_gl::Resources>,
    /// Application structure.
    pub app: T,
    /// The factory that was created along with the device.
    pub factory: gfx_device_gl::Factory,
}

impl<W> BuildFromWindowSettings for PistonWindow<(), W>
    where W: Window + OpenGLWindow + BuildFromWindowSettings,
          W::Event: GenericEvent
{
    fn build_from_window_settings(mut settings: WindowSettings)
    -> Result<PistonWindow<(), W>, String> {
        // Turn on sRGB.
        settings = settings.srgb(true);

        // Use OpenGL 3.2 by default, because this is what window backends
        // usually do.
        let opengl = settings.get_maybe_opengl().unwrap_or(OpenGL::V3_2);
        let samples = settings.get_samples();

        Ok(PistonWindow::new(opengl, samples,
            try!(settings.build()), ()))
    }
}

fn create_main_targets(dim: gfx::tex::Dimensions) ->
(gfx::handle::RenderTargetView<
    gfx_device_gl::Resources, gfx::format::Srgba8>,
 gfx::handle::DepthStencilView<
    gfx_device_gl::Resources, gfx::format::DepthStencil>) {
    use gfx::core::factory::Typed;
    use gfx::format::{DepthStencil, Format, Formatted, Srgba8};

    let color_format: Format = <Srgba8 as Formatted>::get_format();
    let depth_format: Format = <DepthStencil as Formatted>::get_format();
    let (output_color, output_stencil) =
        gfx_device_gl::create_main_targets_raw(dim,
                                               color_format.0,
                                               depth_format.0);
    let output_color = Typed::new(output_color);
    let output_stencil = Typed::new(output_stencil);
    (output_color, output_stencil)
}

impl<T, W> PistonWindow<T, W>
    where W: Window, W::Event: GenericEvent
{
    /// Creates a new piston window.
    pub fn new(
        opengl: OpenGL,
        samples: u8,
        mut window: W,
        app: T
    ) -> Self
        where W: OpenGLWindow
    {
        use piston::window::{ OpenGLWindow, Window };
        use gfx::core::factory::Typed;

        let (device, mut factory) =
            gfx_device_gl::create(|s|
                window.get_proc_address(s) as *const _);

        let (output_color, output_stencil) = {
            let aa = samples as gfx::tex::NumSamples;
            let draw_size = window.draw_size();
            let dim = (draw_size.width as u16, draw_size.height as u16,
                       1, aa.into());
            create_main_targets(dim)
        };

        let g2d = Gfx2d::new(opengl, &mut factory);
        let encoder = factory.create_command_buffer().into();
        PistonWindow {
            window: window,
            encoder: encoder,
            device: device,
            output_color: output_color,
            output_stencil: output_stencil,
            g2d: g2d,
            app: app,
            factory: factory,
        }
    }

    /// Changes application structure.
    pub fn app<U>(self, app: U) -> PistonWindow<U, W> {
        PistonWindow {
            window: self.window,
            encoder: self.encoder,
            device: self.device,
            output_color: self.output_color,
            output_stencil: self.output_stencil,
            g2d: self.g2d,
            app: app,
            factory: self.factory,
        }
    }

    /// Renders 2D graphics.
    pub fn draw_2d<F, E>(&mut self, e: &E, f: F) where
        F: FnOnce(Context, &mut G2d),
        E: GenericEvent
    {
        use piston::input::RenderEvent;

        if let Some(args) = e.render_args() {
            self.g2d.draw(
                &mut self.encoder,
                &self.output_color,
                &self.output_stencil,
                args.viewport(),
                f
            );
            self.encoder.flush(&mut self.device);
        }
    }

    /// Renders 3D graphics.
    pub fn draw_3d<F, E>(&mut self, e: &E, f: F) where
        F: FnOnce(&mut GfxEncoder),
        E: GenericEvent
    {
        use piston::input::RenderEvent;

        if let Some(_) = e.render_args() {
            f(&mut self.encoder);
            self.encoder.flush(&mut self.device);
        }
    }
}

impl<T, W> Window for PistonWindow<T, W>
    where W: Window
{
    type Event = <W as Window>::Event;

    fn should_close(&self) -> bool { self.window.should_close() }
    fn set_should_close(&mut self, value: bool) {
        self.window.set_should_close(value)
    }
    fn size(&self) -> Size { self.window.size() }
    fn draw_size(&self) -> Size { self.window.draw_size() }
    fn swap_buffers(&mut self) { self.window.swap_buffers() }
    fn poll_event(&mut self) -> Option<Self::Event> {
        Window::poll_event(&mut self.window)
    }
}

impl<T, W> AdvancedWindow for PistonWindow<T, W>
    where W: AdvancedWindow
{
    fn get_title(&self) -> String { self.window.get_title() }
    fn set_title(&mut self, title: String) {
        self.window.set_title(title)
    }
    fn get_exit_on_esc(&self) -> bool { self.window.get_exit_on_esc() }
    fn set_exit_on_esc(&mut self, value: bool) {
        self.window.set_exit_on_esc(value)
    }
    fn set_capture_cursor(&mut self, value: bool) {
        self.window.set_capture_cursor(value)
    }
}
<commit_msg>Minor fixes<commit_after>#![deny(missing_docs)]

//! The official Piston window wrapper for the Piston game engine
//!
//! The purpose of this library is to provide an easy-to-use,
//! simple-to-get-started and convenient-for-applications API for Piston.
//!
//! Sets up:
//!
//! - [Gfx](https://github.com/gfx-rs/gfx) with an OpenGL back-end.
//! - [gfx_graphics](https://github.com/pistondevelopers/gfx_graphics)
//! for 2D rendering.
//! - [glutin_window](https://github.com/pistondevelopers/glutin_window)
//! as default window back-end, but this can be swapped (see below).
//!
//! ### Example
//!
//! ```no_run
//! extern crate piston_window;
//!
//! use piston_window::*;
//!
//! fn main() {
//!     let mut window: PistonWindow =
//!         WindowSettings::new("Hello World!", [512; 2])
//!             .build().unwrap();
//!     let mut events = window.events();
//!     while let Some(e) = events.next(&mut window) {
//!         window.draw_2d(&e, |c, g| {
//!             clear([0.5, 0.5, 0.5, 1.0], g);
//!             rectangle([1.0, 0.0, 0.0, 1.0], // red
//!                       [0.0, 0.0, 100.0, 100.0], // rectangle
//!                       c.transform, g);
//!         });
//!     }
//! }
//! ```
//!
//! ### Swap to another window back-end
//!
//! Change the second generic parameter to the window back-end you want to use.
//!
//! ```ignore
//! extern crate piston_window;
//! extern crate sdl2_window;
//!
//! use piston_window::*;
//! use sdl2_window::Sdl2Window;
//!
//! # fn main() {
//!
//! let window: PistonWindow<(), Sdl2Window> =
//!     WindowSettings::new("title", [512; 2])
//!         .build().unwrap();
//!
//! # }
//! ```
//!
//! ### sRGB
//!
//! The impl of `BuildFromWindowSettings` in this library turns on
//! `WindowSettings::srgb`, because it is required by gfx_graphics.
//!
//! Most images such as those found on the internet uses sRGB,
//! that has a non-linear gamma corrected space.
//! When rendering 3D, make sure textures and colors are in linear gamma space.
//! Alternative is to use `Srgb8` and `Srgba8` formats for textures.
//!
//! For more information about sRGB, see
//! https://github.com/PistonDevelopers/piston/issues/1014
//!
//! ### Library dependencies
//!
//! This library is not meant to be depended on by other libraries.
//! Instead, libraries should depend on the lower abstractions,
//! such as the [Piston core](https://github.com/pistondevelopers/piston).

extern crate piston;
extern crate gfx;
extern crate gfx_device_gl;
extern crate gfx_graphics;
extern crate graphics;
extern crate shader_version;
extern crate glutin_window;

use glutin_window::GlutinWindow;
pub use shader_version::OpenGL;
pub use graphics::*;
pub use piston::window::*;
pub use piston::input::*;
pub use piston::event_loop::*;
pub use gfx_graphics::{ GlyphError, Texture, TextureSettings, Flip };

use gfx_graphics::{ Gfx2d, GfxGraphics };

/// Actual gfx::Stream implementation carried by the window.
pub type GfxEncoder = gfx::Encoder<gfx_device_gl::Resources,
    gfx_device_gl::CommandBuffer>;
/// Glyph cache.
pub type Glyphs = gfx_graphics::GlyphCache<gfx_device_gl::Resources,
    gfx_device_gl::Factory>;
/// 2D graphics.
pub type G2d<'a> = GfxGraphics<'a,
    gfx_device_gl::Resources,
    gfx_device_gl::CommandBuffer>;
/// Texture type compatible with `G2d`.
pub type G2dTexture<'a> = Texture<gfx_device_gl::Resources>;

/// Contains everything required for controlling window, graphics, event loop.
pub struct PistonWindow<T = (), W: Window = GlutinWindow> {
    /// The window.
    pub window: W,
    /// GFX encoder.
    pub encoder: GfxEncoder,
    /// GFX device.
    pub device: gfx_device_gl::Device,
    /// Output frame buffer.
    pub output_color: gfx::handle::RenderTargetView<
        gfx_device_gl::Resources, gfx::format::Srgba8>,
    /// Output stencil buffer.
    pub output_stencil: gfx::handle::DepthStencilView<
        gfx_device_gl::Resources, gfx::format::DepthStencil>,
    /// Gfx2d.
    pub g2d: Gfx2d<gfx_device_gl::Resources>,
    /// Application structure.
    pub app: T,
    /// The factory that was created along with the device.
    pub factory: gfx_device_gl::Factory,
}

impl<W> BuildFromWindowSettings for PistonWindow<(), W>
    where W: Window + OpenGLWindow + BuildFromWindowSettings,
          W::Event: GenericEvent
{
    fn build_from_window_settings(mut settings: WindowSettings)
    -> Result<PistonWindow<(), W>, String> {
        // Turn on sRGB.
        settings = settings.srgb(true);

        // Use OpenGL 3.2 by default, because this is what window backends
        // usually do.
        let opengl = settings.get_maybe_opengl().unwrap_or(OpenGL::V3_2);
        let samples = settings.get_samples();

        Ok(PistonWindow::new(opengl, samples,
            try!(settings.build()), ()))
    }
}

fn create_main_targets(dim: gfx::tex::Dimensions) ->
(gfx::handle::RenderTargetView<
    gfx_device_gl::Resources, gfx::format::Srgba8>,
 gfx::handle::DepthStencilView<
    gfx_device_gl::Resources, gfx::format::DepthStencil>) {
    use gfx::core::factory::Typed;
    use gfx::format::{DepthStencil, Format, Formatted, Srgba8};

    let color_format: Format = <Srgba8 as Formatted>::get_format();
    let depth_format: Format = <DepthStencil as Formatted>::get_format();
    let (output_color, output_stencil) =
        gfx_device_gl::create_main_targets_raw(dim,
                                               color_format.0,
                                               depth_format.0);
    let output_color = Typed::new(output_color);
    let output_stencil = Typed::new(output_stencil);
    (output_color, output_stencil)
}

impl<T, W> PistonWindow<T, W>
    where W: Window, W::Event: GenericEvent
{
    /// Creates a new piston window.
    pub fn new(
        opengl: OpenGL,
        samples: u8,
        mut window: W,
        app: T
    ) -> Self
        where W: OpenGLWindow
    {
        use piston::window::{ OpenGLWindow, Window };
        use gfx::core::factory::Typed;

        let (device, mut factory) =
            gfx_device_gl::create(|s|
                window.get_proc_address(s) as *const _);

        let (output_color, output_stencil) = {
            let aa = samples as gfx::tex::NumSamples;
            let draw_size = window.draw_size();
            let dim = (draw_size.width as u16, draw_size.height as u16,
                       1, aa.into());
            create_main_targets(dim)
        };

        let g2d = Gfx2d::new(opengl, &mut factory);
        let encoder = factory.create_command_buffer().into();
        PistonWindow {
            window: window,
            encoder: encoder,
            device: device,
            output_color: output_color,
            output_stencil: output_stencil,
            g2d: g2d,
            app: app,
            factory: factory,
        }
    }

    /// Changes application structure.
    pub fn app<U>(self, app: U) -> PistonWindow<U, W> {
        PistonWindow {
            window: self.window,
            encoder: self.encoder,
            device: self.device,
            output_color: self.output_color,
            output_stencil: self.output_stencil,
            g2d: self.g2d,
            app: app,
            factory: self.factory,
        }
    }

    /// Renders 2D graphics.
    pub fn draw_2d<E, F>(&mut self, e: &E, f: F) where
        E: GenericEvent,
        F: FnOnce(Context, &mut G2d)
    {
        use piston::input::RenderEvent;

        if let Some(args) = e.render_args() {
            self.g2d.draw(
                &mut self.encoder,
                &self.output_color,
                &self.output_stencil,
                args.viewport(),
                f
            );
            self.encoder.flush(&mut self.device);
        }
    }

    /// Renders 3D graphics.
    pub fn draw_3d<E, F>(&mut self, e: &E, f: F) where
        E: GenericEvent,
        F: FnOnce(&mut GfxEncoder)
    {
        use piston::input::RenderEvent;

        if let Some(_) = e.render_args() {
            f(&mut self.encoder);
            self.encoder.flush(&mut self.device);
        }
    }
}

impl<T, W> Window for PistonWindow<T, W>
    where W: Window
{
    type Event = <W as Window>::Event;

    fn should_close(&self) -> bool { self.window.should_close() }
    fn set_should_close(&mut self, value: bool) {
        self.window.set_should_close(value)
    }
    fn size(&self) -> Size { self.window.size() }
    fn draw_size(&self) -> Size { self.window.draw_size() }
    fn swap_buffers(&mut self) { self.window.swap_buffers() }
    fn poll_event(&mut self) -> Option<Self::Event> {
        Window::poll_event(&mut self.window)
    }
}

impl<T, W> AdvancedWindow for PistonWindow<T, W>
    where W: AdvancedWindow
{
    fn get_title(&self) -> String { self.window.get_title() }
    fn set_title(&mut self, title: String) {
        self.window.set_title(title)
    }
    fn get_exit_on_esc(&self) -> bool { self.window.get_exit_on_esc() }
    fn set_exit_on_esc(&mut self, value: bool) {
        self.window.set_exit_on_esc(value)
    }
    fn set_capture_cursor(&mut self, value: bool) {
        self.window.set_capture_cursor(value)
    }
}
<|endoftext|>


<commit_before>extern crate regex;
use std::path::Path;
use std::fs::File;
use std::io::Read;

mod event;
mod output;
mod sass;
mod tokenizer;
mod top_level_event;
mod variable_mapper;

use tokenizer::Tokenizer;

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

#[derive(Debug)]
pub struct Error {
    pub message: String,
    pub kind: ErrorKind,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Error {
        Error { message: format!("IO error! {}", err), kind: ErrorKind::IoError }
    }
}

#[derive(Debug)]
pub enum ErrorKind {
    IoError,
    InvalidStyle,
}

pub fn compile(inputfile: &str, style: &str) -> Result<String> {
    let mut file = try!(File::open(&Path::new(inputfile)));
    let mut sass = String::new();

    try!(file.read_to_string(&mut sass));
    let mut tokenizer = Tokenizer::new(&sass);
    match style {
        "nested"     => Ok(output::nested(&mut tokenizer)),
        "compressed" => Ok(output::compressed(&mut tokenizer)),
        "expanded"   => Ok(output::expanded(&mut tokenizer)),
        "compact"    => Ok(output::compact(&mut tokenizer)),
        "debug"      => Ok(output::debug(&mut tokenizer)),
        _            => {
            Err(Error {
                kind: ErrorKind::InvalidStyle,
                message: format!("Unknown style {:?}. Please specify one of nested, compressed, expanded, or compact.", style),
            })
        },
    }
}
<commit_msg>Read one level of imports into the string before tokenizing<commit_after>extern crate regex;
use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::Read;
use regex::Regex;

mod event;
mod output;
mod sass;
mod tokenizer;
mod top_level_event;
mod variable_mapper;

use tokenizer::Tokenizer;

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

#[derive(Debug)]
pub struct Error {
    pub message: String,
    pub kind: ErrorKind,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Error {
        Error { message: format!("IO error! {}", err), kind: ErrorKind::IoError }
    }
}

#[derive(Debug)]
pub enum ErrorKind {
    IoError,
    InvalidStyle,
}

fn read_import(inputfile: &PathBuf, buffer: &mut String) {
    match File::open(&Path::new(inputfile)) {
        Ok(ref mut file) => {
            match file.read_to_string(buffer) {
                Ok(..) => {},
                Err(e) => panic!("Can't read import file! Filename: {:?} Error: {}", inputfile, e),
            }
        },
        Err(e) => panic!("Can't open import file! Filename: {:?} Error: {}", inputfile, e),
    }
}

pub fn compile(inputfile: &str, style: &str) -> Result<String> {
    let inputpath = PathBuf::from(inputfile);
    let mut file = try!(File::open(&inputpath));
    let mut sass = String::new();

    try!(file.read_to_string(&mut sass));

    let mut imports_resolved = String::new();
    for line in sass.split("\n") {
        let re = Regex::new("@import \"([^\"]*)\";").unwrap();

        match re.captures(line) {
            Some(caps) => {
                read_import(&inputpath.with_file_name(caps.at(1).unwrap()), &mut imports_resolved);
            },
            None => {
                imports_resolved.push_str(line);
                imports_resolved.push_str("\n");
            },
        }
    }

    let mut tokenizer = Tokenizer::new(&imports_resolved);
    match style {
        "nested"     => Ok(output::nested(&mut tokenizer)),
        "compressed" => Ok(output::compressed(&mut tokenizer)),
        "expanded"   => Ok(output::expanded(&mut tokenizer)),
        "compact"    => Ok(output::compact(&mut tokenizer)),
        "debug"      => Ok(output::debug(&mut tokenizer)),
        _            => {
            Err(Error {
                kind: ErrorKind::InvalidStyle,
                message: format!("Unknown style {:?}. Please specify one of nested, compressed, expanded, or compact.", style),
            })
        },
    }
}
<|endoftext|>