<commit_before>use crate::errors::*;
use crate::registry::registry_url;
use crate::VersionExt;
use crate::{Dependency, LocalManifest, Manifest};
use regex::Regex;
use std::env;
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use url::Url;

/// Query latest version from a registry index
///
/// The registry argument must be specified for crates
/// from alternative registries.
///
/// The latest version will be returned as a `Dependency`. This will fail, when
///
/// - there is no Internet connection and offline is false.
/// - summaries in registry index with an incorrect format.
/// - a crate with the given name does not exist on the registry.
pub fn get_latest_dependency(
    crate_name: &str,
    flag_allow_prerelease: bool,
    manifest_path: &Path,
    registry: &Option<Url>,
) -> Result<Dependency> {
    if env::var("CARGO_IS_TEST").is_ok() {
        // We are in a simulated reality. Nothing is real here.
        // FIXME: Use actual test handling code.
        let new_version = if flag_allow_prerelease {
            format!("99999.0.0-alpha.1+{}", crate_name)
        } else {
            match crate_name {
                "test_breaking" => "0.2.0".to_string(),
                "test_nonbreaking" => "0.1.1".to_string(),
                other => format!("99999.0.0+{}", other),
            }
        };

        let features = if crate_name == "your-face" {
            vec!["nose".to_string(), "mouth".to_string(), "eyes".to_string()]
        } else {
            vec![]
        };

        return Ok(Dependency::new(crate_name)
            .set_version(&new_version)
            .set_available_features(features));
    }

    if crate_name.is_empty() {
        return Err(ErrorKind::EmptyCrateName.into());
    }

    let registry = match registry {
        Some(url) => url.clone(),
        None => registry_url(manifest_path, None)?,
    };

    let crate_versions = fuzzy_query_registry_index(crate_name, &registry)?;

    let dep = read_latest_version(&crate_versions, flag_allow_prerelease)?;

    if dep.name != crate_name {
        println!("WARN: Added `{}` instead of `{}`", dep.name, crate_name);
    }

    Ok(dep)
}

#[derive(Debug)]
struct CrateVersion {
    name: String,
    version: semver::Version,
    yanked: bool,
}

/// Fuzzy query crate from registry index
fn fuzzy_query_registry_index(
    crate_name: impl Into<String>,
    registry: &Url,
) -> Result<Vec<CrateVersion>> {
    let index = crates_index::Index::from_url(registry.as_str())?;

    let crate_name = crate_name.into();
    let mut names = gen_fuzzy_crate_names(crate_name.clone())?;
    if let Some(index) = names.iter().position(|x| *x == crate_name) {
        // ref: https://github.com/killercup/cargo-edit/pull/317#discussion_r307365704
        names.swap(index, 0);
    }

    for the_name in names {
        let crate_ = match index.crate_(&the_name) {
            Some(crate_) => crate_,
            None => continue,
        };
        return crate_
            .versions()
            .iter()
            .map(|v| {
                Ok(CrateVersion {
                    name: v.name().to_owned(),
                    version: v.version().parse()?,
                    yanked: v.is_yanked(),
                })
            })
            .collect();
    }
    Err(ErrorKind::NoCrate(crate_name).into())
}

/// Generate all similar crate names
///
/// Examples:
///
/// | input | output |
/// | ----- | ------ |
/// | cargo | cargo  |
/// | cargo-edit | cargo-edit, cargo_edit |
/// | parking_lot_core | parking_lot_core, parking_lot-core, parking-lot_core, parking-lot-core |
fn gen_fuzzy_crate_names(crate_name: String) -> Result<Vec<String>> {
    const PATTERN: [u8; 2] = [b'-', b'_'];

    let wildcard_indexs = crate_name
        .bytes()
        .enumerate()
        .filter(|(_, item)| PATTERN.contains(item))
        .map(|(index, _)| index)
        .take(10)
        .collect::<Vec<usize>>();
    if wildcard_indexs.is_empty() {
        return Ok(vec![crate_name]);
    }

    let mut result = vec![];
    let mut bytes = crate_name.into_bytes();
    for mask in 0..2u128.pow(wildcard_indexs.len() as u32) {
        for (mask_index, wildcard_index) in wildcard_indexs.iter().enumerate() {
            let mask_value = (mask >> mask_index) & 1 == 1;
            if mask_value {
                bytes[*wildcard_index] = b'-';
            } else {
                bytes[*wildcard_index] = b'_';
            }
        }
        result.push(String::from_utf8(bytes.clone()).unwrap());
    }
    Ok(result)
}

// Checks whether a version object is a stable release
fn version_is_stable(version: &CrateVersion) -> bool {
    !version.version.is_prerelease()
}

/// Read latest version from Versions structure
fn read_latest_version(
    versions: &[CrateVersion],
    flag_allow_prerelease: bool,
) -> Result<Dependency> {
    let latest = versions
        .iter()
        .filter(|&v| flag_allow_prerelease || version_is_stable(v))
        .filter(|&v| !v.yanked)
        .max_by_key(|&v| v.version.clone())
        .ok_or(ErrorKind::NoVersionsAvailable)?;

    let name = &latest.name;
    let version = latest.version.to_string();
    Ok(Dependency::new(name).set_version(&version))
}

/// update registry index for given project
pub fn update_registry_index(registry: &Url, quiet: bool) -> Result<()> {
    let colorchoice = if atty::is(atty::Stream::Stdout) {
        ColorChoice::Auto
    } else {
        ColorChoice::Never
    };
    let mut output = StandardStream::stderr(colorchoice);

    let mut index = crates_index::Index::from_url(registry.as_str())?;
    if !quiet {
        output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
        write!(output, "{:>12}", "Updating")?;
        output.reset()?;
        writeln!(output, " '{}' index", registry)?;
    }

    while need_retry(index.update())? {
        registry_blocked_message(&mut output)?;
        std::thread::sleep(REGISTRY_BACKOFF);
    }

    Ok(())
}

/// Time between retries for retrieving the registry.
const REGISTRY_BACKOFF: Duration = Duration::from_secs(1);

/// Check if we need to retry retrieving the Index.
fn need_retry(res: std::result::Result<(), crates_index::Error>) -> Result<bool> {
    match res {
        Ok(()) => Ok(false),
        Err(crates_index::Error::Git(err)) => {
            if err.class() == git2::ErrorClass::Index && err.code() == git2::ErrorCode::Locked {
                Ok(true)
            } else {
                Err(crates_index::Error::Git(err).into())
            }
        }
        Err(err) => Err(err.into()),
    }
}

/// Report to user that the Registry is locked
fn registry_blocked_message(output: &mut StandardStream) -> Result<()> {
    output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
    write!(output, "{:>12}", "Blocking")?;
    output.reset()?;
    writeln!(output, " waiting for lock on registry index")?;
    Ok(())
}

/// Load Cargo.toml in a local path
///
/// This will fail, when Cargo.toml is not present in the root of the path.
pub fn get_manifest_from_path(path: &Path) -> Result<LocalManifest> {
    let cargo_file = path.join("Cargo.toml");
    LocalManifest::try_new(&cargo_file).chain_err(|| "Unable to open local Cargo.toml")
}

/// Load Cargo.toml from  github repo Cargo.toml
///
/// This will fail when:
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from the server is an error or in an incorrect format.
pub fn get_manifest_from_url(url: &str) -> Result<Option<Manifest>> {
    let manifest = if is_github_url(url) {
        Some(get_manifest_from_github(url)?)
    } else if is_gitlab_url(url) {
        Some(get_manifest_from_gitlab(url)?)
    } else {
        None
    };
    Ok(manifest)
}

fn is_github_url(url: &str) -> bool {
    url.contains("https://github.com")
}

fn is_gitlab_url(url: &str) -> bool {
    url.contains("https://gitlab.com")
}

fn get_manifest_from_github(repo: &str) -> Result<Manifest> {
    let re =
        Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$").unwrap();
    get_manifest_from_repository(repo, &re, |user, repo| {
        format!(
            "https://raw.githubusercontent.com/{user}/{repo}/master/Cargo.toml",
            user = user,
            repo = repo
        )
    })
}

fn get_manifest_from_gitlab(repo: &str) -> Result<Manifest> {
    let re =
        Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$").unwrap();
    get_manifest_from_repository(repo, &re, |user, repo| {
        format!(
            "https://gitlab.com/{user}/{repo}/raw/master/Cargo.toml",
            user = user,
            repo = repo
        )
    })
}

