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

use std::env;
use std::ffi::OsString;

use common::ui::UI;
use hcore::crypto::CACHE_KEY_PATH_ENV_VAR;
use hcore::env as henv;
use hcore::fs::CACHE_KEY_PATH;
use hcore::os::users;

use config;
use error::Result;

pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
    try!(inner::rerun_with_sudo_if_needed(ui));

    // If the `$HAB_ORIGIN` environment variable is not present, then see if a default is set in
    // the CLI config. If so, set it as the `$HAB_ORIGIN` environment variable for the `hab-studio`
    // or `docker` execv call.
    if henv::var("HAB_ORIGIN").is_err() {
        let config = try!(config::load_with_sudo_user());
        if let Some(default_origin) = config.origin {
            debug!("Setting default origin {} via CLI config", &default_origin);
            env::set_var("HAB_ORIGIN", default_origin);
        }
    }

    // If the `$HAB_CACHE_KEY_PATH` environment variable is not present, check if we are running
    // under a `sudo` invocation. If so, determine the non-root user that issued the command in
    // order to set their key cache location in the environment variable. This is done so that the
    // `hab-studio` command will find the correct key cache or so that the correct directory will
    // be volume mounted when used with Docker.
    if henv::var(CACHE_KEY_PATH_ENV_VAR).is_err() {
        if let Some(sudo_user) = henv::sudo_user() {
            if let Some(home) = users::get_home_for_user(&sudo_user) {
                let cache_key_path = home.join(format!(".{}", CACHE_KEY_PATH));
                debug!("Setting cache_key_path for SUDO_USER={} to: {}",
                       &sudo_user,
                       cache_key_path.display());
                env::set_var(CACHE_KEY_PATH_ENV_VAR, cache_key_path);
                // Prevent any inner `hab` invocations from triggering similar logic: we will be
                // operating in the context `hab-studio` which is running with rootlike privileges.
                env::remove_var("SUDO_USER");
            }
        }
    }

    inner::start(ui, args)
}

#[cfg(target_os = "linux")]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{am_i_root, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;

    const STUDIO_CMD: &'static str = "hab-studio";
    const STUDIO_CMD_ENVVAR: &'static str = "HAB_STUDIO_BINARY";
    const STUDIO_PACKAGE_IDENT: &'static str = "core/hab-studio";
    const SUDO_CMD: &'static str = "sudo";

    pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let ident = try!(PackageIdent::from_str(STUDIO_PACKAGE_IDENT));
                try!(exec::command_from_pkg(ui,
                                            STUDIO_CMD,
                                            &ident,
                                            &default_cache_key_path(None),
                                            0))
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            try!(process::become_command(cmd, args));
        } else {
            return Err(Error::ExecCommandNotFound(command.to_string_lossy().into_owned()));
        }
        Ok(())
    }

    pub fn rerun_with_sudo_if_needed(ui: &mut UI) -> Result<()> {
        // If I have root permissions, early return, we are done.
        if am_i_root() {
            return Ok(());
        }

        // Otherwise we will try to re-run this program using `sudo`
        match find_command(SUDO_CMD) {
            Some(sudo_prog) => {
                let mut args: Vec<OsString> =
                    vec!["-p".into(), "[sudo hab-studio] password for %u: ".into(), "-E".into()];
                args.append(&mut env::args_os().collect());
                Ok(try!(process::become_command(sudo_prog, args)))
            }
            None => {
                try!(ui.warn(format!("Could not find the `{}' command, is it in your PATH?",
                                     SUDO_CMD)));
                try!(ui.warn("Running Habitat Studio requires root or administrator privileges. \
                              Please retry this command as a super user or use a \
                              privilege-granting facility such as sudo."));
                try!(ui.br());
                Err(Error::RootRequired)
            }
        }
    }
}

#[cfg(not(target_os = "linux"))]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::process::{Command, Stdio};

    use common::ui::UI;
    use hcore::crypto::default_cache_key_path;
    use hcore::env as henv;
    use hcore::fs::{CACHE_KEY_PATH, find_command};
    use hcore::os::process;

    use error::{Error, Result};
    use VERSION;

    const DOCKER_CMD: &'static str = "docker";
    const DOCKER_CMD_ENVVAR: &'static str = "HAB_DOCKER_BINARY";
    const DOCKER_IMAGE: &'static str = "habitat-docker-registry.bintray.io/studio";
    const DOCKER_IMAGE_ENVVAR: &'static str = "HAB_DOCKER_STUDIO_IMAGE";

    pub fn start(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let docker = henv::var(DOCKER_CMD_ENVVAR).unwrap_or(DOCKER_CMD.to_string());

        let cmd = match find_command(&docker) {
            Some(cmd) => cmd,
            None => return Err(Error::ExecCommandNotFound(docker.to_string())),
        };

        let child = Command::new(&cmd)
            .arg("pull")
            .arg(&image_identifier())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .expect("docker failed to start");

        let output = child.wait_with_output()
            .expect("failed to wait on child");

        if output.status.success() {
            debug!("Docker image is reachable. Proceeding with launching docker.");
        } else {
            debug!("Docker image is unreachable. Exit code = {:?}",
                   output.status);

            let err_output = String::from_utf8(output.stderr).unwrap();

            if err_output.contains("image") && err_output.contains("not found") {
                return Err(Error::DockerImageNotFound(image_identifier().to_string()));
            } else if err_output.contains("Cannot connect to the Docker daemon") {
                return Err(Error::DockerDaemonDown);
            } else {
                return Err(Error::DockerNetworkDown(image_identifier().to_string()));
            }
        }

        let mut cmd_args: Vec<OsString> = vec!["run".into(),
                                               "--rm".into(),
                                               "--tty".into(),
                                               "--interactive".into(),
                                               "--privileged".into()];

        let env_vars = vec!["HAB_DEPOT_URL", "HAB_ORIGIN", "http_proxy", "https_proxy"];
        for var in env_vars {
            if let Ok(val) = henv::var(var) {
                debug!("Propagating environment variable into container: {}={}",
                       var,
                       val);
                cmd_args.push("--env".into());
                cmd_args.push(format!("{}={}", var, val).into());
            }
        }

        cmd_args.push("--volume".into());
        cmd_args.push("/var/run/docker.sock:/var/run/docker.sock".into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/{}",
                              default_cache_key_path(None).to_string_lossy(),
                              CACHE_KEY_PATH)
            .into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/src", env::current_dir().unwrap().to_string_lossy()).into());
        cmd_args.push(image_identifier().into());
        cmd_args.extend_from_slice(args.as_slice());

        for var in vec!["http_proxy", "https_proxy"] {
            if let Ok(_) = henv::var(var) {
                debug!("Unsetting proxy environment variable '{}' before calling `{}'",
                       var,
                       docker);
                env::remove_var(var);
            }
        }

        Ok(try!(process::become_command(cmd, cmd_args)))
    }

    pub fn rerun_with_sudo_if_needed(_ui: &mut UI) -> Result<()> {
        // No sudo calls necessary here--we are calling `docker` commands instead
        Ok(())
    }

    /// Returns the Docker Studio image with tag for the desired version which corresponds to the
    /// same version (minus release) as this program.
    fn image_identifier() -> String {
        let version: Vec<&str> = VERSION.split("/").collect();
        henv::var(DOCKER_IMAGE_ENVVAR).unwrap_or(format!("{}:{}", DOCKER_IMAGE, version[0]))
    }

    #[cfg(test)]
    mod tests {
        use super::{image_identifier, DOCKER_IMAGE};
        use VERSION;

        #[test]
        fn retrieve_image_identifier() {
            assert_eq!(image_identifier(), format!("{}:{}", DOCKER_IMAGE, VERSION));
        }
    }
}
<commit_msg>Improve user output - show Docker pull progress on studio enter<commit_after>// Copyright (c) 2016 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::env;
use std::ffi::OsString;

use common::ui::UI;
use hcore::crypto::CACHE_KEY_PATH_ENV_VAR;
use hcore::env as henv;
use hcore::fs::CACHE_KEY_PATH;
use hcore::os::users;

use config;
use error::Result;

pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
    try!(inner::rerun_with_sudo_if_needed(ui));

    // If the `$HAB_ORIGIN` environment variable is not present, then see if a default is set in
    // the CLI config. If so, set it as the `$HAB_ORIGIN` environment variable for the `hab-studio`
    // or `docker` execv call.
    if henv::var("HAB_ORIGIN").is_err() {
        let config = try!(config::load_with_sudo_user());
        if let Some(default_origin) = config.origin {
            debug!("Setting default origin {} via CLI config", &default_origin);
            env::set_var("HAB_ORIGIN", default_origin);
        }
    }

    // If the `$HAB_CACHE_KEY_PATH` environment variable is not present, check if we are running
    // under a `sudo` invocation. If so, determine the non-root user that issued the command in
    // order to set their key cache location in the environment variable. This is done so that the
    // `hab-studio` command will find the correct key cache or so that the correct directory will
    // be volume mounted when used with Docker.
    if henv::var(CACHE_KEY_PATH_ENV_VAR).is_err() {
        if let Some(sudo_user) = henv::sudo_user() {
            if let Some(home) = users::get_home_for_user(&sudo_user) {
                let cache_key_path = home.join(format!(".{}", CACHE_KEY_PATH));
                debug!("Setting cache_key_path for SUDO_USER={} to: {}",
                       &sudo_user,
                       cache_key_path.display());
                env::set_var(CACHE_KEY_PATH_ENV_VAR, cache_key_path);
                // Prevent any inner `hab` invocations from triggering similar logic: we will be
                // operating in the context `hab-studio` which is running with rootlike privileges.
                env::remove_var("SUDO_USER");
            }
        }
    }

    inner::start(ui, args)
}