fn get_manifest_from_repository<T>(repo: &str, matcher: &Regex, url_template: T) -> Result<Manifest>
where
    T: Fn(&str, &str) -> String,
{
    matcher
        .captures(repo)
        .ok_or_else(|| "Unable to parse git repo URL".into())
        .and_then(|cap| match (cap.get(1), cap.get(2)) {
            (Some(user), Some(repo)) => {
                let url = url_template(user.as_str(), repo.as_str());
                get_cargo_toml_from_git_url(&url)
                    .and_then(|m| m.parse().chain_err(|| ErrorKind::ParseCargoToml))
            }
            _ => Err("Git repo url seems incomplete".into()),
        })
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String> {
    let mut req = ureq::get(url);
    req.timeout(get_default_timeout());
    if let Some(proxy) = env_proxy::for_url_str(url)
        .to_url()
        .and_then(|url| ureq::Proxy::new(url).ok())
    {
        req.set_proxy(proxy);
    }
    let res = req.call();
    if res.error() {
        return Err(format!(
            "HTTP request `{}` failed: {}",
            url,
            res.synthetic_error()
                .as_ref()
                .map(|x| x.to_string())
                .unwrap_or_else(|| res.status().to_string())
        )
        .into());
    }

    res.into_string()
        .chain_err(|| "Git response not a valid `String`")
}

const fn get_default_timeout() -> Duration {
    Duration::from_secs(10)
}

#[test]
fn test_gen_fuzzy_crate_names() {
    fn test_helper(input: &str, expect: &[&str]) {
        let mut actual = gen_fuzzy_crate_names(input.to_string()).unwrap();
        actual.sort();

        let mut expect = expect.iter().map(|x| x.to_string()).collect::<Vec<_>>();
        expect.sort();

        assert_eq!(actual, expect);
    }

    test_helper("", &[""]);
    test_helper("-", &["_", "-"]);
    test_helper("DCjanus", &["DCjanus"]);
    test_helper("DC-janus", &["DC-janus", "DC_janus"]);
    test_helper(
        "DC-_janus",
        &["DC__janus", "DC_-janus", "DC-_janus", "DC--janus"],
    );
}

#[test]
fn get_latest_stable_version() {
    let versions = vec![
        CrateVersion {
            name: "foo".into(),
            version: "0.6.0-alpha".parse().unwrap(),
            yanked: false,
        },
        CrateVersion {
            name: "foo".into(),
            version: "0.5.0".parse().unwrap(),
            yanked: false,
        },
    ];
    assert_eq!(
        read_latest_version(&versions, false)
            .unwrap()
            .version()
            .unwrap(),
        "0.5.0"
    );
}

#[test]
fn get_latest_unstable_or_stable_version() {
    let versions = vec![
        CrateVersion {
            name: "foo".into(),
            version: "0.6.0-alpha".parse().unwrap(),
            yanked: false,
        },
        CrateVersion {
            name: "foo".into(),
            version: "0.5.0".parse().unwrap(),
            yanked: false,
        },
    ];
    assert_eq!(
        read_latest_version(&versions, true)
            .unwrap()
            .version()
            .unwrap(),
        "0.6.0-alpha"
    );
}

#[test]
fn get_latest_version_with_yanked() {
    let versions = vec![
        CrateVersion {
            name: "treexml".into(),
            version: "0.3.1".parse().unwrap(),
            yanked: true,
        },
        CrateVersion {
            name: "true".into(),
            version: "0.3.0".parse().unwrap(),
            yanked: false,
        },
    ];
    assert_eq!(
        read_latest_version(&versions, false)
            .unwrap()
            .version()
            .unwrap(),
        "0.3.0"
    );
}

#[test]
fn get_no_latest_version_from_json_when_all_are_yanked() {
    let versions = vec![
        CrateVersion {
            name: "treexml".into(),
            version: "0.3.1".parse().unwrap(),
            yanked: true,
        },
        CrateVersion {
            name: "true".into(),
            version: "0.3.0".parse().unwrap(),
            yanked: true,
        },
    ];
    assert!(read_latest_version(&versions, false).is_err());
}
<commit_msg>feat: Get features from registry (#542)<commit_after>use crate::errors::*;
use crate::registry::registry_url;
use crate::VersionExt;
use crate::{Dependency, LocalManifest, Manifest};
use regex::Regex;
use std::env;
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use url::Url;

/// Query latest version from a registry index
///
/// The registry argument must be specified for crates
/// from alternative registries.
///
/// The latest version will be returned as a `Dependency`. This will fail, when
///
/// - there is no Internet connection and offline is false.
/// - summaries in registry index with an incorrect format.
/// - a crate with the given name does not exist on the registry.
pub fn get_latest_dependency(
    crate_name: &str,
    flag_allow_prerelease: bool,
    manifest_path: &Path,
    registry: &Option<Url>,
) -> Result<Dependency> {
    if env::var("CARGO_IS_TEST").is_ok() {
        // We are in a simulated reality. Nothing is real here.
        // FIXME: Use actual test handling code.
        let new_version = if flag_allow_prerelease {
            format!("99999.0.0-alpha.1+{}", crate_name)
        } else {
            match crate_name {
                "test_breaking" => "0.2.0".to_string(),
                "test_nonbreaking" => "0.1.1".to_string(),
                other => format!("99999.0.0+{}", other),
            }
        };

        let features = if crate_name == "your-face" {
            vec!["nose".to_string(), "mouth".to_string(), "eyes".to_string()]
        } else {
            vec![]
        };

        return Ok(Dependency::new(crate_name)
            .set_version(&new_version)
            .set_available_features(features));
    }

    if crate_name.is_empty() {
        return Err(ErrorKind::EmptyCrateName.into());
    }

    let registry = match registry {
        Some(url) => url.clone(),
        None => registry_url(manifest_path, None)?,
    };

    let crate_versions = fuzzy_query_registry_index(crate_name, &registry)?;

    let dep = read_latest_version(&crate_versions, flag_allow_prerelease)?;

    if dep.name != crate_name {
        println!("WARN: Added `{}` instead of `{}`", dep.name, crate_name);
    }

    Ok(dep)
}

#[derive(Debug)]
struct CrateVersion {
    name: String,
    version: semver::Version,
    yanked: bool,
    available_features: Vec<String>,
}

/// Fuzzy query crate from registry index
fn fuzzy_query_registry_index(
    crate_name: impl Into<String>,
    registry: &Url,
) -> Result<Vec<CrateVersion>> {
    let index = crates_index::Index::from_url(registry.as_str())?;

    let crate_name = crate_name.into();
    let mut names = gen_fuzzy_crate_names(crate_name.clone())?;
    if let Some(index) = names.iter().position(|x| *x == crate_name) {
        // ref: https://github.com/killercup/cargo-edit/pull/317#discussion_r307365704
        names.swap(index, 0);
    }

    for the_name in names {
        let crate_ = match index.crate_(&the_name) {
            Some(crate_) => crate_,
            None => continue,
        };
        return crate_
            .versions()
            .iter()
            .map(|v| {
                Ok(CrateVersion {
                    name: v.name().to_owned(),
                    version: v.version().parse()?,
                    yanked: v.is_yanked(),
                    available_features: v.features().keys().cloned().collect(),
                })
            })
            .collect();
    }
    Err(ErrorKind::NoCrate(crate_name).into())
}

/// Generate all similar crate names
///
/// Examples:
///
/// | input | output |
/// | ----- | ------ |
/// | cargo | cargo  |
/// | cargo-edit | cargo-edit, cargo_edit |
/// | parking_lot_core | parking_lot_core, parking_lot-core, parking-lot_core, parking-lot-core |
fn gen_fuzzy_crate_names(crate_name: String) -> Result<Vec<String>> {
    const PATTERN: [u8; 2] = [b'-', b'_'];

    let wildcard_indexs = crate_name
        .bytes()
        .enumerate()
        .filter(|(_, item)| PATTERN.contains(item))
        .map(|(index, _)| index)
        .take(10)
        .collect::<Vec<usize>>();
    if wildcard_indexs.is_empty() {
        return Ok(vec![crate_name]);
    }

    let mut result = vec![];
    let mut bytes = crate_name.into_bytes();
    for mask in 0..2u128.pow(wildcard_indexs.len() as u32) {
        for (mask_index, wildcard_index) in wildcard_indexs.iter().enumerate() {
            let mask_value = (mask >> mask_index) & 1 == 1;
            if mask_value {
                bytes[*wildcard_index] = b'-';
            } else {
                bytes[*wildcard_index] = b'_';
            }
        }
        result.push(String::from_utf8(bytes.clone()).unwrap());
    }
    Ok(result)
}

// Checks whether a version object is a stable release
fn version_is_stable(version: &CrateVersion) -> bool {
    !version.version.is_prerelease()
}

/// Read latest version from Versions structure
fn read_latest_version(
    versions: &[CrateVersion],
    flag_allow_prerelease: bool,
) -> Result<Dependency> {
    let latest = versions
        .iter()
        .filter(|&v| flag_allow_prerelease || version_is_stable(v))
        .filter(|&v| !v.yanked)
        .max_by_key(|&v| v.version.clone())
        .ok_or(ErrorKind::NoVersionsAvailable)?;

    let name = &latest.name;
    let version = latest.version.to_string();
    Ok(Dependency::new(name)
        .set_version(&version)
        .set_available_features(latest.available_features.clone()))
}

/// update registry index for given project
pub fn update_registry_index(registry: &Url, quiet: bool) -> Result<()> {
    let colorchoice = if atty::is(atty::Stream::Stdout) {
        ColorChoice::Auto
    } else {
        ColorChoice::Never
    };
    let mut output = StandardStream::stderr(colorchoice);

    let mut index = crates_index::Index::from_url(registry.as_str())?;
    if !quiet {
        output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
        write!(output, "{:>12}", "Updating")?;
        output.reset()?;
        writeln!(output, " '{}' index", registry)?;
    }

    while need_retry(index.update())? {
        registry_blocked_message(&mut output)?;
        std::thread::sleep(REGISTRY_BACKOFF);
    }

    Ok(())
}

/// Time between retries for retrieving the registry.
const REGISTRY_BACKOFF: Duration = Duration::from_secs(1);

/// Check if we need to retry retrieving the Index.
fn need_retry(res: std::result::Result<(), crates_index::Error>) -> Result<bool> {
    match res {
        Ok(()) => Ok(false),
        Err(crates_index::Error::Git(err)) => {
            if err.class() == git2::ErrorClass::Index && err.code() == git2::ErrorCode::Locked {
                Ok(true)
            } else {
                Err(crates_index::Error::Git(err).into())
            }
        }
        Err(err) => Err(err.into()),
    }
}

/// Report to user that the Registry is locked
fn registry_blocked_message(output: &mut StandardStream) -> Result<()> {
    output.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
    write!(output, "{:>12}", "Blocking")?;
    output.reset()?;
    writeln!(output, " waiting for lock on registry index")?;
    Ok(())
}

/// Load Cargo.toml in a local path
///
/// This will fail, when Cargo.toml is not present in the root of the path.
pub fn get_manifest_from_path(path: &Path) -> Result<LocalManifest> {
    let cargo_file = path.join("Cargo.toml");
    LocalManifest::try_new(&cargo_file).chain_err(|| "Unable to open local Cargo.toml")
}

/// Load Cargo.toml from  github repo Cargo.toml
///
/// This will fail when:
/// - there is no Internet connection,
/// - Cargo.toml is not present in the root of the master branch,
/// - the response from the server is an error or in an incorrect format.
pub fn get_manifest_from_url(url: &str) -> Result<Option<Manifest>> {
    let manifest = if is_github_url(url) {
        Some(get_manifest_from_github(url)?)
    } else if is_gitlab_url(url) {
        Some(get_manifest_from_gitlab(url)?)
    } else {
        None
    };
    Ok(manifest)
}

fn is_github_url(url: &str) -> bool {
    url.contains("https://github.com")
}

fn is_gitlab_url(url: &str) -> bool {
    url.contains("https://gitlab.com")
}

fn get_manifest_from_github(repo: &str) -> Result<Manifest> {
    let re =
        Regex::new(r"^https://github.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$").unwrap();
    get_manifest_from_repository(repo, &re, |user, repo| {
        format!(
            "https://raw.githubusercontent.com/{user}/{repo}/master/Cargo.toml",
            user = user,
            repo = repo
        )
    })
}

fn get_manifest_from_gitlab(repo: &str) -> Result<Manifest> {
    let re =
        Regex::new(r"^https://gitlab.com/([-_0-9a-zA-Z]+)/([-_0-9a-zA-Z]+)(/|.git)?$").unwrap();
    get_manifest_from_repository(repo, &re, |user, repo| {
        format!(
            "https://gitlab.com/{user}/{repo}/raw/master/Cargo.toml",
            user = user,
            repo = repo
        )
    })
}

fn get_manifest_from_repository<T>(repo: &str, matcher: &Regex, url_template: T) -> Result<Manifest>
where
    T: Fn(&str, &str) -> String,
{
    matcher
        .captures(repo)
        .ok_or_else(|| "Unable to parse git repo URL".into())
        .and_then(|cap| match (cap.get(1), cap.get(2)) {
            (Some(user), Some(repo)) => {
                let url = url_template(user.as_str(), repo.as_str());
                get_cargo_toml_from_git_url(&url)
                    .and_then(|m| m.parse().chain_err(|| ErrorKind::ParseCargoToml))
            }
            _ => Err("Git repo url seems incomplete".into()),
        })
}

fn get_cargo_toml_from_git_url(url: &str) -> Result<String> {
    let mut req = ureq::get(url);
    req.timeout(get_default_timeout());
    if let Some(proxy) = env_proxy::for_url_str(url)
        .to_url()
        .and_then(|url| ureq::Proxy::new(url).ok())
    {
        req.set_proxy(proxy);
    }
    let res = req.call();
    if res.error() {
        return Err(format!(
            "HTTP request `{}` failed: {}",
            url,
            res.synthetic_error()
                .as_ref()
                .map(|x| x.to_string())
                .unwrap_or_else(|| res.status().to_string())
        )
        .into());
    }

    res.into_string()
        .chain_err(|| "Git response not a valid `String`")
}

const fn get_default_timeout() -> Duration {
    Duration::from_secs(10)
}

#[test]
fn test_gen_fuzzy_crate_names() {
    fn test_helper(input: &str, expect: &[&str]) {
        let mut actual = gen_fuzzy_crate_names(input.to_string()).unwrap();
        actual.sort();

        let mut expect = expect.iter().map(|x| x.to_string()).collect::<Vec<_>>();
        expect.sort();

        assert_eq!(actual, expect);
    }

    test_helper("", &[""]);
    test_helper("-", &["_", "-"]);
    test_helper("DCjanus", &["DCjanus"]);
    test_helper("DC-janus", &["DC-janus", "DC_janus"]);
    test_helper(
        "DC-_janus",
        &["DC__janus", "DC_-janus", "DC-_janus", "DC--janus"],
    );
}

#[test]
fn get_latest_stable_version() {
    let versions = vec![
        CrateVersion {
            name: "foo".into(),
            version: "0.6.0-alpha".parse().unwrap(),
            yanked: false,
            available_features: vec![],
        },
        CrateVersion {
            name: "foo".into(),
            version: "0.5.0".parse().unwrap(),
            yanked: false,
            available_features: vec![],
        },
    ];
    assert_eq!(
        read_latest_version(&versions, false)
            .unwrap()
            .version()
            .unwrap(),
        "0.5.0"
    );
}

#[test]
fn get_latest_unstable_or_stable_version() {
    let versions = vec![
        CrateVersion {
            name: "foo".into(),
            version: "0.6.0-alpha".parse().unwrap(),
            yanked: false,
            available_features: vec![],
        },
        CrateVersion {
            name: "foo".into(),
            version: "0.5.0".parse().unwrap(),
            yanked: false,
            available_features: vec![],
        },
    ];
    assert_eq!(
        read_latest_version(&versions, true)
            .unwrap()
            .version()
            .unwrap(),
        "0.6.0-alpha"
    );
}

#[test]
fn get_latest_version_with_yanked() {
    let versions = vec![
        CrateVersion {
            name: "treexml".into(),
            version: "0.3.1".parse().unwrap(),
            yanked: true,
            available_features: vec![],
        },
        CrateVersion {
            name: "true".into(),
            version: "0.3.0".parse().unwrap(),
            yanked: false,
            available_features: vec![],
        },
    ];
    assert_eq!(
        read_latest_version(&versions, false)
            .unwrap()
            .version()
            .unwrap(),
        "0.3.0"
    );
}

#[test]
fn get_no_latest_version_from_json_when_all_are_yanked() {
    let versions = vec![
        CrateVersion {
            name: "treexml".into(),
            version: "0.3.1".parse().unwrap(),
            yanked: true,
            available_features: vec![],
        },
        CrateVersion {
            name: "true".into(),
            version: "0.3.0".parse().unwrap(),
            yanked: true,
            available_features: vec![],
        },
    ];
    assert!(read_latest_version(&versions, false).is_err());
}
<|endoftext|>


<commit_before>use crate::checksum;
use crate::config;
use crate::errors::DotcopterError;
use crate::model::*;
use dirs;
use slog::Logger;
use slog::{debug, error, info, o, warn};
use std;
use std::error::Error;
use std::fs;
use std::path::Path;
use yaml_rust::Yaml;

pub fn process_dot_files(log: &Logger, dot_files: &Yaml, force: bool) {
  if dot_files.is_badvalue() {
    warn!(log, "Empty files list");
  } else {
    for dot_file in config::parse_dot_files(log, dot_files) {
      process_dot_file(log, &dot_file, force);
    }
  }
}

fn process_dot_file(log: &Logger, dot_file: &DotFile, force: bool) {
  let log = &log.new(o!("target" => dot_file.target.clone(), "source" => dot_file.source.clone(), "type" => format!("{:?}", dot_file.dot_file_type)));
  debug!(log, "Process entry");
  let source_with_resolved_home = resolve_home(log, &dot_file.source);
  let target_with_resolved_home = resolve_home(log, &dot_file.target);
  let source_path = Path::new(&source_with_resolved_home);
  let target_path = Path::new(&target_with_resolved_home);
  if !source_path.exists() {
    warn!(log, "Source path does not exist");
    return;
  }
  match dot_file.dot_file_type {
    DotFileType::LINK => process_link(log, source_path, target_path, force),
    DotFileType::COPY => process_copy(log, source_path, target_path, force),
  }
}

fn resolve_home(log: &Logger, path: &str) -> String {
  if let Some(home_dir) = dirs::home_dir() {
    if path.starts_with('~') {
      let mut home_string = home_dir.into_os_string().into_string().expect("home_dir should be a valid string");
      home_string.push_str(&path[1..]);
      return home_string;
    } else {
      return path.to_string();
    }
  } else {
    warn!(log, "Home dir not set");
    return path.to_string();
  }
}

fn process_copy(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match has_same_content(log, source_path, target_path) {
    Ok(true) => info!(log, "File already there"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target already exists but has different content.");
        return;
      }
      match copy_dot_file(source_path, target_path) {
        Ok(_) => info!(log, "Copied file successfully"),
        Err(e) => error!(log, "Failed to copy file"; "error" => e.description()),
      }
    }
    Err(e) => error!(log, "Failed to copy dotfile"; "error" => e.description()),
  }
}

fn copy_dot_file(source: &Path, target: &Path) -> Result<(), DotcopterError> {
  if let Some(parent) = target.parent() {
    fs::create_dir_all(parent)?;
  }
  if target.exists() {
    if target.is_file() {
      fs::remove_file(target)?
    } else if target.is_dir() {
      fs::remove_dir_all(target)?
    }
  }
  fs::copy(source, target)?;
  Ok(())
}

#[cfg_attr(feature = "cargo-clippy", allow(clippy::if_same_then_else))]
fn has_same_content(log: &Logger, source: &Path, target: &Path) -> Result<bool, DotcopterError> {
  if !target.exists() {
    Ok(false)
  } else if target.is_dir() || source.is_dir() {
    Ok(false) //TODO: ???
  } else {
    let source_hash = checksum::hash(source)?;
    let target_hash = checksum::hash(target)?;
    debug!(log, "Hashed files"; "target_hash" => &target_hash, "source_hash" => &source_hash);
    Ok(source_hash == target_hash)
  }
}

fn process_link(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match already_linked(source_path, target_path) {
    Ok(true) => info!(log, "Link already exists"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target exists but does not point to source")
      } else {
        let result = link_dot_file(source_path, target_path);
        match result {
          Err(e) => error!(log, "Failed to create link"; "error" => e.description()),
          Ok(_) => info!(log, "Link created successfully"),
        }
      }
    }
    Err(e) => error!(log, "Failed to check link existence"; "error" => e.description()),
  }
}

fn already_linked(source: &Path, target: &Path) -> Result<bool, DotcopterError> {
  if target.exists() {
    let canonicalized_target = fs::canonicalize(target)?;
    let canonicalized_source = fs::canonicalize(source)?;
    Ok(canonicalized_source == canonicalized_target)
  } else {
    Ok(false)
  }
}

fn link_dot_file(source: &Path, target: &Path) -> Result<(), DotcopterError> {
  if let Some(parent) = target.parent() {
    fs::create_dir_all(parent)?;
  }
  if target.exists() {
    if target.is_file() {
      fs::remove_file(target)?
    } else if target.is_dir() {
      fs::remove_dir_all(target)?
    }
  }
  let canonicalized_source = fs::canonicalize(source)?;
  std::os::unix::fs::symlink(canonicalized_source, target)?;
  Ok(())
}
<commit_msg>[bump] rust 1.38.0<commit_after>use crate::checksum;
use crate::config;
use crate::errors::DotcopterError;
use crate::model::*;
use dirs;
use slog::Logger;
use slog::{debug, error, info, o, warn};
use std;
use std::error::Error;
use std::fs;
use std::path::Path;
use yaml_rust::Yaml;

pub fn process_dot_files(log: &Logger, dot_files: &Yaml, force: bool) {
  if dot_files.is_badvalue() {
    warn!(log, "Empty files list");
  } else {
    for dot_file in config::parse_dot_files(log, dot_files) {
      process_dot_file(log, &dot_file, force);
    }
  }
}

fn process_dot_file(log: &Logger, dot_file: &DotFile, force: bool) {
  let log = &log.new(o!("target" => dot_file.target.clone(), "source" => dot_file.source.clone(), "type" => format!("{:?}", dot_file.dot_file_type)));
  debug!(log, "Process entry");
  let source_with_resolved_home = resolve_home(log, &dot_file.source);
  let target_with_resolved_home = resolve_home(log, &dot_file.target);
  let source_path = Path::new(&source_with_resolved_home);
  let target_path = Path::new(&target_with_resolved_home);
  if !source_path.exists() {
    warn!(log, "Source path does not exist");
    return;
  }
  match dot_file.dot_file_type {
    DotFileType::LINK => process_link(log, source_path, target_path, force),
    DotFileType::COPY => process_copy(log, source_path, target_path, force),
  }
}

fn resolve_home(log: &Logger, path: &str) -> String {
  if let Some(home_dir) = dirs::home_dir() {
    if path.starts_with('~') {
      let mut home_string = home_dir.into_os_string().into_string().expect("home_dir should be a valid string");
      home_string.push_str(&path[1..]);
      home_string
    } else {
      path.to_string()
    }
  } else {
    warn!(log, "Home dir not set");
    path.to_string()
  }
}

fn process_copy(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match has_same_content(log, source_path, target_path) {
    Ok(true) => info!(log, "File already there"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target already exists but has different content.");
        return;
      }
      match copy_dot_file(source_path, target_path) {
        Ok(_) => info!(log, "Copied file successfully"),
        Err(e) => error!(log, "Failed to copy file"; "error" => e.description()),
      }
    }
    Err(e) => error!(log, "Failed to copy dotfile"; "error" => e.description()),
  }
}

fn copy_dot_file(source: &Path, target: &Path) -> Result<(), DotcopterError> {
  if let Some(parent) = target.parent() {
    fs::create_dir_all(parent)?;
  }
  if target.exists() {
    if target.is_file() {
      fs::remove_file(target)?
    } else if target.is_dir() {
      fs::remove_dir_all(target)?
    }
  }
  fs::copy(source, target)?;
  Ok(())
}

#[cfg_attr(feature = "cargo-clippy", allow(clippy::if_same_then_else))]
fn has_same_content(log: &Logger, source: &Path, target: &Path) -> Result<bool, DotcopterError> {
  if !target.exists() {
    Ok(false)
  } else if target.is_dir() || source.is_dir() {
    Ok(false) //TODO: ???
  } else {
    let source_hash = checksum::hash(source)?;
    let target_hash = checksum::hash(target)?;
    debug!(log, "Hashed files"; "target_hash" => &target_hash, "source_hash" => &source_hash);
    Ok(source_hash == target_hash)
  }
}

fn process_link(log: &Logger, source_path: &Path, target_path: &Path, force: bool) {
  match already_linked(source_path, target_path) {
    Ok(true) => info!(log, "Link already exists"),
    Ok(false) => {
      if !force && target_path.exists() {
        error!(log, "Target exists but does not point to source")
      } else {
        let result = link_dot_file(source_path, target_path);
        match result {
          Err(e) => error!(log, "Failed to create link"; "error" => e.description()),
          Ok(_) => info!(log, "Link created successfully"),
        }
      }
    }
    Err(e) => error!(log, "Failed to check link existence"; "error" => e.description()),
  }
}

fn already_linked(source: &Path, target: &Path) -> Result<bool, DotcopterError> {
  if target.exists() {
    let canonicalized_target = fs::canonicalize(target)?;
    let canonicalized_source = fs::canonicalize(source)?;
    Ok(canonicalized_source == canonicalized_target)
  } else {
    Ok(false)
  }
}

fn link_dot_file(source: &Path, target: &Path) -> Result<(), DotcopterError> {
  if let Some(parent) = target.parent() {
    fs::create_dir_all(parent)?;
  }
  if target.exists() {
    if target.is_file() {
      fs::remove_file(target)?
    } else if target.is_dir() {
      fs::remove_dir_all(target)?
    }
  }
  let canonicalized_source = fs::canonicalize(source)?;
  std::os::unix::fs::symlink(canonicalized_source, target)?;
  Ok(())
}
<|endoftext|>


<commit_before>use {HashSet};
use std::collections::{HashMap, RingBuf, Deque};

// call this Unigraph instead?
pub trait Graph<T> {
    /// Insert a new node, returning its index
    fn add_node(&mut self, val: T) -> NodeIndex;

    /// Insert a new edge, returning an error if one of the indices is invalid
    /// and a result otherwise
    fn add_edge(&mut self, i: NodeIndex, j: NodeIndex) -> GraphResult<bool>;

    /// Return an iterator over the out-neighbors for a given node
    fn adj(&self, i: NodeIndex) -> NodeIndices;

    /// Return the number of nodes in the graph
    fn num_nodes(&self) -> uint;

    fn node_indices(&self) -> NodeIndices;

    fn dfs(&self) -> Digraph<NodeIndex> {
        let mut tree = Digraph::new();

        if self.num_nodes() == 0 {
            return tree;
        }

        let mut visited = HashSet::new();
        let mut discovered = vec!();

        discovered.push((0, None));
        loop {
            match discovered.pop() {
                None => break,
                Some((ind, parent)) => {
                    tree.add_node(ind);
                    if parent.is_some() {
                        tree.add_edge(parent.unwrap(), ind);
                    }
                    visited.insert(ind);

                    for i in self.adj(ind) {
                        if !visited.contains(i) {
                            discovered.push((*i, Some(ind)));
                        }
                    }
                }
            }
        }
        return tree;

    }
}

struct GraphError {
    error_type: GraphErrorType,
}

impl GraphError {
    fn invalid_index(ind: NodeIndex) -> GraphError {
        GraphError { error_type: InvalidNodeIndex(ind) }
    }
}

type GraphResult<T> = Result<T, GraphError>;

enum GraphErrorType {
    InvalidNodeIndex(NodeIndex),
}

pub type NodeIndex = uint;
type NodeIndexSet = HashSet<NodeIndex>;

#[deriving(Show, Clone)]
struct Node<T> {
    data: T,
    index: NodeIndex,
}

/// Undirected graph. Allows loops.
#[deriving(Show)]
pub struct UnGraph<T> {
    nodes: HashMap<NodeIndex, Node<T>>,
    adj: HashMap<NodeIndex, NodeIndexSet>,
    num_nodes: uint,
}