#[cfg(target_os = "linux")]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::path::PathBuf;
    use std::str::FromStr;

    use common::ui::UI;
    use hcore::crypto::{init, default_cache_key_path};
    use hcore::env as henv;
    use hcore::fs::{am_i_root, find_command};
    use hcore::os::process;
    use hcore::package::PackageIdent;

    use error::{Error, Result};
    use exec;

    const STUDIO_CMD: &'static str = "hab-studio";
    const STUDIO_CMD_ENVVAR: &'static str = "HAB_STUDIO_BINARY";
    const STUDIO_PACKAGE_IDENT: &'static str = "core/hab-studio";
    const SUDO_CMD: &'static str = "sudo";

    pub fn start(ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let command = match henv::var(STUDIO_CMD_ENVVAR) {
            Ok(command) => PathBuf::from(command),
            Err(_) => {
                init();
                let ident = try!(PackageIdent::from_str(STUDIO_PACKAGE_IDENT));
                try!(exec::command_from_pkg(ui,
                                            STUDIO_CMD,
                                            &ident,
                                            &default_cache_key_path(None),
                                            0))
            }
        };

        if let Some(cmd) = find_command(command.to_string_lossy().as_ref()) {
            try!(process::become_command(cmd, args));
        } else {
            return Err(Error::ExecCommandNotFound(command.to_string_lossy().into_owned()));
        }
        Ok(())
    }

    pub fn rerun_with_sudo_if_needed(ui: &mut UI) -> Result<()> {
        // If I have root permissions, early return, we are done.
        if am_i_root() {
            return Ok(());
        }

        // Otherwise we will try to re-run this program using `sudo`
        match find_command(SUDO_CMD) {
            Some(sudo_prog) => {
                let mut args: Vec<OsString> =
                    vec!["-p".into(), "[sudo hab-studio] password for %u: ".into(), "-E".into()];
                args.append(&mut env::args_os().collect());
                Ok(try!(process::become_command(sudo_prog, args)))
            }
            None => {
                try!(ui.warn(format!("Could not find the `{}' command, is it in your PATH?",
                                     SUDO_CMD)));
                try!(ui.warn("Running Habitat Studio requires root or administrator privileges. \
                              Please retry this command as a super user or use a \
                              privilege-granting facility such as sudo."));
                try!(ui.br());
                Err(Error::RootRequired)
            }
        }
    }
}

#[cfg(not(target_os = "linux"))]
mod inner {
    use std::env;
    use std::ffi::OsString;
    use std::process::{Command, Stdio};

    use common::ui::UI;
    use hcore::crypto::default_cache_key_path;
    use hcore::env as henv;
    use hcore::fs::{CACHE_KEY_PATH, find_command};
    use hcore::os::process;

    use error::{Error, Result};
    use VERSION;

    const DOCKER_CMD: &'static str = "docker";
    const DOCKER_CMD_ENVVAR: &'static str = "HAB_DOCKER_BINARY";
    const DOCKER_IMAGE: &'static str = "habitat-docker-registry.bintray.io/studio";
    const DOCKER_IMAGE_ENVVAR: &'static str = "HAB_DOCKER_STUDIO_IMAGE";

    pub fn start(_ui: &mut UI, args: Vec<OsString>) -> Result<()> {
        let docker = henv::var(DOCKER_CMD_ENVVAR).unwrap_or(DOCKER_CMD.to_string());

        let cmd = match find_command(&docker) {
            Some(cmd) => cmd,
            None => return Err(Error::ExecCommandNotFound(docker.to_string())),
        };

        let child = Command::new(&cmd)
            .arg("pull")
            .arg(&image_identifier())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .spawn()
            .expect("docker failed to start");

        let output = child.wait_with_output()
            .expect("failed to wait on child");

        if output.status.success() {
            debug!("Docker image is reachable. Proceeding with launching docker.");
        } else {
            debug!("Docker image is unreachable. Exit code = {:?}",
                   output.status);

            let err_output = String::from_utf8(output.stderr).unwrap();

            if err_output.contains("image") && err_output.contains("not found") {
                return Err(Error::DockerImageNotFound(image_identifier().to_string()));
            } else if err_output.contains("Cannot connect to the Docker daemon") {
                return Err(Error::DockerDaemonDown);
            } else {
                return Err(Error::DockerNetworkDown(image_identifier().to_string()));
            }
        }

        let mut cmd_args: Vec<OsString> = vec!["run".into(),
                                               "--rm".into(),
                                               "--tty".into(),
                                               "--interactive".into(),
                                               "--privileged".into()];

        let env_vars = vec!["HAB_DEPOT_URL", "HAB_ORIGIN", "http_proxy", "https_proxy"];
        for var in env_vars {
            if let Ok(val) = henv::var(var) {
                debug!("Propagating environment variable into container: {}={}",
                       var,
                       val);
                cmd_args.push("--env".into());
                cmd_args.push(format!("{}={}", var, val).into());
            }
        }

        cmd_args.push("--volume".into());
        cmd_args.push("/var/run/docker.sock:/var/run/docker.sock".into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/{}",
                              default_cache_key_path(None).to_string_lossy(),
                              CACHE_KEY_PATH)
            .into());
        cmd_args.push("--volume".into());
        cmd_args.push(format!("{}:/src", env::current_dir().unwrap().to_string_lossy()).into());
        cmd_args.push(image_identifier().into());
        cmd_args.extend_from_slice(args.as_slice());

        for var in vec!["http_proxy", "https_proxy"] {
            if let Ok(_) = henv::var(var) {
                debug!("Unsetting proxy environment variable '{}' before calling `{}'",
                       var,
                       docker);
                env::remove_var(var);
            }
        }

        Ok(try!(process::become_command(cmd, cmd_args)))
    }

    pub fn rerun_with_sudo_if_needed(_ui: &mut UI) -> Result<()> {
        // No sudo calls necessary here--we are calling `docker` commands instead
        Ok(())
    }

    /// Returns the Docker Studio image with tag for the desired version which corresponds to the
    /// same version (minus release) as this program.
    fn image_identifier() -> String {
        let version: Vec<&str> = VERSION.split("/").collect();
        henv::var(DOCKER_IMAGE_ENVVAR).unwrap_or(format!("{}:{}", DOCKER_IMAGE, version[0]))
    }

    #[cfg(test)]
    mod tests {
        use super::{image_identifier, DOCKER_IMAGE};
        use VERSION;

        #[test]
        fn retrieve_image_identifier() {
            assert_eq!(image_identifier(), format!("{}:{}", DOCKER_IMAGE, VERSION));
        }
    }
}
<|endoftext|>


<commit_before>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

error_chain! {
    types {
        ContactError, ContactErrorKind, ResultExt, Result;
    }

    links {
        StoreError(::libimagstore::error::StoreError, ::libimagstore::error::StoreErrorKind);
        RefError(::libimagentryref::error::RefError, ::libimagentryref::error::RefErrorKind);
    }

    foreign_links {
        Io(::std::io::Error);
        TomlQueryError(::toml_query::error::Error);
        VObjectError(::vobject::ParseError);
    }

    errors {

    }
}

<commit_msg>Add error for header type error<commit_after>//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//

error_chain! {
    types {
        ContactError, ContactErrorKind, ResultExt, Result;
    }

    links {
        StoreError(::libimagstore::error::StoreError, ::libimagstore::error::StoreErrorKind);
        RefError(::libimagentryref::error::RefError, ::libimagentryref::error::RefErrorKind);
    }

    foreign_links {
        Io(::std::io::Error);
        TomlQueryError(::toml_query::error::Error);
        VObjectError(::vobject::ParseError);
    }

    errors {

        HeaderTypeError(ty: &'static str, loc: &'static str) {
            description("Type error in header")
            display("Type error in header, expected {} at '{}', found other type", ty, loc)
        }

    }
}

<|endoftext|>


<commit_before>use std::vec::Vec;
use tokens::*;
use lexer::*;
use parser::*;
use symbols::*;
use symbol_table::*;
use support::*;
use plp::PLPWriter

pub fn compile_class(tokens: &Vec<Token>, start_index: usize) -> (usize, String)
{
    let invalid_types = get_invalid_token_types();
    let invalid_values = get_invalid_token_values();

    let mut plp_string: String = String::new();
    let mut current_index: usize = start_index;

    if tokens[current_index].value != "{" { panic!("Expected '{{' received: {}", tokens[current_index].value); }
    else { current_index += 1; }

    for (index, token) in tokens.iter().enumerate()
    {
        // Handle forward skipping
        if index < current_index { continue; }
        else { current_index = index; }

        // TODO: encapsulate into token_rules.validate(token)
        // Panic! if token type is invalid
        if invalid_types.contains(&token.name)
        {
            panic!("Unsupported token type: {}", token.name);
        }
        // Panic! if token value is invalid
        else if invalid_values.contains(&&*token.value)
        {
            panic!("Unsupported token value: {}", token.value);
        }

        if token.value == "class"
        {
            // parse class body
            let (end_index, compiled_class) = compile_class(&tokens, index + 1);
            plp_string.push_str(&*compiled_class);

            current_index = end_index;
        }
        else if token.name == "type" // || token.name == "identifier"
        {
            // look ahead
            // parse variable declaration
            // OR parse method declaration
        }
        else if token.value == "if"
        {
            // parse if body
            // Unsupported for now
        }
        else if token.name == "construct.conditional"
        {
            // parse conditional
            // Unsupported for now
        }
        else
        {
            panic!("Unexpected token: {}\t{}", token.name, token.value);
        }
    }

    (current_index + 1, plp_string)
}

/// Write PLP code to evaluate the given symbol sequence, and load the result into a specific register
/// A sequence can be:
/// * a  simple variable reference,
/// * a method call,
/// * a variable accessed from another symbol (e.g. foo.bar or Foo.staticBar),
/// * a method accessed from another symbol (e.g. foo.bar() or Foo.staticBar()), or
/// * a complex chain of the above (e.g. foo.method().valueInReturnValue.value.method())
///
/// The start index should be the first symbol in the sequence
/// @return (plp_code, the first index AFTER this symbol sequence)
pub fn compile_symbol_sequence( tokens: &Vec<Token>,
                            start: usize,
                            current_namespace: &str,
                            temp_register: &str, // indirect
                            load_registers: (&str, &str),
                            target_register: &str,
                            symbols: &StaticSymbolTable)
                            -> (String, usize)
{
    // TODO: handle array access

    let mut plp = PLPWriter::new();
    let mut index = start;
    while index < (tokens.len() - 1)
    {
        let token = &tokens[index];

        if token.name == "identifier"
        {
            let lookahead_token = &tokens[index + 1];

            // Method call
            if lookahead_token.value == "("
            {
                // compile the method and append it directly to the compiled plp code
                let method_code = compile_method_call(tokens, index, current_namespace, temp_register, load_registers, symbols);
                plp.code.push_str(&*method_code);
            }
            // Variable read
            else
            {
                let symbol = symbols.lookup_variable(current_namespace, &*token.value).unwrap();
                match symbol.location
                {
                    SymbolLocation::Register(name) => {
                            plp.mov(target_register, name);
                        },
                    SymbolLocation::Memory(address) => {
                            plp.li(load_registers.0, address.label_name);
                            plp.lw(target_register, address.offset, load_registers.0);
                        },
                    SymbolLocation::InstancedMemory(offset) => {
                            plp.lw(target_register, offset, target_register);
                        },
                    SymbolLocation::Structured => {
                            // TODO: append to namespace
                        },
                };
            }
        }
        else if token.value == "."
        {
            // Access references are handled when it's children are parsed (in the if block above)
            // so skip this token
            index += 1;
            continue;
        }
        else
        {
            break;
        }

        index += 1;
    }
    // first index AFTER the sequence
    index += 1;

    // TODO: compile

    (plp.code, index)
}

/// The range should start at the method identifier and end on the token AFTER the closing parenthesis
pub fn compile_method_call( tokens: &Vec<Token>,
                        start: usize,
                        current_namespace: &str,
                        arg_register: &str,
                        load_registers: (&str, &str),
                        symbols: &StaticSymbolTable)
                        -> String
{
    let mut plp = PLPWriter::new();

    // start at the token AFTER the open parenthesis
    let mut index = start + 2;

    // Index OF the closing parenthesis
    let end_index = identify_body_bounds(&tokens, index, ("(", ")")).unwrap();

    // TODO: Keep track of argument types, in order, to determine the method signature
    let mut argument_types: Vec<&str> = Vec::new();

    while index < end_index
    {
        let token = &tokens[index];
        if token.value == "("
        {
            panic!("Parenthesis surrounding expression currently unsupported");
        }
        else if token.value == ","
        {
            // Skip commas, arguments are separated by the stack divisors
            index += 1;
            continue;
        }
        else
        {
            // Load argument into arg_register
            let (code, new_index) = compile_arithmetic_statement(tokens, index, current_namespace, ("$t9", "$t8"), load_registers, arg_register, symbols);
            plp.code.push_str(&*code);
            index = new_index;

            // Push argument to the stack
            plp.push(arg_register);

            // TODO: push argument_type to argument_types
        }
    }

    // Find nested method calls
    // Handle each argument one at a time, and push each to the stack

    // TODO: determine namespace from caller and current_namespace
    let namespace = "";

    let id_token = &tokens[start];
    let method_name = &*id_token.value;
    // TODO: determine if method is static
    // TODO: if function is non-static, push $this to stack

    let method_symbol = symbols.lookup_function(namespace, method_name, &argument_types).unwrap();
    match method_symbol.location
    {
        SymbolLocation::Register(name) => {
                panic!("Found method at a Register instead of a constant Memory address");
            },
        SymbolLocation::Memory(address) => {
                plp.call(address.label_name);
            },
        SymbolLocation::InstancedMemory(offset) => {
                panic!("Found method at InstancedMemory instead of a constant Memory address");
            },
        SymbolLocation::Structured => {
                // TODO: call constructor
                panic!("Constructors currently unsupported");
            },
    };

    return plp.code;
}

/// Future revision: @return (code, end_index, result_type)
pub fn compile_arithmetic_statement(tokens: &Vec<Token>,            // used
                                start: usize,                   // used
                                current_namespace: &str,        // indirect
                                temp_registers: (&str, &str),   // used
                                load_registers: (&str, &str),
                                target_register: &str,
                                symbols: &StaticSymbolTable)
                                -> (String, usize)
{
    // TODO: handle parenthesis and operder of operations
    let mut plp = PLPWriter::new();

    // Evaluate first symbol and store it in target_register
    let mut index = compile_evaluation(tokens, start, current_namespace, temp_registers.0, load_registers, target_register, symbols, &mut plp);

    // loop until arithmetic sequence ends
    let mut operator_token = &tokens[index];
    while operator_token.name.starts_with("operator")
    {
        // PRESUMPTION: The first operand is stored in target_register

        // Evaluate the second operand and store the result in temp_registers.1
        // Do not alter target_register
        index = compile_evaluation(tokens, index + 1, current_namespace, temp_registers.0, load_registers, temp_registers.1, symbols, &mut plp);

        // Perform the operation on the current result (target_register) and the second operand (temp_registers.1)
        compile_arithmetic_operation(&operator_token, (target_register, temp_registers.1), target_register);
        operator_token = &tokens[index];
    }

    (plp.code, index)
}

/// Writes plp code to evaluate a value triggered by the start token
/// If the token is a literal, the literal value will be loaded into the target_register
/// If the token is an identifier, it will be evaluated based on what the symbol represents
/// * If the symbol represents a method, the method will be called and the result stored in target_register
/// * If the symbol represents a variable, or a chain of accessors, the sequence will be evaluated and the result stored in target_register
///
/// This method will compile plp code directly to a PLPWriter as specified
pub fn compile_evaluation(  tokens: &Vec<Token>,            // used
                        start: usize,                   // used
                        current_namespace: &str,        // indirect-------------
                        temp_register: &str,            // used
                        load_registers: (&str, &str),   // indirect-------------
                        target_register: &str,          // used
                        symbols: &StaticSymbolTable,    // indirect-------------
                        plp: &mut PLPWriter)            // used
                        -> usize
{
    let mut token = &tokens[start];
    let mut end_index = start;

    if token.name.starts_with("literal")
    {
        let value = &*token.value;
        plp.li(target_register, value);

        end_index += 1;
    }
    else if token.name == "identifier"
    {
        // handle identifier
        let (access_code, new_index) = compile_symbol_sequence(tokens, start, current_namespace, temp_register, load_registers, target_register, symbols);
        plp.code.push_str(&*access_code);

        end_index = new_index;
    }
    else if token.value == "("
    {
        // TODO: find end bounds and evaluate parenthetical expression
        panic!("Unexpected token: {}\t{}", token.name, token.value);
    }
    else
    {
        panic!("Unexpected token: {}\t{}", token.name, token.value);
    }

    end_index
}

/// Writes plp code to perform a binary operation on two registers, and store the result in a third register
/// The specified registers need not be unique, and may all be the same if desired
/// This method performs only a single operation, and does not check for register validity
/// The register arguments are assumed to be prefaced with '$'
pub fn compile_arithmetic_operation(operator: &Token, operand_registers: (&str, &str), result_register: &str) -> String
{
    let mut plp = PLPWriter::new();

    match &*operator.value
    {
        "+" => plp.addu(result_register, operand_registers.0, operand_registers.1),
        "-" => plp.subu(result_register, operand_registers.0, operand_registers.1),
        "*" => plp.mullo(result_register, operand_registers.0, operand_registers.1),
         _  => panic!("Unsupported operator: {}: {}", operator.name, operator.value)
    };

    return plp.code;
}
<commit_msg>Make compile_arithmetic_statement recursive<commit_after>use std::vec::Vec;
use tokens::*;
use lexer::*;
use parser::*;
use symbols::*;
use symbol_table::*;
use support::*;
use plp::PLPWriter;

pub fn compile_class(tokens: &Vec<Token>, start_index: usize) -> (usize, String)
{
    let invalid_types = get_invalid_token_types();
    let invalid_values = get_invalid_token_values();

    let mut plp_string: String = String::new();
    let mut current_index: usize = start_index;

    if tokens[current_index].value != "{" { panic!("Expected '{{' received: {}", tokens[current_index].value); }
    else { current_index += 1; }

    for (index, token) in tokens.iter().enumerate()
    {
        // Handle forward skipping
        if index < current_index { continue; }
        else { current_index = index; }

        // TODO: encapsulate into token_rules.validate(token)
        // Panic! if token type is invalid
        if invalid_types.contains(&token.name)
        {
            panic!("Unsupported token type: {}", token.name);
        }
        // Panic! if token value is invalid
        else if invalid_values.contains(&&*token.value)
        {
            panic!("Unsupported token value: {}", token.value);
        }

        if token.value == "class"
        {
            // parse class body
            let (end_index, compiled_class) = compile_class(&tokens, index + 1);
            plp_string.push_str(&*compiled_class);

            current_index = end_index;
        }
        else if token.name == "type" // || token.name == "identifier"
        {
            // look ahead
            // parse variable declaration
            // OR parse method declaration
        }
        else if token.value == "if"
        {
            // parse if body
            // Unsupported for now
        }
        else if token.name == "construct.conditional"
        {
            // parse conditional
            // Unsupported for now
        }
        else
        {
            panic!("Unexpected token: {}\t{}", token.name, token.value);
        }
    }

    (current_index + 1, plp_string)
}

/// Write PLP code to evaluate the given symbol sequence, and load the result into a specific register
/// A sequence can be:
/// * a  simple variable reference,
/// * a method call,
/// * a variable accessed from another symbol (e.g. foo.bar or Foo.staticBar),
/// * a method accessed from another symbol (e.g. foo.bar() or Foo.staticBar()), or
/// * a complex chain of the above (e.g. foo.method().valueInReturnValue.value.method())
///
/// The start index should be the first symbol in the sequence
/// @return (plp_code, the first index AFTER this symbol sequence)
pub fn compile_symbol_sequence( tokens: &Vec<Token>,
                            start: usize,
                            current_namespace: &str,
                            temp_register: &str, // indirect
                            load_registers: (&str, &str),
                            target_register: &str,
                            symbols: &StaticSymbolTable)
                            -> (String, usize)
{
    // TODO: handle array access

    let mut plp = PLPWriter::new();
    let mut index = start;
    while index < (tokens.len() - 1)
    {
        let token = &tokens[index];

        if token.name == "identifier"
        {
            let lookahead_token = &tokens[index + 1];

            // Method call
            if lookahead_token.value == "("
            {
                // compile the method and append it directly to the compiled plp code
                let method_code = compile_method_call(tokens, index, current_namespace, temp_register, load_registers, symbols);
                plp.code.push_str(&*method_code);
            }
            // Variable read
            else
            {
                let symbol = symbols.lookup_variable(current_namespace, &*token.value).unwrap();
                match symbol.location
                {
                    SymbolLocation::Register(name) => {
                            plp.mov(target_register, name);
                        },
                    SymbolLocation::Memory(address) => {
                            plp.li(load_registers.0, address.label_name);
                            plp.lw(target_register, address.offset, load_registers.0);
                        },
                    SymbolLocation::InstancedMemory(offset) => {
                            plp.lw(target_register, offset, target_register);
                        },
                    SymbolLocation::Structured => {
                            // TODO: append to namespace
                        },
                };
            }
        }
        else if token.value == "."
        {
            // Access references are handled when it's children are parsed (in the if block above)
            // so skip this token
            index += 1;
            continue;
        }
        else
        {
            break;
        }

        index += 1;
    }
    // first index AFTER the sequence
    index += 1;

    // TODO: compile

    (plp.code, index)
}