/// Directed graph. Allows loops.
#[deriving(Show)]
pub struct Digraph<T> {
    nodes: HashMap<NodeIndex, Node<T>>,
    in_adj: HashMap<NodeIndex, NodeIndexSet>,
    out_adj: HashMap<NodeIndex, NodeIndexSet>,
    num_nodes: uint,
}

impl<T> UnGraph<T> {
    pub fn new() -> UnGraph<T> {
        UnGraph { nodes: HashMap::new(), adj: HashMap::new(), num_nodes: 0 }
    }

    pub fn degree(&mut self, ind: NodeIndex) -> Result<uint, GraphError> {
        if !self.adj.contains_key(&ind) {
            Err(GraphError::invalid_index(ind))
        } else {
            Ok( self.get_adj(ind).len() )
        }
    }

    pub fn get_node<'a>(&'a self, i: NodeIndex) -> &'a Node<T> {
        self.nodes.find(&i).unwrap()
    }

    pub fn get_adj<'a>(&'a self, i: NodeIndex) -> &'a NodeIndexSet {
        self.adj.find(&i).unwrap()
    }
}
impl<T: Clone> UnGraph<T> {
    /// Returns a new graph induced by a set of node indices
    pub fn induced_subgraph(&self, nodes: &NodeIndexSet) -> UnGraph<T> {
        let mut new = UnGraph::new();
        let mut ind_map = HashMap::new();

        for (i, ind) in nodes.iter().enumerate() {
            ind_map.insert(ind, i);
            new.add_node(self.get_node(*ind).data.clone());
        }

        // Here we're assuming that NodeIndex = uint. Not sure how to easily do otherwise
        for i in range(0, self.num_nodes) {
            for j in self.get_adj(i).iter() {
                new.add_edge(i, *j);
            }
        }

        new
    }
}

impl<T> Graph<T> for UnGraph<T> {
    fn add_node(&mut self, val: T) -> NodeIndex {
        let ind = self.num_nodes;
        self.nodes.insert(ind, Node { data: val, index: ind });
        self.adj.insert(ind, HashSet::new());
        self.num_nodes += 1;
        ind
    }

    fn add_edge(&mut self, i: NodeIndex, j: NodeIndex) -> GraphResult<bool> {
        if !self.nodes.contains_key(&i) {
            Err(GraphError::invalid_index(i))
        } else if !self.nodes.contains_key(&j) {
            Err(GraphError::invalid_index(j))
        } else {
            if self.get_adj(i).contains(&j) {
                Ok(false)
            } else {
                self.adj.find_mut(&i).unwrap().insert(j);
                self.adj.find_mut(&j).unwrap().insert(i);
                Ok(true)
            }
        }
    }

    fn adj(&self, i: NodeIndex) -> NodeIndices {
        FromIterator::from_iter(self.get_adj(i).iter().map(|&x| x))
    }

    fn num_nodes(&self) -> uint {
        self.num_nodes
    }

    fn node_indices(&self) -> NodeIndices {
        FromIterator::from_iter(self.nodes.keys().map(|&x| x))
    }
}

impl<T> Digraph<T> {
    pub fn new() -> Digraph<T> {
        Digraph { nodes: HashMap::new(),
                  in_adj: HashMap::new(),
                  out_adj: HashMap::new(),
                  num_nodes: 0 }
    }

    pub fn out_degree(&mut self, ind: NodeIndex) -> Result<uint, GraphError> {
        if !self.nodes.contains_key(&ind) {
            Err(GraphError::invalid_index(ind))
        } else {
            Ok(self.get_out_adj(ind).len())
        }
    }

    pub fn in_degree(&mut self, ind: NodeIndex) -> Result<uint, GraphError> {
        if ind >= self.num_nodes {
            Err(GraphError::invalid_index(ind))
        } else {
            Ok(self.get_in_adj(ind).len())
        }
    }

    pub fn get_node<'a>(&'a self, i: NodeIndex) -> &'a Node<T> {
        self.nodes.find(&i).unwrap()
    }

    pub fn get_in_adj<'a>(&'a self, i: NodeIndex) -> &'a NodeIndexSet {
        self.in_adj.find(&i).unwrap()
    }

    pub fn get_out_adj<'a>(&'a self, i: NodeIndex) -> &'a NodeIndexSet {
        self.out_adj.find(&i).unwrap()
    }
}

impl<T: Clone> Digraph<T> {
    /// Returns a new graph induced by a set of node indices
    pub fn induced_subgraph(&self, nodes: &NodeIndexSet) -> Digraph<T> {
        let mut new = Digraph::new();
        let mut ind_map = HashMap::new();

        for (i, ind) in nodes.iter().enumerate() {
            ind_map.insert(ind, i);
            new.add_node(self.get_node(*ind).data.clone());
        }

        // Here we're assuming that NodeIndex = uint. Not sure how to easily do otherwise
        for i in range(0, self.num_nodes) {
            for j in self.get_in_adj(i).iter() {
                new.add_edge(*j, i);
            }

            for j in self.get_out_adj(i).iter() {
                new.add_edge(i, *j);
            }
        }

        new
    }
}

impl<T> Graph<T> for Digraph<T> {
    fn add_node(&mut self, val: T) -> NodeIndex {
        let ind = self.num_nodes;
        self.nodes.insert(ind, Node { data: val, index: ind });
        self.in_adj.insert(ind, HashSet::new());
        self.out_adj.insert(ind, HashSet::new());
        self.num_nodes += 1;
        ind
    }

    fn add_edge(&mut self, i: NodeIndex, j: NodeIndex) -> GraphResult<bool> {
        if !self.nodes.contains_key(&i) {
            Err(GraphError::invalid_index(i))
        } else if !self.nodes.contains_key(&j) {
            Err(GraphError::invalid_index(j))
        } else {
            if self.get_out_adj(i).contains(&j) {
                Ok(false)
            } else {
                self.out_adj.find_mut(&i).unwrap().insert(j);
                self.in_adj.find_mut(&j).unwrap().insert(i);
                Ok(true)
            }
        }
    }

    fn adj(&self, i: NodeIndex) -> NodeIndices {
        FromIterator::from_iter(self.get_out_adj(i).iter().map(|&x| x))
    }

    fn num_nodes(&self) -> uint {
        self.num_nodes
    }

    fn node_indices(&self) -> NodeIndices {
        FromIterator::from_iter(self.nodes.keys().map(|&x| x))
    }
}

struct NodeIndices {
    indices: Vec<NodeIndex>,
    curr: uint,
}

impl Iterator<NodeIndex> for NodeIndices {
    fn next(&mut self) -> Option<NodeIndex> {
        if self.curr < self.indices.len() {
            self.curr += 1;
            Some(self.indices[self.curr - 1])
        } else {
            None
        }
    }
}

impl FromIterator<NodeIndex> for NodeIndices {
    fn from_iter<T: Iterator<NodeIndex>>(mut it: T) -> NodeIndices {
        let mut vec = vec!();
        for i in it {
            vec.push(i);
        }
        NodeIndices { indices: vec, curr: 0 }
    }

}
<commit_msg>UnGraph => Ungraph<commit_after>use {HashSet};
use std::collections::{HashMap, RingBuf, Deque};

// call this Unigraph instead?
pub trait Graph<T> {
    /// Insert a new node, returning its index
    fn add_node(&mut self, val: T) -> NodeIndex;

    /// Insert a new edge, returning an error if one of the indices is invalid
    /// and a result otherwise
    fn add_edge(&mut self, i: NodeIndex, j: NodeIndex) -> GraphResult<bool>;

    /// Return an iterator over the out-neighbors for a given node
    fn adj(&self, i: NodeIndex) -> NodeIndices;

    /// Return the number of nodes in the graph
    fn num_nodes(&self) -> uint;

    fn node_indices(&self) -> NodeIndices;

    fn dfs(&self) -> Digraph<NodeIndex> {
        let mut tree = Digraph::new();

        if self.num_nodes() == 0 {
            return tree;
        }

        let mut visited = HashSet::new();
        let mut discovered = vec!();

        discovered.push((0, None));
        loop {
            match discovered.pop() {
                None => break,
                Some((ind, parent)) => {
                    tree.add_node(ind);
                    if parent.is_some() {
                        tree.add_edge(parent.unwrap(), ind);
                    }
                    visited.insert(ind);

                    for i in self.adj(ind) {
                        if !visited.contains(i) {
                            discovered.push((*i, Some(ind)));
                        }
                    }
                }
            }
        }
        return tree;

    }
}

struct GraphError {
    error_type: GraphErrorType,
}

impl GraphError {
    fn invalid_index(ind: NodeIndex) -> GraphError {
        GraphError { error_type: InvalidNodeIndex(ind) }
    }
}

type GraphResult<T> = Result<T, GraphError>;

enum GraphErrorType {
    InvalidNodeIndex(NodeIndex),
}

pub type NodeIndex = uint;
type NodeIndexSet = HashSet<NodeIndex>;

#[deriving(Show, Clone)]
struct Node<T> {
    data: T,
    index: NodeIndex,
}

/// Undirected graph. Allows loops.
#[deriving(Show)]
pub struct Ungraph<T> {
    nodes: HashMap<NodeIndex, Node<T>>,
    adj: HashMap<NodeIndex, NodeIndexSet>,
    num_nodes: uint,
}

/// Directed graph. Allows loops.
#[deriving(Show)]
pub struct Digraph<T> {
    nodes: HashMap<NodeIndex, Node<T>>,
    in_adj: HashMap<NodeIndex, NodeIndexSet>,
    out_adj: HashMap<NodeIndex, NodeIndexSet>,
    num_nodes: uint,
}

impl<T> Ungraph<T> {
    pub fn new() -> Ungraph<T> {
        Ungraph { nodes: HashMap::new(), adj: HashMap::new(), num_nodes: 0 }
    }

    pub fn degree(&mut self, ind: NodeIndex) -> Result<uint, GraphError> {
        if !self.adj.contains_key(&ind) {
            Err(GraphError::invalid_index(ind))
        } else {
            Ok( self.get_adj(ind).len() )
        }
    }

    pub fn get_node<'a>(&'a self, i: NodeIndex) -> &'a Node<T> {
        self.nodes.find(&i).unwrap()
    }

    pub fn get_adj<'a>(&'a self, i: NodeIndex) -> &'a NodeIndexSet {
        self.adj.find(&i).unwrap()
    }
}
impl<T: Clone> Ungraph<T> {
    /// Returns a new graph induced by a set of node indices
    pub fn induced_subgraph(&self, nodes: &NodeIndexSet) -> Ungraph<T> {
        let mut new = Ungraph::new();
        let mut ind_map = HashMap::new();

        for (i, ind) in nodes.iter().enumerate() {
            ind_map.insert(ind, i);
            new.add_node(self.get_node(*ind).data.clone());
        }

        // Here we're assuming that NodeIndex = uint. Not sure how to easily do otherwise
        for i in range(0, self.num_nodes) {
            for j in self.get_adj(i).iter() {
                new.add_edge(i, *j);
            }
        }

        new
    }
}

impl<T> Graph<T> for Ungraph<T> {
    fn add_node(&mut self, val: T) -> NodeIndex {
        let ind = self.num_nodes;
        self.nodes.insert(ind, Node { data: val, index: ind });
        self.adj.insert(ind, HashSet::new());
        self.num_nodes += 1;
        ind
    }

    fn add_edge(&mut self, i: NodeIndex, j: NodeIndex) -> GraphResult<bool> {
        if !self.nodes.contains_key(&i) {
            Err(GraphError::invalid_index(i))
        } else if !self.nodes.contains_key(&j) {
            Err(GraphError::invalid_index(j))
        } else {
            if self.get_adj(i).contains(&j) {
                Ok(false)
            } else {
                self.adj.find_mut(&i).unwrap().insert(j);
                self.adj.find_mut(&j).unwrap().insert(i);
                Ok(true)
            }
        }
    }

    fn adj(&self, i: NodeIndex) -> NodeIndices {
        FromIterator::from_iter(self.get_adj(i).iter().map(|&x| x))
    }

    fn num_nodes(&self) -> uint {
        self.num_nodes
    }

    fn node_indices(&self) -> NodeIndices {
        FromIterator::from_iter(self.nodes.keys().map(|&x| x))
    }
}

impl<T> Digraph<T> {
    pub fn new() -> Digraph<T> {
        Digraph { nodes: HashMap::new(),
                  in_adj: HashMap::new(),
                  out_adj: HashMap::new(),
                  num_nodes: 0 }
    }