/// The range should start at the method identifier and end on the token AFTER the closing parenthesis
pub fn compile_method_call( tokens: &Vec<Token>,
                        start: usize,
                        current_namespace: &str,
                        arg_register: &str,
                        load_registers: (&str, &str),
                        symbols: &StaticSymbolTable)
                        -> String
{
    let mut plp = PLPWriter::new();

    // start at the token AFTER the open parenthesis
    let mut index = start + 2;

    // Index OF the closing parenthesis
    let end_index = identify_body_bounds(&tokens, index, ("(", ")")).unwrap();

    // TODO: Keep track of argument types, in order, to determine the method signature
    let mut argument_types: Vec<&str> = Vec::new();

    while index < end_index
    {
        let token = &tokens[index];
        if token.value == "("
        {
            panic!("Parenthesis surrounding expression currently unsupported");
        }
        else if token.value == ","
        {
            // Skip commas, arguments are separated by the stack divisors
            index += 1;
            continue;
        }
        else
        {
            // Load argument into arg_register
            let (code, new_index) = compile_arithmetic_statement(tokens, index, current_namespace, "$t9", load_registers, arg_register, symbols);
            plp.code.push_str(&*code);
            index = new_index;

            // Push argument to the stack
            plp.push(arg_register);

            // TODO: push argument_type to argument_types
        }
    }

    // Find nested method calls
    // Handle each argument one at a time, and push each to the stack

    // TODO: determine namespace from caller and current_namespace
    let namespace = "";

    let id_token = &tokens[start];
    let method_name = &*id_token.value;
    // TODO: determine if method is static
    // TODO: if function is non-static, push $this to stack

    let method_symbol = symbols.lookup_function(namespace, method_name, &argument_types).unwrap();
    match method_symbol.location
    {
        SymbolLocation::Register(name) => {
                panic!("Found method at a Register instead of a constant Memory address");
            },
        SymbolLocation::Memory(address) => {
                plp.call(address.label_name);
            },
        SymbolLocation::InstancedMemory(offset) => {
                panic!("Found method at InstancedMemory instead of a constant Memory address");
            },
        SymbolLocation::Structured => {
                // TODO: call constructor
                panic!("Constructors currently unsupported");
            },
    };

    return plp.code;
}

/// Future revision: @return (code, end_index, result_type)
pub fn compile_arithmetic_statement(tokens: &Vec<Token>,            // used
	                                start: usize,                   // used
	                                current_namespace: &str,        // indirect
	                                temp_register: &str,   			// used
	                                load_registers: (&str, &str),	// indirect
	                                target_register: &str,			// used
	                                symbols: &StaticSymbolTable)
	                                -> (String, usize)
{
    // TODO: handle parenthesis and order of operations
    let mut plp = PLPWriter::new();

    // Evaluate first symbol and store it in target_register, then push the result to the stack
    let mut index = compile_evaluation(tokens, start, current_namespace, temp_register, load_registers, target_register, symbols, &mut plp);
	plp.push(target_register);

    // loop until arithmetic sequence ends
	let operator_token = &tokens[index];
    if operator_token.name.starts_with("operator")
    {
        // PRESUMPTION: The first operand is at the top of the stack

        // Evaluate the second operand and store the result in temp_register
        let (code, new_index) = compile_arithmetic_statement(tokens, index + 1, current_namespace, temp_register, load_registers, temp_register, symbols);
		index = new_index;
		plp.code.push_str(&*code);

		// Retreive the first operand from the stack and store it in target_register
		plp.pop(target_register);

        // Perform the operation on the first (target_register) and second operand (temp_register) and store the result in target_register
        let code = compile_arithmetic_operation(&operator_token, (target_register, temp_register), target_register);
		plp.code.push_str(&*code);
    }

    (plp.code, index)
}

/// Writes plp code to evaluate a value triggered by the start token
/// If the token is a literal, the literal value will be loaded into the target_register
/// If the token is an identifier, it will be evaluated based on what the symbol represents
/// * If the symbol represents a method, the method will be called and the result stored in target_register
/// * If the symbol represents a variable, or a chain of accessors, the sequence will be evaluated and the result stored in target_register
///
/// This method will compile plp code directly to a PLPWriter as specified
pub fn compile_evaluation(  tokens: &Vec<Token>,            // used
                        start: usize,                   // used
                        current_namespace: &str,        // indirect-------------
                        temp_register: &str,            // used
                        load_registers: (&str, &str),   // indirect-------------
                        target_register: &str,          // used
                        symbols: &StaticSymbolTable,    // indirect-------------
                        plp: &mut PLPWriter)            // used
                        -> usize
{
    let mut token = &tokens[start];
    let mut end_index = start;

    if token.name.starts_with("literal")
    {
        let value = &*token.value;
        plp.li(target_register, value);

        end_index += 1;
    }
    else if token.name == "identifier"
    {
        // handle identifier
        let (access_code, new_index) = compile_symbol_sequence(tokens, start, current_namespace, temp_register, load_registers, target_register, symbols);
        plp.code.push_str(&*access_code);

        end_index = new_index;
    }
    else if token.value == "("
    {
        // TODO: find end bounds and evaluate parenthetical expression
        panic!("Unexpected token: {}\t{}", token.name, token.value);
    }
    else
    {
        panic!("Unexpected token: {}\t{}", token.name, token.value);
    }

    end_index
}

/// Writes plp code to perform a binary operation on two registers, and store the result in a third register
/// The specified registers need not be unique, and may all be the same if desired
/// This method performs only a single operation, and does not check for register validity
/// The register arguments are assumed to be prefaced with '$'
pub fn compile_arithmetic_operation(operator: &Token, operand_registers: (&str, &str), result_register: &str) -> String
{
    let mut plp = PLPWriter::new();

    match &*operator.value
    {
        "+" => plp.addu(result_register, operand_registers.0, operand_registers.1),
        "-" => plp.subu(result_register, operand_registers.0, operand_registers.1),
        "*" => plp.mullo(result_register, operand_registers.0, operand_registers.1),
         _  => panic!("Unsupported operator: {}: {}", operator.name, operator.value)
    };

    return plp.code;
}
<|endoftext|>


<commit_before>use clap::Arg;
use clap::App;
use clap::{ArgMatches};
use autoconf;
use rusqlite::Connection;

pub fn cli_clap(number: &u32) -> ArgMatches{

    let application = App::new(autoconf::package())
                        .about("Parses an input file to do awesome things")
                        .version(autoconf::version_cli())
                        .author("Owen Synge <osynge@googlemail.com>")
                        .arg(Arg::with_name("verbose")
                                    .help("Increase log output.")
                                    .short("v")
                                    .multiple(true)
                                    .long("verbose"))
                        .arg(Arg::with_name("quiet")
                                    .help("Decrease log output.")
                                    .short("q")
                                    .multiple(true)
                                    .long("quiet"))
                        .arg(Arg::with_name("env")
                                    .short("e")
                                    .long("env")
                                    .value_name("ENVIROMENT_VARIABLE")
                                    .help("Which enviroment variables to process")
                                    .multiple(true)
                                    .takes_value(true))
                        .arg(Arg::with_name("dir-jobs")
                                    .long("dir-jobs")
                                    .value_name("DIR_JOB")
                                    .help("directory storing json jobs.")
                                    .multiple(true)
                                    .takes_value(true))
                        .arg(Arg::with_name("dir-sh")
                                    .long("dir-sh")
                                    .value_name("DIR_SHELL")
                                    .help("directory storing jobs shell scripts.")
                                    .multiple(true)
                                    .takes_value(true))
                        .arg(Arg::with_name("dir-py")
                                    .long("dir-py")
                                    .value_name("DIR_PYTHON")
                                    .help("directory storing jobs python scripts.")
                                    .multiple(true)
                                    .takes_value(true))
                        .arg(Arg::with_name("config")
                                    .short("c")
                                    .long("config")
                                    .value_name("FILE")
                                    .help("Sets a custom config file")
                                    .takes_value(true))
                        .arg(Arg::with_name("target")
                                    .short("t")
                                    .long("target")
                                    .value_name("TARGET")
                                    .help("Sets the provider to be selected.")
                                    .takes_value(true))
                        .arg(Arg::with_name("list-provides")
                                    .long("list-provides")
                                    .help("list-provides"))
                        .arg(Arg::with_name("session")
                                    .long("session")
                                    .help("session"));
    let matches = application.get_matches();
    return matches;
}
<commit_msg>reformated with rustfmt<commit_after>use clap::Arg;
use clap::App;
use clap::ArgMatches;
use autoconf;
use rusqlite::Connection;

pub fn cli_clap(number: &u32) -> ArgMatches {

    let application = App::new(autoconf::package())
        .about("Parses an input file to do awesome things")
        .version(autoconf::version_cli())
        .author("Owen Synge <osynge@googlemail.com>")
        .arg(
            Arg::with_name("verbose")
                .help("Increase log output.")
                .short("v")
                .multiple(true)
                .long("verbose"),
        )
        .arg(
            Arg::with_name("quiet")
                .help("Decrease log output.")
                .short("q")
                .multiple(true)
                .long("quiet"),
        )
        .arg(
            Arg::with_name("env")
                .short("e")
                .long("env")
                .value_name("ENVIROMENT_VARIABLE")
                .help("Which enviroment variables to process")
                .multiple(true)
                .takes_value(true),
        )
        .arg(
            Arg::with_name("dir-jobs")
                .long("dir-jobs")
                .value_name("DIR_JOB")
                .help("directory storing json jobs.")
                .multiple(true)
                .takes_value(true),
        )
        .arg(
            Arg::with_name("dir-sh")
                .long("dir-sh")
                .value_name("DIR_SHELL")
                .help("directory storing jobs shell scripts.")
                .multiple(true)
                .takes_value(true),
        )
        .arg(
            Arg::with_name("dir-py")
                .long("dir-py")
                .value_name("DIR_PYTHON")
                .help("directory storing jobs python scripts.")
                .multiple(true)
                .takes_value(true),
        )
        .arg(
            Arg::with_name("config")
                .short("c")
                .long("config")
                .value_name("FILE")
                .help("Sets a custom config file")
                .takes_value(true),
        )
        .arg(
            Arg::with_name("target")
                .short("t")
                .long("target")
                .value_name("TARGET")
                .help("Sets the provider to be selected.")
                .takes_value(true),
        )
        .arg(Arg::with_name("list-provides").long("list-provides").help(
            "list-provides",
        ))
        .arg(Arg::with_name("session").long("session").help("session"));
    let matches = application.get_matches();
    return matches;
}
<|endoftext|>


<commit_before>// Just a grab bug of stuff that you wouldn't want to actualy write

fn strange() -> bool { let _x = ret true; }

fn funny() {
    fn f(_x: ()) { }
    f(ret);
}

fn what() {
    fn the(x: @mutable bool) { ret while !*x { *x = true; }; }
    let i = @mutable false;
    let dont = bind the(i);
    dont();
    assert (*i);
}

fn zombiejesus() {
    do  {
        while (ret) {
            if (ret) {
                alt (ret) { _ { ret ? ret : ret } };
            } else if (ret) {
                ret;
            }
        }
    } while ret
}

fn notsure() {
    let _x;
    let _y = (_x = 0) == (_x = 0);
    let _z = (_x <- 0) < (_x = 0);
    let _a = (_x += 0) == (_x = 0);
    let _b = (_y <-> _z) == (_y <-> _z);
}

fn hammertime() -> int {
    // FIXME: Doesn't compile
    //let _x = log true == (ret 0);
    ret 0;
}

fn canttouchthis() -> uint {
    pure fn p() -> bool { true }
    let _a = (assert (true)) == (check (p()));
    let _c = (check (p())) == ();
    let _b = (log 0) == (ret 0u);
}

fn angrydome() {
    while true { if break { } }
    let i = 0;
    do  { i += 1; if i == 1 { alt cont { _ { } } } } while false
}

fn evil_lincoln() { let evil <- log "lincoln"; }

fn main() {
    strange();
    funny();
    what();
    zombiejesus();
    notsure();
    hammertime();
    canttouchthis();
    angrydome();
    evil_lincoln();
}
<commit_msg>Enable hammertime<commit_after>// Just a grab bug of stuff that you wouldn't want to actualy write

fn strange() -> bool { let _x = ret true; }

fn funny() {
    fn f(_x: ()) { }
    f(ret);
}

fn what() {
    fn the(x: @mutable bool) { ret while !*x { *x = true; }; }
    let i = @mutable false;
    let dont = bind the(i);
    dont();
    assert (*i);
}

fn zombiejesus() {
    do  {
        while (ret) {
            if (ret) {
                alt (ret) { _ { ret ? ret : ret } };
            } else if (ret) {
                ret;
            }
        }
    } while ret
}

fn notsure() {
    let _x;
    let _y = (_x = 0) == (_x = 0);
    let _z = (_x <- 0) < (_x = 0);
    let _a = (_x += 0) == (_x = 0);
    let _b = (_y <-> _z) == (_y <-> _z);
}

fn hammertime() -> int {
    let _x = log true == (ret 0);
}

fn canttouchthis() -> uint {
    pure fn p() -> bool { true }
    let _a = (assert (true)) == (check (p()));
    let _c = (check (p())) == ();
    let _b = (log 0) == (ret 0u);
}

fn angrydome() {
    while true { if break { } }
    let i = 0;
    do  { i += 1; if i == 1 { alt cont { _ { } } } } while false
}

fn evil_lincoln() { let evil <- log "lincoln"; }

fn main() {
    strange();
    funny();
    what();
    zombiejesus();
    notsure();
    hammertime();
    canttouchthis();
    angrydome();
    evil_lincoln();
}
<|endoftext|>


<commit_before>

use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::mpsc::{Sender, Receiver, TryRecvError};
use std::thread;
use std::time::Duration;

use serialport as sp;
use base64;

use serial_support::errors::*;
use serial_support::messages::*;

/// Convenience type for a listener
/// that accepts weak refs of Senders of Serial Reponses
/// This is how the manager will communicate
/// results back to the websockets
type SubscReceiver = Receiver<SubscriptionRequest>;

/// Struct for containing Port information
pub struct OpenPort {
  /// Handle that controls writes to port
  write_lock_sub_id: Option<String>,
  /// The opened serial port
  /// SerialPort is not Sized, so it makes hashmap mad
  /// and so we deal with these shennanigans
  port: RefCell<Box<sp::SerialPort>>,
}

/// Subscription
pub struct Subscription {
  /// Subscription
  subscriber: Sender<SerialResponse>,
  /// The ports it is subscribed to
  ports: Vec<String>,
  /// Subscription id
  sub_id: String,
}

/// Manages tracking and reading/writing from serial
/// ports
///
/// TODO efficiency can be improved through choice of better
/// data structures though looping through a short list
/// is probably faster than hashing...
struct SerialPortManager {
  /// Receiver for serial requests
  receiver: Receiver<SerialRequest>,
  /// Receiver for response subscription requests
  subsc_receiver: SubscReceiver,
  /// List of port names to subscribers
  subscriptions: Vec<Subscription>,
  /// Maintains list of ports
  open_ports: HashMap<String, OpenPort>,
}

impl SerialPortManager {
  /// Try and get the subscription for sub_id
  /// If unsuccessful, return a Err<ErrorKind::SubscriptionNotFound>
  fn get_subscription(&mut self, sub_id: & String) -> Result<&mut Subscription> {
    match self.subscriptions.iter_mut().find(|s| & s.sub_id == sub_id) {
      None => Err(ErrorKind::SubscriptionNotFound(sub_id.to_string()).into()),
      Some(sub) => Ok(sub)
    }
  }

  /// Try and get the serial port
  /// If unsuccessful, return a Err<ErrorKind::OpenPortNotFound>
  fn get_port(&mut self, port: &String) -> Result<&mut OpenPort> {
    match self.open_ports.get_mut(port) {
      None => Err(ErrorKind::OpenPortNotFound(port.to_string()).into()),
      Some(sp) => Ok(sp)
    }
  }

  /// Open the given port.
  /// If the port is already open, then just subscribe to it
  fn open_port(&mut self, sub_id: & String, port_name:& String) -> Result<SerialResponse>  {

    fn open_serial_port(port:& String) -> Result<Box<sp::SerialPort>>{
      // If not, open it
      let sp_settings = sp::SerialPortSettings {
        baud_rate: sp::BaudRate::Baud115200,
        data_bits: sp::DataBits::Eight,
        flow_control: sp::FlowControl::None,
        parity: sp::Parity::None,
        stop_bits: sp::StopBits::One,
        timeout: Duration::from_millis(1),
      };
      sp::open_with_settings(&port, &sp_settings).map_err(|err|ErrorKind::SerialportError(err).into())
    };

    let port_res = self.get_port(port_name)?;

    // let port = match port_res{
    //   Ok(p) => p,
    //   Err(_) => {
        let op = OpenPort{write_lock_sub_id: None, port:RefCell::new(port_res)};
        self.open_ports.insert(port_name.to_string(), op);
        // &mut op
    //   }
    // };

    let sub = self.get_subscription(sub_id)?;

    let mut sp = try!(open_serial_port(port_name));

    // let port = self.get_port(port_name).unwrap_or_else(|_|{
    //     let op = OpenPort{write_lock_sub_id: None, port:RefCell::new(sp)};
    //     self.open_ports.insert(port_name.to_string(), op);
    //     &mut op
    //   });

    match sub.ports.iter().position(|p| p == port_name) {
      None => {sub.ports.push(port_name.to_string())}
      _ => {debug!("Port '{}' already subscribed by sub '{}' ", port_name, sub_id)}
    };

    Ok(SerialResponse::Opened{
      port: port_name.to_string()
    })

    // if subscription.ports.contains(port){
    //   return
    // }

    // self.get_port(port)?.map_error()

    // self.get_subscription(sub_id)?.

    // match self.subscriptions.iter().position(|s| s.sub_id == sub_id){
    //   // We don't even know who to send an error to...
    //   None => warn!("No subscription found for sub_id {}",sub_id),
    //   Some(idx) => {
    //     let sub = &mut self.subscriptions[idx];
    //     let serial_port = match self.open_ports.get(&port){
    //       None => {
    //         let sp = sp::open_with_settings(&port, &sp_settings);
    //         self.open_ports.insert(port, sp);
    //         return
    //       }
    //       Some(sp) => Ok(sp.port.borrow_mut())
    //     };

    //   }
    // }

    // match self.open_ports.get(&port) {
    //   case Some(serial_port) => {

    //   },
    //   case None => {

    //   }
    // }

  }

  // fn add_sender(&self, port: String, sender: Sender<SerialResponse>) {}

  fn set_write_lock(&self, sub_id: String, port: String) {}

  fn release_write_lock(&self, sub_id: String, port: Option<String>) {}

  fn write_port(&mut self, sub_id: String, port: String, data: String, base64: bool) {
    // get port

    // check sub_id matches write_lock id

    // If it does, try and write

    // Else send a write lock error
  }

  /// Send a response to a subscription with the id sub_id
  fn send_response(&mut self, sub_id: String, response: SerialResponse) {
    match self
            .subscriptions
            .iter()
            .position(|s| s.sub_id == sub_id) {
      Some(idx) => {
        match self.subscriptions[idx].subscriber.send(response.clone()) {
          Ok(_) => debug!("Sucessfully sent {:?} to {}", response.clone(), sub_id),
          Err(_) => {
            debug!("Remote end for sub_id {} closed, removing", sub_id);
            self.subscriptions.remove(idx);
          }
        }
      }
      None => warn!("No subscription found for sub_id '{}'", sub_id),
    }
  }

  /// Broadcast a response to all subscriptions
  fn broadcast(&mut self, response: SerialResponse) {
    self
      .subscriptions
      .retain(|sub| {
        sub
          .subscriber
          .send(response.clone())
          .map(|_| true)
          .unwrap_or_else(|_| {
                            warn!("Connection closed.");
                            false
                          })
      });
  }

  fn close_port(&self, sub_id: String, port: Option<String>) {}

  /// Handles and dispatches SerialRequest sent by
  /// the channel
  fn handle_serial_request(&mut self, msg: SerialRequest) {
    // match msg {
    //   SerialRequest::Open { sub_id, port } => self.open_port(sub_id, port),
    //   SerialRequest::WriteLock { sub_id, port } => self.set_write_lock(sub_id, port),
    //   SerialRequest::ReleaseWriteLock { sub_id, port } => self.release_write_lock(sub_id, port),
    //   SerialRequest::Write {
    //     sub_id,
    //     port,
    //     data,
    //     base64,
    //   } => self.write_port(sub_id, port, data, base64.unwrap_or(false)),
    //   SerialRequest::Close { sub_id, port } => self.close_port(sub_id, port),
    // }
  }