    pub fn out_degree(&mut self, ind: NodeIndex) -> Result<uint, GraphError> {
        if !self.nodes.contains_key(&ind) {
            Err(GraphError::invalid_index(ind))
        } else {
            Ok(self.get_out_adj(ind).len())
        }
    }

    pub fn in_degree(&mut self, ind: NodeIndex) -> Result<uint, GraphError> {
        if ind >= self.num_nodes {
            Err(GraphError::invalid_index(ind))
        } else {
            Ok(self.get_in_adj(ind).len())
        }
    }

    pub fn get_node<'a>(&'a self, i: NodeIndex) -> &'a Node<T> {
        self.nodes.find(&i).unwrap()
    }

    pub fn get_in_adj<'a>(&'a self, i: NodeIndex) -> &'a NodeIndexSet {
        self.in_adj.find(&i).unwrap()
    }

    pub fn get_out_adj<'a>(&'a self, i: NodeIndex) -> &'a NodeIndexSet {
        self.out_adj.find(&i).unwrap()
    }
}

impl<T: Clone> Digraph<T> {
    /// Returns a new graph induced by a set of node indices
    pub fn induced_subgraph(&self, nodes: &NodeIndexSet) -> Digraph<T> {
        let mut new = Digraph::new();
        let mut ind_map = HashMap::new();

        for (i, ind) in nodes.iter().enumerate() {
            ind_map.insert(ind, i);
            new.add_node(self.get_node(*ind).data.clone());
        }

        // Here we're assuming that NodeIndex = uint. Not sure how to easily do otherwise
        for i in range(0, self.num_nodes) {
            for j in self.get_in_adj(i).iter() {
                new.add_edge(*j, i);
            }

            for j in self.get_out_adj(i).iter() {
                new.add_edge(i, *j);
            }
        }

        new
    }
}

impl<T> Graph<T> for Digraph<T> {
    fn add_node(&mut self, val: T) -> NodeIndex {
        let ind = self.num_nodes;
        self.nodes.insert(ind, Node { data: val, index: ind });
        self.in_adj.insert(ind, HashSet::new());
        self.out_adj.insert(ind, HashSet::new());
        self.num_nodes += 1;
        ind
    }

    fn add_edge(&mut self, i: NodeIndex, j: NodeIndex) -> GraphResult<bool> {
        if !self.nodes.contains_key(&i) {
            Err(GraphError::invalid_index(i))
        } else if !self.nodes.contains_key(&j) {
            Err(GraphError::invalid_index(j))
        } else {
            if self.get_out_adj(i).contains(&j) {
                Ok(false)
            } else {
                self.out_adj.find_mut(&i).unwrap().insert(j);
                self.in_adj.find_mut(&j).unwrap().insert(i);
                Ok(true)
            }
        }
    }

    fn adj(&self, i: NodeIndex) -> NodeIndices {
        FromIterator::from_iter(self.get_out_adj(i).iter().map(|&x| x))
    }

    fn num_nodes(&self) -> uint {
        self.num_nodes
    }

    fn node_indices(&self) -> NodeIndices {
        FromIterator::from_iter(self.nodes.keys().map(|&x| x))
    }
}

struct NodeIndices {
    indices: Vec<NodeIndex>,
    curr: uint,
}

impl Iterator<NodeIndex> for NodeIndices {
    fn next(&mut self) -> Option<NodeIndex> {
        if self.curr < self.indices.len() {
            self.curr += 1;
            Some(self.indices[self.curr - 1])
        } else {
            None
        }
    }
}

impl FromIterator<NodeIndex> for NodeIndices {
    fn from_iter<T: Iterator<NodeIndex>>(mut it: T) -> NodeIndices {
        let mut vec = vec!();
        for i in it {
            vec.push(i);
        }
        NodeIndices { indices: vec, curr: 0 }
    }

}
<|endoftext|>


<commit_before>use std::rc::Rc;
use std::marker::PhantomData;

use typedrw::TypedMemoryMap;
use PrefixExtender;

pub trait GraphExtenderExt<G: GraphTrait> {
    fn extend_using<P,L: Fn(&P)->u64+'static>(&self, route: L) -> Rc<GraphExtender<G,P,L>>;
}

impl<G: GraphTrait> GraphExtenderExt<G> for Rc<G> {
    fn extend_using<P,L: Fn(&P)->u64+'static>(&self, logic: L) -> Rc<GraphExtender<G,P,L>> {
        Rc::new(GraphExtender {
            graph:  self.clone(),
            logic:  Rc::new(logic),
            phant:  PhantomData,
        })
    }
}

pub trait GraphTrait : 'static {
    type Target: Ord;
    fn nodes(&self) -> usize;
    fn edges(&self, node: usize) -> &[Self::Target];
}

pub struct GraphVector<E> {
    pub nodes: Vec<u64>,
    pub edges: Vec<E>,
}

impl<E: Ord+Send+'static> GraphTrait for GraphVector<E> {
    type Target = E;
    #[inline(always)]
    fn nodes(&self) -> usize {
        self.nodes.len()
    }
    #[inline(always)]
    fn edges(&self, node: usize) -> &[E] {
        if node + 1 < self.nodes.len() {
            let start = self.nodes[node] as usize;
            let limit = self.nodes[node+1] as usize;
            &self.edges[start..limit]
        }
        else { &[] }
    }
}

pub struct GraphMMap<E: Ord+Copy> {
    nodes: TypedMemoryMap<u64>,
    edges: TypedMemoryMap<E>,
}

impl<E: Ord+Copy> GraphMMap<E> {
    pub fn new(prefix: &str) -> GraphMMap<E> {
        GraphMMap {
            nodes: TypedMemoryMap::new(format!("{}.offsets", prefix)),
            edges: TypedMemoryMap::new(format!("{}.targets", prefix)),
        }
    }
}

impl<E: Ord+Copy+Send+'static> GraphTrait for GraphMMap<E> {
    type Target = E;
    #[inline(always)]
    fn nodes(&self) -> usize {
        self.nodes[..].len()
    }
    #[inline(always)]
    fn edges(&self, node: usize) -> &[E] {
        let nodes = &self.nodes[..];
        if node + 1 < nodes.len() {
            let start = nodes[node] as usize;
            let limit = nodes[node+1] as usize;
            &self.edges[..][start..limit]
        }
        else { &[] }
    }
}

pub struct GraphExtender<G: GraphTrait, P, L: Fn(&P)->u64> {
    graph: Rc<G>,
    logic: Rc<L>,
    phant: PhantomData<P>,
}

impl<G: GraphTrait, P, L: Fn(&P)->u64+'static> PrefixExtender for GraphExtender<G, P, L>
where <G as GraphTrait>::Target : Clone {
    type Prefix = P;
    type Extension = G::Target;

    type RoutingFunction = L;
    fn logic(&self) -> Rc<L> { self.logic.clone() }

    fn count(&self, prefix: &P) -> u64 {
        let node = (*self.logic)(prefix) as usize;
        self.graph.edges(node).len() as u64
    }

    fn propose(&self, prefix: &P, list: &mut Vec<G::Target>) {
        let node = (*self.logic)(prefix) as usize;
        list.extend(self.graph.edges(node).iter().cloned());
    }

    fn intersect(&self, prefix: &P, list: &mut Vec<G::Target>) {
        let node = (*self.logic)(prefix) as usize;
        let mut slice = self.graph.edges(node);

        if list.len() < slice.len() / 4 {
            list.retain(|value| {
                slice = gallop(slice, value);
                slice.len() > 0 && &slice[0] == value
            });
        }
        else {
            list.retain(move |value| {
                while slice.len() > 0 && &slice[0] < value { slice = &slice[1..]; }
                slice.len() > 0 && &slice[0] == value
            });
        }
    }
}

// intended to advance slice to start at the first element >= value.

#[inline(always)]
pub fn gallop<'a, T: Ord>(mut slice: &'a [T], value: &T) -> &'a [T] {
    // if empty slice, or already >= element, return
    if slice.len() > 0 && &slice[0] < value {
        let mut step = 1;
        while step < slice.len() && &slice[step] < value {
            slice = &slice[step..];
            step = step << 1;
        }

        step = step >> 1;
        while step > 0 {
            if step < slice.len() && &slice[step] < value {
                slice = &slice[step..];
            }
            step = step >> 1;
        }

        slice = &slice[1..]; // advance one, as we always stayed < value
    }

    return slice;
}
<commit_msg>using more efficient clone while we aren't yet recycling vectors<commit_after>use std::rc::Rc;
use std::marker::PhantomData;

use typedrw::TypedMemoryMap;
use PrefixExtender;

pub trait GraphExtenderExt<G: GraphTrait> {
    fn extend_using<P,L: Fn(&P)->u64+'static>(&self, route: L) -> Rc<GraphExtender<G,P,L>>;
}

impl<G: GraphTrait> GraphExtenderExt<G> for Rc<G> {
    fn extend_using<P,L: Fn(&P)->u64+'static>(&self, logic: L) -> Rc<GraphExtender<G,P,L>> {
        Rc::new(GraphExtender {
            graph:  self.clone(),
            logic:  Rc::new(logic),
            phant:  PhantomData,
        })
    }
}

pub trait GraphTrait : 'static {
    type Target: Ord;
    fn nodes(&self) -> usize;
    fn edges(&self, node: usize) -> &[Self::Target];
}

pub struct GraphVector<E> {
    pub nodes: Vec<u64>,
    pub edges: Vec<E>,
}

impl<E: Ord+Send+'static> GraphTrait for GraphVector<E> {
    type Target = E;
    #[inline(always)]
    fn nodes(&self) -> usize {
        self.nodes.len()
    }
    #[inline(always)]
    fn edges(&self, node: usize) -> &[E] {
        if node + 1 < self.nodes.len() {
            let start = self.nodes[node] as usize;
            let limit = self.nodes[node+1] as usize;
            &self.edges[start..limit]
        }
        else { &[] }
    }
}

pub struct GraphMMap<E: Ord+Copy> {
    nodes: TypedMemoryMap<u64>,
    edges: TypedMemoryMap<E>,
}

impl<E: Ord+Copy> GraphMMap<E> {
    pub fn new(prefix: &str) -> GraphMMap<E> {
        GraphMMap {
            nodes: TypedMemoryMap::new(format!("{}.offsets", prefix)),
            edges: TypedMemoryMap::new(format!("{}.targets", prefix)),
        }
    }
}

impl<E: Ord+Copy+Send+'static> GraphTrait for GraphMMap<E> {
    type Target = E;
    #[inline(always)]
    fn nodes(&self) -> usize {
        self.nodes[..].len()
    }
    #[inline(always)]
    fn edges(&self, node: usize) -> &[E] {
        let nodes = &self.nodes[..];
        if node + 1 < nodes.len() {
            let start = nodes[node] as usize;
            let limit = nodes[node+1] as usize;
            &self.edges[..][start..limit]
        }
        else { &[] }
    }
}

pub struct GraphExtender<G: GraphTrait, P, L: Fn(&P)->u64> {
    graph: Rc<G>,
    logic: Rc<L>,
    phant: PhantomData<P>,
}

impl<G: GraphTrait, P, L: Fn(&P)->u64+'static> PrefixExtender for GraphExtender<G, P, L>
where <G as GraphTrait>::Target : Clone {
    type Prefix = P;
    type Extension = G::Target;

    type RoutingFunction = L;
    fn logic(&self) -> Rc<L> { self.logic.clone() }

    fn count(&self, prefix: &P) -> u64 {
        let node = (*self.logic)(prefix) as usize;
        self.graph.edges(node).len() as u64
    }

    fn propose(&self, prefix: &P, list: &mut Vec<G::Target>) {
        let node = (*self.logic)(prefix) as usize;
        *list = self.graph.edges(node).to_vec();
//        list.extend(self.graph.edges(node).iter().cloned());
    }

    fn intersect(&self, prefix: &P, list: &mut Vec<G::Target>) {
        let node = (*self.logic)(prefix) as usize;
        let mut slice = self.graph.edges(node);

        if list.len() < slice.len() / 4 {
            list.retain(|value| {
                slice = gallop(slice, value);
                slice.len() > 0 && &slice[0] == value
            });
        }
        else {
            list.retain(move |value| {
                while slice.len() > 0 && &slice[0] < value { slice = &slice[1..]; }
                slice.len() > 0 && &slice[0] == value
            });
        }
    }
}