  /// Fire up the port manager
  ///
  /// TODO Right now this is spinning a thread between reading
  /// subscription requests, reading serial port data
  /// etc. This will cause high cpu usage
  ///
  /// TODO Once this is working, refactor to avoid
  /// thread spinning?
  ///
  fn run(&mut self) {

    let mut shutdown = false;
    // Check about 30 times a second
    let sleep_dur = Duration::from_millis(33);
    let mut serial_buf: Vec<u8> = vec![0; 4096];

    while !shutdown {
      // Sleep for a little bit to avoid pegging cpu
      thread::sleep(sleep_dur);

      // Handle serial operation requests
      match self.receiver.try_recv() {
        Err(e) => {
          match e {
            TryRecvError::Empty => {
              // nothing to do
            }
            TryRecvError::Disconnected => {
              // Remote end hung up, time to shutdown
              info!("Shutting down SerialPortManager");
            }
          }
        }
        Ok(req) => self.handle_serial_request(req),
      }

      // Check for new data on each port
      for (port_name, open_port) in self.open_ports.iter() {
        // If we have data, send it back
        // Read needs to borrow &mut self, so wrap port in RC?
        let result = match open_port
                .port
                .borrow_mut()
                .read(serial_buf.as_mut_slice()) {
          Ok(bytes_read) => {
            // We got some data
            let bytes = &serial_buf[0..bytes_read];
            // Try and parse the bytes as utf-8
            match String::from_utf8(bytes.to_vec()) {
              // We need to send as binary
              Err(e) => Err(ErrorKind::Utf8Error(e)),
              Ok(s) => {
                Ok(SerialResponse::Read {
                     port: port_name.to_string(),
                     data: s,
                     base64: Some(false),
                   })
              }
            }
          }
          Err(_) => Err(ErrorKind::PortReadError(port_name.to_string()).into()),
        };

        let r = result.unwrap_or_else(|ek| to_serial_response_error(ek.into()));

        // Send report, retain only live connections
        self
          .subscriptions
          .retain(|c| {
            // If its not the port we are currently interested in, we keep it
            if !c.ports.contains(&port_name) {
              true
            } else {
              // Otherwise try and send on it, and if it fails
              // remove the subscription
              c.subscriber
                .send(r.clone())
                .map(|_| true)
                .unwrap_or_else(|_| {
                                  warn!("Connection was closed.");
                                  false
                                })
            }
          });
      }

      // Check for Subscribe requests
      match self.subsc_receiver.try_recv() {
        Err(e) => {
          match e {
            TryRecvError::Empty => {
              // nothing to do
            }
            TryRecvError::Disconnected => {
              // Remote end hung up, time to shutdown
              shutdown = true;
              info!("Shutting down SerialPortManager");
            }
          }
        }
        Ok(sub_req) => {
          self
            .subscriptions
            .push(Subscription {
                    ports: Vec::with_capacity(4),
                    subscriber: sub_req.subscriber,
                    sub_id: sub_req.sub_id,
                  });
        }
      }
    }
  }
}

// TODO How to spawn and fire it up
// TODO Document that closing subscription sender will result in
// portmanager shutdown.
// fn spawnManager() -> {}
<commit_msg>Still not compiling, but clearer idea of what I need to do. Split borrows<commit_after>

use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::mpsc::{Sender, Receiver, TryRecvError};
use std::thread;
use std::time::Duration;

use serialport as sp;
use base64;

use serial_support::errors::*;
use serial_support::messages::*;

/// Convenience type for a listener
/// that accepts weak refs of Senders of Serial Reponses
/// This is how the manager will communicate
/// results back to the websockets
type SubscReceiver = Receiver<SubscriptionRequest>;

/// Struct for containing Port information
pub struct OpenPort {
  /// Handle that controls writes to port
  write_lock_sub_id: Option<String>,
  /// The opened serial port
  /// SerialPort is not Sized, so it makes hashmap mad
  /// and so we deal with these shennanigans
  port: RefCell<Box<sp::SerialPort>>,
}

/// Subscription
pub struct Subscription {
  /// Subscription
  subscriber: Sender<SerialResponse>,
  /// The ports it is subscribed to
  ports: Vec<String>,
  /// Subscription id
  sub_id: String,
}

/// Manages tracking and reading/writing from serial
/// ports
///
/// TODO efficiency can be improved through choice of better
/// data structures though looping through a short list
/// is probably faster than hashing...
struct SerialPortManager {
  /// Receiver for serial requests
  receiver: Receiver<SerialRequest>,
  /// Receiver for response subscription requests
  subsc_receiver: SubscReceiver,
  /// List of port names to subscribers
  subscriptions: Vec<Subscription>,
  /// Maintains list of ports
  open_ports: HashMap<String, OpenPort>,
}

impl SerialPortManager {
  // /// Try and get the subscription for sub_id
  // /// If unsuccessful, return a Err<ErrorKind::SubscriptionNotFound>
  // fn get_subscription(&mut self, sub_id: & String) -> Result<&mut Subscription> {
  //   match self.subscriptions.iter_mut().find(|s| & s.sub_id == sub_id) {
  //     None => Err(ErrorKind::SubscriptionNotFound(sub_id.to_string()).into()),
  //     Some(sub) => Ok(sub)
  //   }
  // }

  // /// Try and get the serial port
  // /// If unsuccessful, return a Err<ErrorKind::OpenPortNotFound>
  // fn get_port(&mut self, port: &String) -> Result<&mut OpenPort> {
  //   match self.open_ports.get_mut(port) {
  //     None => Err(ErrorKind::OpenPortNotFound(port.to_string()).into()),
  //     Some(sp) => Ok(sp)
  //   }
  // }

  /// Open the given port.
  /// If the port is already open, then just subscribe to it
  fn open_port(&mut self, sub_id: & String, port_name:& String) -> Result<SerialResponse>  {

    fn open_serial_port(port:& String) -> Result<Box<sp::SerialPort>>{
      // If not, open it
      let sp_settings = sp::SerialPortSettings {
        baud_rate: sp::BaudRate::Baud115200,
        data_bits: sp::DataBits::Eight,
        flow_control: sp::FlowControl::None,
        parity: sp::Parity::None,
        stop_bits: sp::StopBits::One,
        timeout: Duration::from_millis(1),
      };
      sp::open_with_settings(&port, &sp_settings).map_err(|err|ErrorKind::SerialportError(err).into())
    };

    let sub = self.
      subscriptions.
      iter_mut().
      find(|s| & s.sub_id == sub_id).
      ok_or(ErrorKind::SubscriptionNotFound(sub_id.to_string()))?;

    let mut sp = try!(open_serial_port(port_name));

    let port = self.open_ports.get_mut(port_name).or_else(||{
        let op = OpenPort{write_lock_sub_id: None, port:RefCell::new(sp)};
        self.open_ports.insert(port_name.to_string(), op);
        Some(& mut op)
      });

    match sub.ports.iter().position(|p| p == port_name) {
      None => {sub.ports.push(port_name.to_string())}
      _ => {debug!("Port '{}' already subscribed by sub '{}' ", port_name, sub_id)}
    };

    Ok(SerialResponse::Opened{
      port: port_name.to_string()
    })

    // if subscription.ports.contains(port){
    //   return
    // }

    // self.get_port(port)?.map_error()

    // self.get_subscription(sub_id)?.

    // match self.subscriptions.iter().position(|s| s.sub_id == sub_id){
    //   // We don't even know who to send an error to...
    //   None => warn!("No subscription found for sub_id {}",sub_id),
    //   Some(idx) => {
    //     let sub = &mut self.subscriptions[idx];
    //     let serial_port = match self.open_ports.get(&port){
    //       None => {
    //         let sp = sp::open_with_settings(&port, &sp_settings);
    //         self.open_ports.insert(port, sp);
    //         return
    //       }
    //       Some(sp) => Ok(sp.port.borrow_mut())
    //     };

    //   }
    // }

    // match self.open_ports.get(&port) {
    //   case Some(serial_port) => {

    //   },
    //   case None => {

    //   }
    // }

  }

  // fn add_sender(&self, port: String, sender: Sender<SerialResponse>) {}

  fn set_write_lock(&self, sub_id: String, port: String) {}

  fn release_write_lock(&self, sub_id: String, port: Option<String>) {}

  fn write_port(&mut self, sub_id: String, port: String, data: String, base64: bool) {
    // get port

    // check sub_id matches write_lock id

    // If it does, try and write

    // Else send a write lock error
  }

  /// Send a response to a subscription with the id sub_id
  fn send_response(&mut self, sub_id: String, response: SerialResponse) {
    match self
            .subscriptions
            .iter()
            .position(|s| s.sub_id == sub_id) {
      Some(idx) => {
        match self.subscriptions[idx].subscriber.send(response.clone()) {
          Ok(_) => debug!("Sucessfully sent {:?} to {}", response.clone(), sub_id),
          Err(_) => {
            debug!("Remote end for sub_id {} closed, removing", sub_id);
            self.subscriptions.remove(idx);
          }
        }
      }
      None => warn!("No subscription found for sub_id '{}'", sub_id),
    }
  }

  /// Broadcast a response to all subscriptions
  fn broadcast(&mut self, response: SerialResponse) {
    self
      .subscriptions
      .retain(|sub| {
        sub
          .subscriber
          .send(response.clone())
          .map(|_| true)
          .unwrap_or_else(|_| {
                            warn!("Connection closed.");
                            false
                          })
      });
  }

  fn close_port(&self, sub_id: String, port: Option<String>) {}

  /// Handles and dispatches SerialRequest sent by
  /// the channel
  fn handle_serial_request(&mut self, msg: SerialRequest) {
    // match msg {
    //   SerialRequest::Open { sub_id, port } => self.open_port(sub_id, port),
    //   SerialRequest::WriteLock { sub_id, port } => self.set_write_lock(sub_id, port),
    //   SerialRequest::ReleaseWriteLock { sub_id, port } => self.release_write_lock(sub_id, port),
    //   SerialRequest::Write {
    //     sub_id,
    //     port,
    //     data,
    //     base64,
    //   } => self.write_port(sub_id, port, data, base64.unwrap_or(false)),
    //   SerialRequest::Close { sub_id, port } => self.close_port(sub_id, port),
    // }
  }

  /// Fire up the port manager
  ///
  /// TODO Right now this is spinning a thread between reading
  /// subscription requests, reading serial port data
  /// etc. This will cause high cpu usage
  ///
  /// TODO Once this is working, refactor to avoid
  /// thread spinning?
  ///
  fn run(&mut self) {

    let mut shutdown = false;
    // Check about 30 times a second
    let sleep_dur = Duration::from_millis(33);
    let mut serial_buf: Vec<u8> = vec![0; 4096];

    while !shutdown {
      // Sleep for a little bit to avoid pegging cpu
      thread::sleep(sleep_dur);

      // Handle serial operation requests
      match self.receiver.try_recv() {
        Err(e) => {
          match e {
            TryRecvError::Empty => {
              // nothing to do
            }
            TryRecvError::Disconnected => {
              // Remote end hung up, time to shutdown
              info!("Shutting down SerialPortManager");
            }
          }
        }
        Ok(req) => self.handle_serial_request(req),
      }

      // Check for new data on each port
      for (port_name, open_port) in self.open_ports.iter() {
        // If we have data, send it back
        // Read needs to borrow &mut self, so wrap port in RC?
        let result = match open_port
                .port
                .borrow_mut()
                .read(serial_buf.as_mut_slice()) {
          Ok(bytes_read) => {
            // We got some data
            let bytes = &serial_buf[0..bytes_read];
            // Try and parse the bytes as utf-8
            match String::from_utf8(bytes.to_vec()) {
              // We need to send as binary
              Err(e) => Err(ErrorKind::Utf8Error(e)),
              Ok(s) => {
                Ok(SerialResponse::Read {
                     port: port_name.to_string(),
                     data: s,
                     base64: Some(false),
                   })
              }
            }
          }
          Err(_) => Err(ErrorKind::PortReadError(port_name.to_string()).into()),
        };

        let r = result.unwrap_or_else(|ek| to_serial_response_error(ek.into()));

        // Send report, retain only live connections
        self
          .subscriptions
          .retain(|c| {
            // If its not the port we are currently interested in, we keep it
            if !c.ports.contains(&port_name) {
              true
            } else {
              // Otherwise try and send on it, and if it fails
              // remove the subscription
              c.subscriber
                .send(r.clone())
                .map(|_| true)
                .unwrap_or_else(|_| {
                                  warn!("Connection was closed.");
                                  false
                                })
            }
          });
      }

      // Check for Subscribe requests
      match self.subsc_receiver.try_recv() {
        Err(e) => {
          match e {
            TryRecvError::Empty => {
              // nothing to do
            }
            TryRecvError::Disconnected => {
              // Remote end hung up, time to shutdown
              shutdown = true;
              info!("Shutting down SerialPortManager");
            }
          }
        }
        Ok(sub_req) => {
          self
            .subscriptions
            .push(Subscription {
                    ports: Vec::with_capacity(4),
                    subscriber: sub_req.subscriber,
                    sub_id: sub_req.sub_id,
                  });
        }
      }
    }
  }
}

// TODO How to spawn and fire it up
// TODO Document that closing subscription sender will result in
// portmanager shutdown.
// fn spawnManager() -> {}
<|endoftext|>


<commit_before>// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Semver parsing and logic

#[allow(missing_doc)];

use std::char;
use std::cmp;
use std::io::{ReaderUtil};
use std::io;
use std::option::{Option, Some, None};
use std::to_str::ToStr;
use std::uint;

#[deriving(Eq)]
pub enum Identifier {
    Numeric(uint),
    AlphaNumeric(~str)
}

impl cmp::Ord for Identifier {
    #[inline]
    fn lt(&self, other: &Identifier) -> bool {
        match (self, other) {
            (&Numeric(a), &Numeric(b)) => a < b,
            (&Numeric(_), _) => true,
            (&AlphaNumeric(ref a), &AlphaNumeric(ref b)) => *a < *b,
            (&AlphaNumeric(_), _) => false
        }
    }
    #[inline]
    fn le(&self, other: &Identifier) -> bool {
        ! (other < self)
    }
    #[inline]
    fn gt(&self, other: &Identifier) -> bool {
        other < self
    }
    #[inline]
    fn ge(&self, other: &Identifier) -> bool {
        ! (self < other)
    }
}

impl ToStr for Identifier {
    #[inline]
    fn to_str(&self) -> ~str {
        match self {
            &Numeric(n) => n.to_str(),
            &AlphaNumeric(ref s) => s.to_str()
        }
    }
}

#[deriving(Eq)]
pub struct Version {
    major: uint,
    minor: uint,
    patch: uint,
    pre: ~[Identifier],
    build: ~[Identifier],
}

impl ToStr for Version {
    #[inline]
    fn to_str(&self) -> ~str {
        let s = fmt!("%u.%u.%u", self.major, self.minor, self.patch);
        let s = if self.pre.is_empty() {
            s
        } else {
            fmt!("%s-%s", s, self.pre.map(|i| i.to_str()).connect("."))
        };
        if self.build.is_empty() {
            s
        } else {
            fmt!("%s+%s", s, self.build.map(|i| i.to_str()).connect("."))
        }
    }
}

impl cmp::Ord for Version {
    #[inline]
    fn lt(&self, other: &Version) -> bool {

        self.major < other.major ||

            (self.major == other.major &&
             self.minor < other.minor) ||

            (self.major == other.major &&
             self.minor == other.minor &&
             self.patch < other.patch) ||

            (self.major == other.major &&
             self.minor == other.minor &&
             self.patch == other.patch &&
             // NB: semver spec says 0.0.0-pre < 0.0.0
             // but the version of ord defined for vec
             // says that [] < [pre], so we alter it
             // here.
             (match (self.pre.len(), other.pre.len()) {
                 (0, 0) => false,
                 (0, _) => false,
                 (_, 0) => true,
                 (_, _) => self.pre < other.pre
             }))
    }

    #[inline]
    fn le(&self, other: &Version) -> bool {
        ! (other < self)
    }
    #[inline]
    fn gt(&self, other: &Version) -> bool {
        other < self
    }
    #[inline]
    fn ge(&self, other: &Version) -> bool {
        ! (self < other)
    }
}

condition! {
    bad_parse: () -> ();
}

fn take_nonempty_prefix(rdr: @io::Reader,
                        ch: char,
                        pred: &fn(char) -> bool) -> (~str, char) {
    let mut buf = ~"";
    let mut ch = ch;
    while pred(ch) {
        buf.push_char(ch);
        ch = rdr.read_char();
    }
    if buf.is_empty() {
        bad_parse::cond.raise(())
    }
    debug!("extracted nonempty prefix: %s", buf);
    (buf, ch)
}

fn take_num(rdr: @io::Reader, ch: char) -> (uint, char) {
    let (s, ch) = take_nonempty_prefix(rdr, ch, char::is_digit);
    match uint::from_str(s) {
        None => { bad_parse::cond.raise(()); (0, ch) },
        Some(i) => (i, ch)
    }
}

fn take_ident(rdr: @io::Reader, ch: char) -> (Identifier, char) {
    let (s,ch) = take_nonempty_prefix(rdr, ch, char::is_alphanumeric);
    if s.iter().all(char::is_digit) {
        match uint::from_str(s) {
            None => { bad_parse::cond.raise(()); (Numeric(0), ch) },
            Some(i) => (Numeric(i), ch)
        }
    } else {
        (AlphaNumeric(s), ch)
    }
}

fn expect(ch: char, c: char) {
    if ch != c {
        bad_parse::cond.raise(())
    }
}

fn parse_reader(rdr: @io::Reader) -> Version {
    let (major, ch) = take_num(rdr, rdr.read_char());
    expect(ch, '.');
    let (minor, ch) = take_num(rdr, rdr.read_char());
    expect(ch, '.');
    let (patch, ch) = take_num(rdr, rdr.read_char());

    let mut pre = ~[];
    let mut build = ~[];

    let mut ch = ch;
    if ch == '-' {
        loop {
            let (id, c) = take_ident(rdr, rdr.read_char());
            pre.push(id);
            ch = c;
            if ch != '.' { break; }
        }
    }

    if ch == '+' {
        loop {
            let (id, c) = take_ident(rdr, rdr.read_char());
            build.push(id);
            ch = c;
            if ch != '.' { break; }
        }
    }

    Version {
        major: major,
        minor: minor,
        patch: patch,
        pre: pre,
        build: build,
    }
}

pub fn parse(s: &str) -> Option<Version> {
    if !s.is_ascii() {
        return None;
    }
    let s = s.trim();
    let mut bad = false;
    do bad_parse::cond.trap(|_| { debug!("bad"); bad = true }).in {
        do io::with_str_reader(s) |rdr| {
            let v = parse_reader(rdr);
            if bad || v.to_str() != s.to_owned() {
                None
            } else {
                Some(v)
            }
        }
    }
}

#[test]
fn test_parse() {
    assert_eq!(parse(""), None);
    assert_eq!(parse("  "), None);
    assert_eq!(parse("1"), None);
    assert_eq!(parse("1.2"), None);
    assert_eq!(parse("1.2"), None);
    assert_eq!(parse("1"), None);
    assert_eq!(parse("1.2"), None);
    assert_eq!(parse("1.2.3-"), None);
    assert_eq!(parse("a.b.c"), None);
    assert_eq!(parse("1.2.3 abc"), None);

    assert!(parse("1.2.3") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[],
    }));
    assert!(parse("  1.2.3  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[],
    }));
    assert!(parse("1.2.3-alpha1") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[]
    }));
    assert!(parse("  1.2.3-alpha1  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[]
    }));
    assert!(parse("1.2.3+build5") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("  1.2.3+build5  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("1.2.3-alpha1+build5") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("  1.2.3-alpha1+build5  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("1.2.3-1.alpha1.9+build5.7.3aedf  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[Numeric(1),AlphaNumeric(~"alpha1"),Numeric(9)],
        build: ~[AlphaNumeric(~"build5"),
                 Numeric(7),
                 AlphaNumeric(~"3aedf")]
    }));

}

#[test]
fn test_eq() {
    assert_eq!(parse("1.2.3"), parse("1.2.3"));
    assert_eq!(parse("1.2.3-alpha1"), parse("1.2.3-alpha1"));
}

#[test]
fn test_ne() {
    assert!(parse("0.0.0")       != parse("0.0.1"));
    assert!(parse("0.0.0")       != parse("0.1.0"));
    assert!(parse("0.0.0")       != parse("1.0.0"));
    assert!(parse("1.2.3-alpha") != parse("1.2.3-beta"));
}

#[test]
fn test_lt() {
    assert!(parse("0.0.0")        < parse("1.2.3-alpha2"));
    assert!(parse("1.0.0")        < parse("1.2.3-alpha2"));
    assert!(parse("1.2.0")        < parse("1.2.3-alpha2"));
    assert!(parse("1.2.3-alpha1") < parse("1.2.3"));
    assert!(parse("1.2.3-alpha1") < parse("1.2.3-alpha2"));
    assert!(!(parse("1.2.3-alpha2") < parse("1.2.3-alpha2")));
}

#[test]
fn test_le() {
    assert!(parse("0.0.0")        <= parse("1.2.3-alpha2"));
    assert!(parse("1.0.0")        <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.0")        <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.3-alpha1") <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.3-alpha2") <= parse("1.2.3-alpha2"));
}

#[test]
fn test_gt() {
    assert!(parse("1.2.3-alpha2") > parse("0.0.0"));
    assert!(parse("1.2.3-alpha2") > parse("1.0.0"));
    assert!(parse("1.2.3-alpha2") > parse("1.2.0"));
    assert!(parse("1.2.3-alpha2") > parse("1.2.3-alpha1"));
    assert!(parse("1.2.3")        > parse("1.2.3-alpha2"));
    assert!(!(parse("1.2.3-alpha2") > parse("1.2.3-alpha2")));
}

#[test]
fn test_ge() {
    assert!(parse("1.2.3-alpha2") >= parse("0.0.0"));
    assert!(parse("1.2.3-alpha2") >= parse("1.0.0"));
    assert!(parse("1.2.3-alpha2") >= parse("1.2.0"));
    assert!(parse("1.2.3-alpha2") >= parse("1.2.3-alpha1"));
    assert!(parse("1.2.3-alpha2") >= parse("1.2.3-alpha2"));
}

#[test]
fn test_spec_order() {

    let vs = ["1.0.0-alpha",
              "1.0.0-alpha.1",
              "1.0.0-alpha.beta",
              "1.0.0-beta",
              "1.0.0-beta.2",
              "1.0.0-beta.11",
              "1.0.0-rc.1",
              "1.0.0"];
    let mut i = 1;
    while i < vs.len() {
        let a = parse(vs[i-1]).get();
        let b = parse(vs[i]).get();
        assert!(a < b);
        i += 1;
    }
}
<commit_msg>Add more tests for build metadata.<commit_after>// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Semver parsing and logic