// intended to advance slice to start at the first element >= value.

#[inline(always)]
pub fn gallop<'a, T: Ord>(mut slice: &'a [T], value: &T) -> &'a [T] {
    // if empty slice, or already >= element, return
    if slice.len() > 0 && &slice[0] < value {
        let mut step = 1;
        while step < slice.len() && &slice[step] < value {
            slice = &slice[step..];
            step = step << 1;
        }

        step = step >> 1;
        while step > 0 {
            if step < slice.len() && &slice[step] < value {
                slice = &slice[step..];
            }
            step = step >> 1;
        }

        slice = &slice[1..]; // advance one, as we always stayed < value
    }

    return slice;
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::io::BufRead;
use std::io::BufReader;
use std::fs::File;
use std::rc::Rc;
use std::path::Path;

use display::Display;
use sdl2;
use player;
use player::Player;

#[derive(Clone)]
#[derive(Hash)]
#[derive(Eq)]
#[derive(PartialEq)]
pub enum SquareType {
    EMPTY = 0isize,
    WALL = 1isize,
    BOX = 2isize,
    TARGET = 3isize,
    TARGETVALID = 4isize
}

pub struct Level<'a> {
    columns: isize,
    rows: isize,
    boxsize: isize,
    renderer: Display<'a>,
    textures: HashMap<SquareType, Rc<sdl2::render::Texture>>,
    player_textures: HashMap<player::Orientation, Rc<sdl2::render::Texture>>,
    grid: Vec<Vec<Square>>,
    start_position: (usize, usize)
}

impl<'a> Level<'a> {
    pub fn new(sdl_context: & sdl2::Sdl) -> Level<'a> {
        let width : u32 = 20 * 34;
        let height : u32 = 20 * 34;

        let renderer = Display::new(width, height, sdl_context);

        Level {
            columns: 20,
            rows: 20,
            boxsize: 34,
            renderer: renderer,
            textures: HashMap::new(),
            player_textures: HashMap::new(),
            grid: Vec::new(),
            start_position: (0, 0)
        }
    }

    pub fn init(&mut self) {
        self.textures = self.renderer.get_grid_textures();
        self.player_textures = self.renderer.get_player_textures();
        let grid_content: Vec<Vec<SquareType>> = self.get_level_content();
        self.grid = Level::create_grid(grid_content);
    }

    pub fn get_start_position(&self) -> (usize, usize) {
        self.start_position
    }

    fn get_level_content(&mut self) -> Vec<Vec<SquareType>> {
        let level_path = Path::new("./resources/level.txt");
        let buffer = BufReader::new(File::open(&level_path).unwrap());
        let lines: Vec<String> = buffer.lines().map(|x| x.unwrap()).collect();

        let mut grid: Vec<Vec<SquareType>> = Vec::new();
        for line in lines.iter() {
            let mut row: Vec<SquareType> = Vec::new();
            let mut myline = line.clone();
            myline.pop();
            for c in myline.chars() {
                if c == 'P' {
                    self.start_position = (row.len(), grid.len());
                }
                let code: isize = match c.to_digit(4) {
                    Some(d) => d as isize,
                    None => 0isize
                };
                let square_type = match code {
                    0 => SquareType::EMPTY,
                    1 => SquareType::WALL,
                    2 => SquareType::BOX,
                    3 => SquareType::TARGET,
                    4 => SquareType::TARGETVALID,
                    _ => SquareType::EMPTY
                };

                row.push(square_type);
            }
            grid.push(row);
        }
        return grid;
    }

    fn create_grid(content: Vec<Vec<SquareType>>) -> Vec<Vec<Square>> {
        let mut grid: Vec<Vec<Square>> = Vec::new();
        let mut i = 0isize;

        for content_row in content.iter() {
            let mut row: Vec<Square> = Vec::new();
            let mut j = 0isize;
            for square_type in content_row.iter() {
                let s = create_square(j, i, square_type.clone());
                row.push(s);
                j += 1;
            }
            i += 1;
            grid.push(row);
        }
        return grid;
    }

    pub fn update_display(&mut self, player: &Player) {
        self.renderer.clear_screen();
        self.renderer.render_grid(&self.grid, self.boxsize as i32, &self.textures);
        let player_texture = match self.player_textures.get(&player.orientation) {
            Some(t) => t,
            None => panic!(format!("error on texture retrieval for player orientation {}", player.orientation.clone() as isize))
        };
        self.renderer.render_player(&**player_texture, player.get_position(), self.boxsize as i32);
        self.renderer.renderer.drawer().present();
    }

    pub fn is_move_allowed(&self, player: &Player, movement: (i8, i8)) -> bool {
        let (x, y) = player.get_position();
        let (dx, dy) = movement;
        let new_x = x as isize + dx as isize;
        let new_y = y as isize + dy as isize;

        match self.grid[new_y as usize][new_x as usize].square_type {
            SquareType::EMPTY => true,
            SquareType::WALL => false,
            SquareType::BOX | SquareType::TARGETVALID => {
                let pos = ((new_x + dx as isize) as usize, (new_y + dy as isize) as usize);
                self.is_free(pos)
            },
            SquareType::TARGET => true
        }
    }
    pub fn is_free(&self, position: (usize, usize)) -> bool {
        let (x, y) = position;
        match self.grid[y][x].square_type {
            SquareType::EMPTY | SquareType::TARGET => true,
            _ => false
        }
    }
    pub fn is_box_present(&self, position: (usize, usize)) -> bool {
        let (x, y) = position;
        match self.grid[y][x].square_type {
            SquareType::BOX | SquareType::TARGETVALID => true,
            _ => false
        }
    }

    pub fn move_box(&mut self, position: (usize, usize), delta: (i8, i8)) {
        let (x, y) = position;
        let (dx, dy) = delta;
        let new_x = (x as isize + dx as isize) as usize;
        let new_y = (y as isize + dy as isize) as usize;
        match self.grid[y][x].square_type {
            SquareType::BOX => self.grid[y][x].square_type = SquareType::EMPTY,
            SquareType::TARGETVALID => self.grid[y][x].square_type = SquareType::TARGET,
            _ => ()
        };

        match self.grid[new_y][new_x].square_type {
            SquareType::EMPTY => self.grid[new_y][new_x].square_type = SquareType::BOX,
            SquareType::TARGET => self.grid[new_y][new_x].square_type = SquareType::TARGETVALID,
            _ => ()
        };
    }
}

pub struct Square {
    pub x: isize,
    pub y: isize,
    pub square_type: SquareType,
}

pub fn create_square(x: isize, y: isize, square_type: SquareType) -> Square {
    Square{x:x, y:y, square_type: square_type}
}
<commit_msg>refactor the level class<commit_after>use std::collections::HashMap;
use std::io::{BufRead, BufReader };
use std::fs::File;
use std::rc::Rc;
use std::path::Path;

use display::Display;
use sdl2;
use player;
use player::Player;

#[derive(Clone)]
#[derive(Hash)]
#[derive(Eq)]
#[derive(PartialEq)]
pub enum SquareType {
    EMPTY = 0isize,
    WALL = 1isize,
    BOX = 2isize,
    TARGET = 3isize,
    TARGETVALID = 4isize
}

pub struct Level<'a> {
    columns: isize,
    rows: isize,
    boxsize: isize,
    renderer: Display<'a>,
    textures: HashMap<SquareType, Rc<sdl2::render::Texture>>,
    player_textures: HashMap<player::Orientation, Rc<sdl2::render::Texture>>,
    grid: Vec<Vec<Square>>,
    start_position: (usize, usize)
}

impl<'a> Level<'a> {
    pub fn new(sdl_context: & sdl2::Sdl) -> Level<'a> {
        let width : u32 = 20 * 34;
        let height : u32 = 20 * 34;

        let renderer = Display::new(width, height, sdl_context);

        Level {
            columns: 20,
            rows: 20,
            boxsize: 34,
            renderer: renderer,
            textures: HashMap::new(),
            player_textures: HashMap::new(),
            grid: Vec::new(),
            start_position: (0, 0)
        }
    }

    pub fn init(&mut self) {
        self.textures = self.renderer.get_grid_textures();
        self.player_textures = self.renderer.get_player_textures();

        let level_path = Path::new("./resources/level.txt");
        let buffer : BufReader<File> = BufReader::new(File::open(level_path).unwrap());
        let lines: Vec<String> = buffer.lines().map(|x| x.unwrap()).collect();
        let grid_content: Vec<Vec<SquareType>> = self.get_level_content(lines);
        self.grid = Level::create_grid(grid_content);
    }

    pub fn get_start_position(&self) -> (usize, usize) {
        self.start_position
    }

    fn get_level_content(&mut self, lines: Vec<String>) -> Vec<Vec<SquareType>> {
        let mut grid: Vec<Vec<SquareType>> = Vec::new();
        for line in lines.iter() {
            let mut row: Vec<SquareType> = Vec::new();
            let mut myline = line.clone();
            myline.pop();
            for c in myline.chars() {
                if c == 'P' {
                    self.start_position = (row.len(), grid.len());
                }
                let code: isize = match c.to_digit(4) {
                    Some(d) => d as isize,
                    None => 0isize
                };
                let square_type = match code {
                    0 => SquareType::EMPTY,
                    1 => SquareType::WALL,
                    2 => SquareType::BOX,
                    3 => SquareType::TARGET,
                    4 => SquareType::TARGETVALID,
                    _ => SquareType::EMPTY
                };

                row.push(square_type);
            }
            grid.push(row);
        }
        return grid;
    }

    fn create_grid(content: Vec<Vec<SquareType>>) -> Vec<Vec<Square>> {
        let mut grid: Vec<Vec<Square>> = Vec::new();
        let mut i = 0isize;

        for content_row in content.iter() {
            let mut row: Vec<Square> = Vec::new();
            let mut j = 0isize;
            for square_type in content_row.iter() {
                let s = create_square(j, i, square_type.clone());
                row.push(s);
                j += 1;
            }
            i += 1;
            grid.push(row);
        }
        return grid;
    }

    pub fn update_display(&mut self, player: &Player) {
        self.renderer.clear_screen();
        self.renderer.render_grid(&self.grid, self.boxsize as i32, &self.textures);
        let player_texture = match self.player_textures.get(&player.orientation) {
            Some(t) => t,
            None => panic!(format!("error on texture retrieval for player orientation {}", player.orientation.clone() as isize))
        };
        self.renderer.render_player(&**player_texture, player.get_position(), self.boxsize as i32);
        self.renderer.renderer.drawer().present();
    }

    pub fn is_move_allowed(&self, player: &Player, movement: (i8, i8)) -> bool {
        let (x, y) = player.get_position();
        let (dx, dy) = movement;
        let new_x = x as isize + dx as isize;
        let new_y = y as isize + dy as isize;

        match self.grid[new_y as usize][new_x as usize].square_type {
            SquareType::EMPTY => true,
            SquareType::WALL => false,
            SquareType::BOX | SquareType::TARGETVALID => {
                let pos = ((new_x + dx as isize) as usize, (new_y + dy as isize) as usize);
                self.is_free(pos)
            },
            SquareType::TARGET => true
        }
    }
    pub fn is_free(&self, position: (usize, usize)) -> bool {
        let (x, y) = position;
        match self.grid[y][x].square_type {
            SquareType::EMPTY | SquareType::TARGET => true,
            _ => false
        }
    }
    pub fn is_box_present(&self, position: (usize, usize)) -> bool {
        let (x, y) = position;
        match self.grid[y][x].square_type {
            SquareType::BOX | SquareType::TARGETVALID => true,
            _ => false
        }
    }

    pub fn move_box(&mut self, position: (usize, usize), delta: (i8, i8)) {
        let (x, y) = position;
        let (dx, dy) = delta;
        let new_x = (x as isize + dx as isize) as usize;
        let new_y = (y as isize + dy as isize) as usize;
        match self.grid[y][x].square_type {
            SquareType::BOX => self.grid[y][x].square_type = SquareType::EMPTY,
            SquareType::TARGETVALID => self.grid[y][x].square_type = SquareType::TARGET,
            _ => ()
        };

        match self.grid[new_y][new_x].square_type {
            SquareType::EMPTY => self.grid[new_y][new_x].square_type = SquareType::BOX,
            SquareType::TARGET => self.grid[new_y][new_x].square_type = SquareType::TARGETVALID,
            _ => ()
        };
    }
}

pub struct Square {
    pub x: isize,
    pub y: isize,
    pub square_type: SquareType,
}