#[allow(missing_doc)];

use std::char;
use std::cmp;
use std::io::{ReaderUtil};
use std::io;
use std::option::{Option, Some, None};
use std::to_str::ToStr;
use std::uint;

#[deriving(Eq)]
pub enum Identifier {
    Numeric(uint),
    AlphaNumeric(~str)
}

impl cmp::Ord for Identifier {
    #[inline]
    fn lt(&self, other: &Identifier) -> bool {
        match (self, other) {
            (&Numeric(a), &Numeric(b)) => a < b,
            (&Numeric(_), _) => true,
            (&AlphaNumeric(ref a), &AlphaNumeric(ref b)) => *a < *b,
            (&AlphaNumeric(_), _) => false
        }
    }
    #[inline]
    fn le(&self, other: &Identifier) -> bool {
        ! (other < self)
    }
    #[inline]
    fn gt(&self, other: &Identifier) -> bool {
        other < self
    }
    #[inline]
    fn ge(&self, other: &Identifier) -> bool {
        ! (self < other)
    }
}

impl ToStr for Identifier {
    #[inline]
    fn to_str(&self) -> ~str {
        match self {
            &Numeric(n) => n.to_str(),
            &AlphaNumeric(ref s) => s.to_str()
        }
    }
}

#[deriving(Eq)]
pub struct Version {
    major: uint,
    minor: uint,
    patch: uint,
    pre: ~[Identifier],
    build: ~[Identifier],
}

impl ToStr for Version {
    #[inline]
    fn to_str(&self) -> ~str {
        let s = fmt!("%u.%u.%u", self.major, self.minor, self.patch);
        let s = if self.pre.is_empty() {
            s
        } else {
            fmt!("%s-%s", s, self.pre.map(|i| i.to_str()).connect("."))
        };
        if self.build.is_empty() {
            s
        } else {
            fmt!("%s+%s", s, self.build.map(|i| i.to_str()).connect("."))
        }
    }
}

impl cmp::Ord for Version {
    #[inline]
    fn lt(&self, other: &Version) -> bool {

        self.major < other.major ||

            (self.major == other.major &&
             self.minor < other.minor) ||

            (self.major == other.major &&
             self.minor == other.minor &&
             self.patch < other.patch) ||

            (self.major == other.major &&
             self.minor == other.minor &&
             self.patch == other.patch &&
             // NB: semver spec says 0.0.0-pre < 0.0.0
             // but the version of ord defined for vec
             // says that [] < [pre], so we alter it
             // here.
             (match (self.pre.len(), other.pre.len()) {
                 (0, 0) => false,
                 (0, _) => false,
                 (_, 0) => true,
                 (_, _) => self.pre < other.pre
             }))
    }

    #[inline]
    fn le(&self, other: &Version) -> bool {
        ! (other < self)
    }
    #[inline]
    fn gt(&self, other: &Version) -> bool {
        other < self
    }
    #[inline]
    fn ge(&self, other: &Version) -> bool {
        ! (self < other)
    }
}

condition! {
    bad_parse: () -> ();
}

fn take_nonempty_prefix(rdr: @io::Reader,
                        ch: char,
                        pred: &fn(char) -> bool) -> (~str, char) {
    let mut buf = ~"";
    let mut ch = ch;
    while pred(ch) {
        buf.push_char(ch);
        ch = rdr.read_char();
    }
    if buf.is_empty() {
        bad_parse::cond.raise(())
    }
    debug!("extracted nonempty prefix: %s", buf);
    (buf, ch)
}

fn take_num(rdr: @io::Reader, ch: char) -> (uint, char) {
    let (s, ch) = take_nonempty_prefix(rdr, ch, char::is_digit);
    match uint::from_str(s) {
        None => { bad_parse::cond.raise(()); (0, ch) },
        Some(i) => (i, ch)
    }
}

fn take_ident(rdr: @io::Reader, ch: char) -> (Identifier, char) {
    let (s,ch) = take_nonempty_prefix(rdr, ch, char::is_alphanumeric);
    if s.iter().all(char::is_digit) {
        match uint::from_str(s) {
            None => { bad_parse::cond.raise(()); (Numeric(0), ch) },
            Some(i) => (Numeric(i), ch)
        }
    } else {
        (AlphaNumeric(s), ch)
    }
}

fn expect(ch: char, c: char) {
    if ch != c {
        bad_parse::cond.raise(())
    }
}

fn parse_reader(rdr: @io::Reader) -> Version {
    let (major, ch) = take_num(rdr, rdr.read_char());
    expect(ch, '.');
    let (minor, ch) = take_num(rdr, rdr.read_char());
    expect(ch, '.');
    let (patch, ch) = take_num(rdr, rdr.read_char());

    let mut pre = ~[];
    let mut build = ~[];

    let mut ch = ch;
    if ch == '-' {
        loop {
            let (id, c) = take_ident(rdr, rdr.read_char());
            pre.push(id);
            ch = c;
            if ch != '.' { break; }
        }
    }

    if ch == '+' {
        loop {
            let (id, c) = take_ident(rdr, rdr.read_char());
            build.push(id);
            ch = c;
            if ch != '.' { break; }
        }
    }

    Version {
        major: major,
        minor: minor,
        patch: patch,
        pre: pre,
        build: build,
    }
}

pub fn parse(s: &str) -> Option<Version> {
    if !s.is_ascii() {
        return None;
    }
    let s = s.trim();
    let mut bad = false;
    do bad_parse::cond.trap(|_| { debug!("bad"); bad = true }).in {
        do io::with_str_reader(s) |rdr| {
            let v = parse_reader(rdr);
            if bad || v.to_str() != s.to_owned() {
                None
            } else {
                Some(v)
            }
        }
    }
}

#[test]
fn test_parse() {
    assert_eq!(parse(""), None);
    assert_eq!(parse("  "), None);
    assert_eq!(parse("1"), None);
    assert_eq!(parse("1.2"), None);
    assert_eq!(parse("1.2"), None);
    assert_eq!(parse("1"), None);
    assert_eq!(parse("1.2"), None);
    assert_eq!(parse("1.2.3-"), None);
    assert_eq!(parse("a.b.c"), None);
    assert_eq!(parse("1.2.3 abc"), None);

    assert!(parse("1.2.3") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[],
    }));
    assert!(parse("  1.2.3  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[],
    }));
    assert!(parse("1.2.3-alpha1") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[]
    }));
    assert!(parse("  1.2.3-alpha1  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[]
    }));
    assert!(parse("1.2.3+build5") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("  1.2.3+build5  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("1.2.3-alpha1+build5") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("  1.2.3-alpha1+build5  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[AlphaNumeric(~"alpha1")],
        build: ~[AlphaNumeric(~"build5")]
    }));
    assert!(parse("1.2.3-1.alpha1.9+build5.7.3aedf  ") == Some(Version {
        major: 1u,
        minor: 2u,
        patch: 3u,
        pre: ~[Numeric(1),AlphaNumeric(~"alpha1"),Numeric(9)],
        build: ~[AlphaNumeric(~"build5"),
                 Numeric(7),
                 AlphaNumeric(~"3aedf")]
    }));

}

#[test]
fn test_eq() {
    assert_eq!(parse("1.2.3"), parse("1.2.3"));
    assert_eq!(parse("1.2.3-alpha1"), parse("1.2.3-alpha1"));
    assert_eq!(parse("1.2.3+build.42"), parse("1.2.3+build.42"));
    assert_eq!(parse("1.2.3-alpha1+42"), parse("1.2.3-alpha1+42"));
}

#[test]
fn test_ne() {
    assert!(parse("0.0.0")       != parse("0.0.1"));
    assert!(parse("0.0.0")       != parse("0.1.0"));
    assert!(parse("0.0.0")       != parse("1.0.0"));
    assert!(parse("1.2.3-alpha") != parse("1.2.3-beta"));
    assert!(parse("1.2.3+23")    != parse("1.2.3+42"));
}

#[test]
fn test_lt() {
    assert!(parse("0.0.0")        < parse("1.2.3-alpha2"));
    assert!(parse("1.0.0")        < parse("1.2.3-alpha2"));
    assert!(parse("1.2.0")        < parse("1.2.3-alpha2"));
    assert!(parse("1.2.3-alpha1") < parse("1.2.3"));
    assert!(parse("1.2.3-alpha1") < parse("1.2.3-alpha2"));
    assert!(!(parse("1.2.3-alpha2") < parse("1.2.3-alpha2")));
    assert!(!(parse("1.2.3+23")     < parse("1.2.3+42")));
}

#[test]
fn test_le() {
    assert!(parse("0.0.0")        <= parse("1.2.3-alpha2"));
    assert!(parse("1.0.0")        <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.0")        <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.3-alpha1") <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.3-alpha2") <= parse("1.2.3-alpha2"));
    assert!(parse("1.2.3+23")     <= parse("1.2.3+42"));
}

#[test]
fn test_gt() {
    assert!(parse("1.2.3-alpha2") > parse("0.0.0"));
    assert!(parse("1.2.3-alpha2") > parse("1.0.0"));
    assert!(parse("1.2.3-alpha2") > parse("1.2.0"));
    assert!(parse("1.2.3-alpha2") > parse("1.2.3-alpha1"));
    assert!(parse("1.2.3")        > parse("1.2.3-alpha2"));
    assert!(!(parse("1.2.3-alpha2") > parse("1.2.3-alpha2")));
    assert!(!(parse("1.2.3+23")     > parse("1.2.3+42")));
}

#[test]
fn test_ge() {
    assert!(parse("1.2.3-alpha2") >= parse("0.0.0"));
    assert!(parse("1.2.3-alpha2") >= parse("1.0.0"));
    assert!(parse("1.2.3-alpha2") >= parse("1.2.0"));
    assert!(parse("1.2.3-alpha2") >= parse("1.2.3-alpha1"));
    assert!(parse("1.2.3-alpha2") >= parse("1.2.3-alpha2"));
    assert!(parse("1.2.3+23")     >= parse("1.2.3+42"));
}

#[test]
fn test_spec_order() {

    let vs = ["1.0.0-alpha",
              "1.0.0-alpha.1",
              "1.0.0-alpha.beta",
              "1.0.0-beta",
              "1.0.0-beta.2",
              "1.0.0-beta.11",
              "1.0.0-rc.1",
              "1.0.0"];
    let mut i = 1;
    while i < vs.len() {
        let a = parse(vs[i-1]).get();
        let b = parse(vs[i]).get();
        assert!(a < b);
        i += 1;
    }
}
<|endoftext|>