pub fn create_square(x: isize, y: isize, square_type: SquareType) -> Square {
    Square{x:x, y:y, square_type: square_type}
}
<|endoftext|>


<commit_before>use ast::{TIdent, TNumber, TString, TSemicolon, TComment, TColon, TDot, TEqual, TOpenParen, TCloseParen, TComma, TOpenBlock, TCloseBlock, TOpenArray, TCloseArray, TQuestion, TNumOp, TBitOp, TCompOp, TLogOp, TArrow};
use ast::{OpAdd, OpSub, OpMul, OpDiv, OpMod};
use ast::{BitAnd, BitOr, BitXor};
use ast::{CompEqual, CompNotEqual, CompLessThan, CompGreaterThan, CompLessThanOrEqual, CompGreaterThanOrEqual};
use ast::{LogAnd, LogOr};
use ast::{Token, TokenData};
use std::io::{BufReader, BufferedReader, Buffer, IoResult, EndOfFile};
use std::strbuf::StrBuf;
use std::char::{from_u32, is_whitespace};
use std::num::from_str_radix;
#[deriving(Clone)]
#[deriving(Eq)]
#[deriving(Show)]
/// The type of string used
pub enum StringType {
	/// Double-quoted
	DoubleQuote,
	/// Single-quoted
	SingleQuote
}
#[deriving(Clone)]
#[deriving(Eq)]
#[deriving(Show)]
/// The type of comment used
pub enum CommentType {
	/// Multi-line comment
	MultiLineComment,
	/// Single-line comment
	SingleLineComment
}
#[deriving(Clone, Eq, Show)]
/// The type of number used
pub enum NumberType {
	/// Decimal number
	DecimalNumber,
	/// Hexadecimal number
	HexadecimalNumber,
	/// Octal number
	OctalNumber
}
/// The Javascript Lexer
pub struct Lexer<B> {
	/// The list of tokens generated so far
	pub tokens : Vec<Token>,
	/// The string buffer for identities
	ident_buffer : StrBuf,
	/// The string buffer for strings
	string_buffer : StrBuf,
	/// The string buffer for comments
	comment_buffer : StrBuf,
	/// The string buffer for numbers
	num_buffer : StrBuf,
	/// The kind of string - i.e. double quote or single quote or none if it isn't in a string
	string_start : Option<StringType>,
	/// The kind of comment
	current_comment : Option<CommentType>,
	/// The kind of number
	current_number : Option<NumberType>,
	/// If a backwards slash has just been read
	escaped : bool,
	/// The current line number
	line_number : uint,
	/// The current column number
	column_number : uint,
	/// The reader
	buffer: B,
	/// The current character
	current_char: Option<char>
}
impl<B:Buffer> Lexer<B> {
	/// Creates a new lexer with empty buffers
	pub fn new(buffer: B) -> Lexer<B> {
		return Lexer {
			tokens: Vec::new(),
			ident_buffer: StrBuf::with_capacity(32),
			string_buffer: StrBuf::with_capacity(256),
			num_buffer: StrBuf::with_capacity(16),
			comment_buffer: StrBuf::with_capacity(32),
			string_start: None,
			current_comment: None,
			current_number: None,
			escaped: false,
			line_number: 1,
			column_number: 0,
			buffer: buffer,
			current_char: None
		};
	}
	fn clear_buffer(&mut self) {
		if self.ident_buffer.len() > 0 {
			let ident = self.ident_buffer.clone().into_owned();
			self.push_token(TIdent(ident));
			self.ident_buffer.truncate(0);
		}
		if self.current_number.is_some() {
			let radix = match self.current_number.unwrap() {
				HexadecimalNumber => 16,
				OctalNumber => 8,
				DecimalNumber => 10
			};
			let num = match from_str_radix(self.num_buffer.as_slice(), radix) {
				Some(v) => v,
				None => fail!("{}:{}: Could not parse '{}' as a base {} number", self.line_number, self.column_number, self.num_buffer, radix)
			};
			self.push_token(TNumber(num));
			self.num_buffer.truncate(0);
			self.current_number = None;
		}
	}
	fn push_token(&mut self, tk:TokenData) {
		self.tokens.push(Token::new(tk, self.line_number, self.column_number))
	}
	/// Processes an input stream from a string into an array of tokens
	pub fn lex_str(script:~str) -> Vec<Token> {
		let script_bytes:&[u8] = script.as_bytes();
		let reader = BufReader::new(script_bytes);
		let buf_reader = BufferedReader::new(reader);
		let mut lexer = Lexer::new(buf_reader);
		lexer.lex().unwrap();
		lexer.tokens
	}
	fn next(&mut self) -> IoResult<char> {
		match self.current_char {
			Some(c) => {
				self.current_char = None;
				Ok(c)
			}
			None => self.buffer.read_char()
		}
	}
	fn peek(&mut self) -> IoResult<char> {
		let ch = try!(self.buffer.read_char());
		self.current_char = Some(ch);
		Ok(ch)
	}
	/// Processes an input stream from a BufferedReader into an array of tokens
	pub fn lex(&mut self) -> IoResult<()> {
		loop {
			let ch = match self.next() {
				Ok(ch) => ch,
				Err(ref err) if err.kind == EndOfFile => break,
				Err(err) => return Err(err)
			};
			self.column_number += 1;
			match ch {
				_ if self.escaped => {
					self.escaped = false;
					if ch != '\n' {
						let escaped_ch = match ch {
							'n' => '\n',
							'r' => '\r',
							't' => '\t',
							'b' => '\x08',
							'f' => '\x0c',
							'0' => '\0',
							'x' => {
								let mut nums = StrBuf::with_capacity(2);
								for _ in range(0, 2) {
									nums.push_char(try!(self.next()).clone());
								}
								self.column_number += 2;
								let as_num = match from_str_radix(nums.as_slice(), 16) {
									Some(v) => v,
									None => 0
								};
								match from_u32(as_num) {
									Some(v) => v,
									None => fail!("{}:{}: {} is not a valid unicode scalar value", self.line_number, self.column_number, as_num)
								}
							},
							'u' => {
								let mut nums = StrBuf::new();
								for _ in range(0, 4) {
									nums.push_char(try!(self.next()));
								}
								self.column_number += 4;
								let as_num = match from_str_radix(nums.as_slice(), 16) {
									Some(v) => v,
									None => 0
								};
								match from_u32(as_num) {
									Some(v) => v,
									None => fail!("{}:{}: {} is not a valid unicode scalar value", self.line_number, self.column_number, as_num)
								}
							},
							'\'' if self.string_start == Some(SingleQuote) => '\'',
							'"' if self.string_start == Some(DoubleQuote) => '"',
							_ => fail!("{}:{}: Invalid escape `{}`", self.line_number, self.column_number, ch)
						};
						self.string_buffer.push_char(escaped_ch);
					}
				},
				'\n' if self.current_comment == Some(SingleLineComment) => {
					let comment = self.comment_buffer.clone().into_owned();
					self.push_token(TComment(comment));
					self.comment_buffer.truncate(0);
					self.current_comment = None;
				},
				'*' if self.current_comment == Some(MultiLineComment) && self.peek() == Ok('/') => {
					self.current_char = None;
					let comment = self.comment_buffer.clone().into_owned();
					self.push_token(TComment(comment));
					self.comment_buffer.truncate(0);
					self.current_comment = None;
				},
				_ if self.current_comment.is_some() => {
					self.comment_buffer.push_char(ch);
				},
				'"' if self.string_start == Some(DoubleQuote) => {
					self.string_start = None;
					let string = self.string_buffer.clone().into_owned();
					self.push_token(TString(string));
					self.string_buffer.truncate(0);
				},
				'\'' if self.string_start == Some(SingleQuote) => {
					self.string_start = None;
					let string = self.string_buffer.clone().into_owned();
					self.push_token(TString(string));
					self.string_buffer.truncate(0);
				},
				'\\' if self.string_start.is_some() => self.escaped = true,
				_ if self.string_start.is_some() => self.string_buffer.push_char(ch),
				'"' if self.string_start.is_none() => self.string_start = Some(DoubleQuote),
				'0' if self.peek() == Ok('x') => {
					self.current_char = None;
					self.current_number = Some(HexadecimalNumber);
				},
				'0' if self.ident_buffer.len() == 0 && self.current_number.is_none() => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(OctalNumber);
				},
				'0'..'7' if self.current_number == Some(OctalNumber) => {
					self.num_buffer.push_char(ch);
				},
				'8' | '9' if self.current_number == Some(OctalNumber) => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(DecimalNumber);
				},
				'0'.. '9' | 'A'.. 'F' | 'a' .. 'f' if self.current_number == Some(HexadecimalNumber) => {
					self.num_buffer.push_char(ch)
				},
				'0'.. '9' if self.ident_buffer.len() == 0 => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(DecimalNumber);
				},
				'\'' if self.string_start.is_none() => self.string_start = Some(SingleQuote),
				'.' if self.current_number.is_some() && !self.num_buffer.as_slice().contains(".") => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(DecimalNumber);
				},
				';' => {
					self.clear_buffer();
					self.push_token(TSemicolon);
				},
				':' => {
					self.clear_buffer();
					self.push_token(TColon);
				},
				'.' => {
					self.clear_buffer();
					self.push_token(TDot);
				},
				'(' => {
					self.clear_buffer();
					self.push_token(TOpenParen);
				},
				')' => {
					self.clear_buffer();
					self.push_token(TCloseParen);
				},
				',' => {
					self.clear_buffer();
					self.push_token(TComma);
				},
				'{' => {
					self.clear_buffer();
					self.push_token(TOpenBlock);
				},
				'}' => {
					self.clear_buffer();
					self.push_token(TCloseBlock);
				},
				'[' => {
					self.clear_buffer();
					self.push_token(TOpenArray);
				},
				']' => {
					self.clear_buffer();
					self.push_token(TCloseArray);
				},
				'?' => {
					self.clear_buffer();
					self.push_token(TQuestion);
				},
				'/' if self.peek() == Ok('/') => {
					self.current_char = None;
					self.current_comment = Some(SingleLineComment);
				},
				'/' if self.peek() == Ok('*') => {
					self.current_char = None;
					self.current_comment = Some(MultiLineComment);
				},
				'/' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpDiv));
				},
				'*' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpMul));
				},
				'+' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpAdd));
				},
				'-' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpSub));
				},
				'%' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpMod));
				},
				'|' if self.peek() == Ok('|') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TLogOp(LogOr));
				},
				'|' => {
					self.clear_buffer();
					self.push_token(TBitOp(BitOr));
				},
				'&' if self.peek() == Ok('&') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TLogOp(LogAnd));
				},
				'&' => {
					self.clear_buffer();
					self.push_token(TBitOp(BitAnd));
				},
				'^' => {
					self.clear_buffer();
					self.push_token(TBitOp(BitXor));
				},
				'=' if self.peek() == Ok('>') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TArrow);
				},
				'=' if self.peek() == Ok('=') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TCompOp(CompEqual));
				},
				'=' => {
					self.clear_buffer();
					self.push_token(TEqual);
				},
				'<' if self.peek() == Ok('=') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TCompOp(CompLessThanOrEqual));
				},
				'<' => {
					self.clear_buffer();
					self.push_token(TCompOp(CompLessThan));
				},
				'>' if self.peek() == Ok('=') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TCompOp(CompGreaterThanOrEqual));
				},
				'>' => {
					self.clear_buffer();
					self.push_token(TCompOp(CompGreaterThan));
				},
				'!' if self.peek() == Ok('=') => {
					self.current_char = None;
					self.clear_buffer();
					self.push_token(TCompOp(CompNotEqual));
				},
				'\n' => {
					self.clear_buffer();
					self.line_number += 1;
					self.column_number = 0;
				},
				'\r' => {
					self.clear_buffer();
					self.column_number = 0;
				},
				_ if is_whitespace(ch) => self.clear_buffer(),
				_ => self.ident_buffer.push_char(ch)
			};
		}
		self.clear_buffer();
		Ok(())
	}
}<commit_msg>Tidy up the lexer and allow nested peeks<commit_after>use ast::{TIdent, TNumber, TString, TSemicolon, TComment, TColon, TDot, TEqual, TOpenParen, TCloseParen, TComma, TOpenBlock, TCloseBlock, TOpenArray, TCloseArray, TQuestion, TNumOp, TBitOp, TCompOp, TLogOp, TArrow};
use ast::{OpAdd, OpSub, OpMul, OpDiv, OpMod};
use ast::{BitAnd, BitOr, BitXor};
use ast::{CompEqual, CompNotEqual, CompLessThan, CompGreaterThan, CompLessThanOrEqual, CompGreaterThanOrEqual};
use ast::{LogAnd, LogOr};
use ast::{Token, TokenData};
use std::io::{BufReader, BufferedReader, Buffer, IoResult, EndOfFile};
use std::strbuf::StrBuf;
use std::char::{from_u32, is_whitespace};
use std::num::from_str_radix;
#[deriving(Clone)]
#[deriving(Eq)]
#[deriving(Show)]
/// The type of string used
pub enum StringType {
	/// Double-quoted
	DoubleQuote,
	/// Single-quoted
	SingleQuote
}
#[deriving(Clone)]
#[deriving(Eq)]
#[deriving(Show)]
/// The type of comment used
pub enum CommentType {
	/// Multi-line comment
	MultiLineComment,
	/// Single-line comment
	SingleLineComment
}
#[deriving(Clone, Eq, Show)]
/// The type of number used
pub enum NumberType {
	/// Decimal number
	DecimalNumber,
	/// Hexadecimal number
	HexadecimalNumber,
	/// Octal number
	OctalNumber
}
/// The Javascript Lexer
pub struct Lexer<B> {
	/// The list of tokens generated so far
	pub tokens : Vec<Token>,
	/// The string buffer for identities
	ident_buffer : StrBuf,
	/// The string buffer for strings
	string_buffer : StrBuf,
	/// The string buffer for comments
	comment_buffer : StrBuf,
	/// The string buffer for numbers
	num_buffer : StrBuf,
	/// The kind of string - i.e. double quote or single quote or none if it isn't in a string
	string_start : Option<StringType>,
	/// The kind of comment
	current_comment : Option<CommentType>,
	/// The kind of number
	current_number : Option<NumberType>,
	/// If a backwards slash has just been read
	escaped : bool,
	/// The current line number
	line_number : uint,
	/// The current column number
	column_number : uint,
	/// The reader
	buffer: B,
	/// The peeked character buffer
	peek_buffer: StrBuf
}
impl<B:Buffer> Lexer<B> {
	/// Creates a new lexer with empty buffers
	pub fn new(buffer: B) -> Lexer<B> {
		return Lexer {
			tokens: Vec::new(),
			ident_buffer: StrBuf::with_capacity(32),
			string_buffer: StrBuf::with_capacity(256),
			num_buffer: StrBuf::with_capacity(16),
			comment_buffer: StrBuf::with_capacity(32),
			string_start: None,
			current_comment: None,
			current_number: None,
			escaped: false,
			line_number: 1,
			column_number: 0,
			buffer: buffer,
			peek_buffer: StrBuf::new()
		};
	}
	fn clear_buffer(&mut self) {
		if self.ident_buffer.len() > 0 {
			let ident = self.ident_buffer.clone().into_owned();
			self.push_token(TIdent(ident));
			self.ident_buffer.truncate(0);
		}
		if self.current_number.is_some() {
			let radix = match self.current_number.unwrap() {
				HexadecimalNumber => 16,
				OctalNumber => 8,
				DecimalNumber => 10
			};
			let num = match from_str_radix(self.num_buffer.as_slice(), radix) {
				Some(v) => v,
				None => fail!("{}:{}: Could not parse '{}' as a base {} number", self.line_number, self.column_number, self.num_buffer, radix)
			};
			self.push_token(TNumber(num));
			self.num_buffer.truncate(0);
			self.current_number = None;
		}
	}
	fn push_token(&mut self, tk:TokenData) {
		self.tokens.push(Token::new(tk, self.line_number, self.column_number))
	}
	/// Processes an input stream from a string into an array of tokens
	pub fn lex_str(script:~str) -> Vec<Token> {
		let script_bytes:&[u8] = script.as_bytes();
		let reader = BufReader::new(script_bytes);
		let buf_reader = BufferedReader::new(reader);
		let mut lexer = Lexer::new(buf_reader);
		lexer.lex().unwrap();
		lexer.tokens
	}
	fn next(&mut self) -> IoResult<char> {
		if self.peek_buffer.len() == 0 {
			self.buffer.read_char()
		} else {
			Ok(self.peek_buffer.pop_char().unwrap())
		}
	}
	fn peek(&mut self) -> IoResult<char> {
		Ok(if self.peek_buffer.len() == 0 {
			let ch = try!(self.buffer.read_char());
			self.peek_buffer.push_char(ch);
			ch
		} else {
			self.peek_buffer.as_slice().char_at(self.peek_buffer.len() - 1)
		})
	}
	fn peek_for(&mut self, peek:char) -> bool {
		if self.peek_buffer.len() > 0 {
			self.peek_buffer.pop_char() == Some(peek)
		} else {
			match self.buffer.read_char() {
				Ok(ch) if ch == peek => true,
				Ok(ch) if ch != peek => {
					self.peek_buffer.push_char(ch);
					false
				},
				_ => false
			}
		}
	}
	/// Processes an input stream from a BufferedReader into an array of tokens
	pub fn lex(&mut self) -> IoResult<()> {
		loop {
			let ch = match self.next() {
				Ok(ch) => ch,
				Err(ref err) if err.kind == EndOfFile => break,
				Err(err) => return Err(err)
			};
			self.column_number += 1;
			match ch {
				_ if self.escaped => {
					self.escaped = false;
					if ch != '\n' {
						let escaped_ch = match ch {
							'n' => '\n',
							'r' => '\r',
							't' => '\t',
							'b' => '\x08',
							'f' => '\x0c',
							'0' => '\0',
							'x' => {
								let mut nums = StrBuf::with_capacity(2);
								for _ in range(0, 2) {
									nums.push_char(try!(self.next()).clone());
								}
								self.column_number += 2;
								let as_num = match from_str_radix(nums.as_slice(), 16) {
									Some(v) => v,
									None => 0
								};
								match from_u32(as_num) {
									Some(v) => v,
									None => fail!("{}:{}: {} is not a valid unicode scalar value", self.line_number, self.column_number, as_num)
								}
							},
							'u' => {
								let mut nums = StrBuf::new();
								for _ in range(0, 4) {
									nums.push_char(try!(self.next()));
								}
								self.column_number += 4;
								let as_num = match from_str_radix(nums.as_slice(), 16) {
									Some(v) => v,
									None => 0
								};
								match from_u32(as_num) {
									Some(v) => v,
									None => fail!("{}:{}: {} is not a valid unicode scalar value", self.line_number, self.column_number, as_num)
								}
							},
							'\'' if self.string_start == Some(SingleQuote) => '\'',
							'"' if self.string_start == Some(DoubleQuote) => '"',
							_ => fail!("{}:{}: Invalid escape `{}`", self.line_number, self.column_number, ch)
						};
						self.string_buffer.push_char(escaped_ch);
					}
				},
				'\n' if self.current_comment == Some(SingleLineComment) => {
					let comment = self.comment_buffer.clone().into_owned();
					self.push_token(TComment(comment));
					self.comment_buffer.truncate(0);
					self.current_comment = None;
				},
				'*' if self.current_comment == Some(MultiLineComment) && self.peek() == Ok('/') => {
					let comment = self.comment_buffer.clone().into_owned();
					self.push_token(TComment(comment));
					self.comment_buffer.truncate(0);
					self.current_comment = None;
				},
				_ if self.current_comment.is_some() => {
					self.comment_buffer.push_char(ch);
				},
				'"' if self.string_start == Some(DoubleQuote) => {
					self.string_start = None;
					let string = self.string_buffer.clone().into_owned();
					self.push_token(TString(string));
					self.string_buffer.truncate(0);
				},
				'\'' if self.string_start == Some(SingleQuote) => {
					self.string_start = None;
					let string = self.string_buffer.clone().into_owned();
					self.push_token(TString(string));
					self.string_buffer.truncate(0);
				},
				'\\' if self.string_start.is_some() => self.escaped = true,
				_ if self.string_start.is_some() => self.string_buffer.push_char(ch),
				'"' if self.string_start.is_none() => self.string_start = Some(DoubleQuote),
				'0' if self.peek_for('x') => {
					self.current_number = Some(HexadecimalNumber);
				},
				'0' if self.ident_buffer.len() == 0 && self.current_number.is_none() => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(OctalNumber);
				},
				'0'..'7' if self.current_number == Some(OctalNumber) => {
					self.num_buffer.push_char(ch);
				},
				'8' | '9' if self.current_number == Some(OctalNumber) => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(DecimalNumber);
				},
				'0'.. '9' | 'A'.. 'F' | 'a' .. 'f' if self.current_number == Some(HexadecimalNumber) => {
					self.num_buffer.push_char(ch)
				},
				'0'.. '9' if self.ident_buffer.len() == 0 => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(DecimalNumber);
				},
				'\'' if self.string_start.is_none() => self.string_start = Some(SingleQuote),
				'.' if self.current_number.is_some() && !self.num_buffer.as_slice().contains(".") => {
					self.num_buffer.push_char(ch);
					self.current_number = Some(DecimalNumber);
				},
				';' => {
					self.clear_buffer();
					self.push_token(TSemicolon);
				},
				':' => {
					self.clear_buffer();
					self.push_token(TColon);
				},
				'.' => {
					self.clear_buffer();
					self.push_token(TDot);
				},
				'(' => {
					self.clear_buffer();
					self.push_token(TOpenParen);
				},
				')' => {
					self.clear_buffer();
					self.push_token(TCloseParen);
				},
				',' => {
					self.clear_buffer();
					self.push_token(TComma);
				},
				'{' => {
					self.clear_buffer();
					self.push_token(TOpenBlock);
				},
				'}' => {
					self.clear_buffer();
					self.push_token(TCloseBlock);
				},
				'[' => {
					self.clear_buffer();
					self.push_token(TOpenArray);
				},
				']' => {
					self.clear_buffer();
					self.push_token(TCloseArray);
				},
				'?' => {
					self.clear_buffer();
					self.push_token(TQuestion);
				},
				'/' if self.peek_for('/') => {
					self.current_comment = Some(SingleLineComment);
				},
				'/' if self.peek_for('*') => {
					self.current_comment = Some(MultiLineComment);
				},
				'/' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpDiv));
				},
				'*' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpMul));
				},
				'+' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpAdd));
				},
				'-' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpSub));
				},
				'%' => {
					self.clear_buffer();
					self.push_token(TNumOp(OpMod));
				},
				'|' if self.peek_for('|') => {
					self.clear_buffer();
					self.push_token(TLogOp(LogOr));
				},
				'|' => {
					self.clear_buffer();
					self.push_token(TBitOp(BitOr));
				},
				'&' if self.peek_for('&') => {
					self.clear_buffer();
					self.push_token(TLogOp(LogAnd));
				},
				'&' => {
					self.clear_buffer();
					self.push_token(TBitOp(BitAnd));
				},
				'^' => {
					self.clear_buffer();
					self.push_token(TBitOp(BitXor));
				},
				'=' if self.peek_for('>') => {
					self.clear_buffer();
					self.push_token(TArrow);
				},
				'=' if self.peek_for('=') => {
					self.clear_buffer();
					self.push_token(TCompOp(CompEqual));
				},
				'=' => {
					self.clear_buffer();
					self.push_token(TEqual);
				},
				'<' if self.peek_for('=') => {
					self.clear_buffer();
					self.push_token(TCompOp(CompLessThanOrEqual));
				},
				'<' => {
					self.clear_buffer();
					self.push_token(TCompOp(CompLessThan));
				},
				'>' if self.peek_for('=') => {
					self.clear_buffer();
					self.push_token(TCompOp(CompGreaterThanOrEqual));
				},
				'>' => {
					self.clear_buffer();
					self.push_token(TCompOp(CompGreaterThan));
				},
				'!' if self.peek_for('=') => {
					self.clear_buffer();
					self.push_token(TCompOp(CompNotEqual));
				},
				'\n' => {
					self.clear_buffer();
					self.line_number += 1;
					self.column_number = 0;
				},
				'\r' => {
					self.clear_buffer();
					self.column_number = 0;
				},
				_ if is_whitespace(ch) => self.clear_buffer(),
				_ => self.ident_buffer.push_char(ch)
			};
		}
		self.clear_buffer();
		Ok(())
	}
}<|endoftext|>