<commit_before>// Copyright 2015-2016 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

mod command;
mod directory;
mod file;
mod package;
mod service;
mod telemetry;

use czmq::{ZCert, ZFrame, ZMsg, ZSock, SocketType};
use error::Result;
use inapi::Host;
use self::command::CommandApi;
use self::directory::DirectoryApi;
use self::file::FileApi;
use self::package::PackageApi;
use self::service::ServiceApi;
use self::telemetry::TelemetryApi;
use std::cell::RefCell;
use std::rc::Rc;
use std::result::Result as StdResult;
use zdaemon::{Api, Error as DError, ZMsgExtended};

pub fn endpoint(api_port: u32, cert: &ZCert) -> Result<Api> {
    let mut api_sock = ZSock::new(SocketType::ROUTER);
    cert.apply(&mut api_sock);
    api_sock.set_zap_domain("agent.intecture");
    api_sock.set_curve_server(true);
    api_sock.set_linger(1000);
    api_sock.bind(&format!("tcp://*:{}", api_port))?;

    let mut api = Api::new(api_sock);

    let path: Option<String> = None;
    let host = Rc::new(RefCell::new(Host::local(path)?));

    let host_clone = host.clone();
    api.add("command::exec", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = CommandApi::exec(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    let directory_api = Rc::new(DirectoryApi::new(host.clone()));
    let directory_clone = directory_api.clone();
    api.add("directory::is_directory", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.is_directory(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::exists", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.exists(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::create", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.create(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::delete", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.delete(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::mv", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.mv(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::get_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.get_owner(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::set_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.set_owner(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::get_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.get_mode(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::set_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.set_mode(sock, &i); error_handler(sock, r, &i) });

    let file_api = Rc::new(FileApi::new(host.clone())?);
    let file_clone = file_api.clone();
    api.add("file::is_file", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.is_file(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::exists", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.exists(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::delete", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.delete(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::mv", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.mv(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::copy", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.copy(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::get_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.get_owner(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::set_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.set_owner(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::get_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.get_mode(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::set_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.set_mode(sock, &i); error_handler(sock, r, &i) });

    let host_clone = host.clone();
    api.add("package::default_provider", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = PackageApi::default_provider(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    let host_clone = host.clone();
    api.add("service::action", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = ServiceApi::action(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    let host_clone = host.clone();
    api.add("telemetry", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = TelemetryApi::get(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    Ok(api)
}

fn error_handler(sock: &mut ZSock, result: Result<()>, router_id: &[u8]) -> StdResult<(), DError> {
    match result {
        Ok(_) => Ok(()),
        Err(e) => {
            let derror: DError = e.into();
            let msg = ZMsg::new_err(&derror)?;
            msg.pushbytes(router_id)?;
            msg.send(sock)?;
            Err(derror)
        }
    }
}

#[cfg(test)]
mod tests {
    use czmq::{ZMsg, ZSock};
    use error::Error;
    use std::error::Error as StdError;
    use super::error_handler;

    #[test]
    fn test_error_handler() {
        let mut client = ZSock::new_push("inproc://server_test_error_handler").unwrap();
        let mut server = ZSock::new_pull("inproc://server_test_error_handler").unwrap();
        server.set_rcvtimeo(Some(500));

        let e = server.send_str("fail").unwrap_err();
        let e_desc = e.description().to_string();
        assert!(error_handler(&mut client, Err(Error::Czmq(e)), b"router_id").is_err());

        let msg = ZMsg::recv(&mut server).unwrap();
        assert_eq!(msg.popstr().unwrap().unwrap(), "router_id");
        assert_eq!(msg.popstr().unwrap().unwrap(), "Err");
        assert_eq!(msg.popstr().unwrap().unwrap(), e_desc);
    }
}
<commit_msg>Fix missing REQ blank frame in error handler<commit_after>// Copyright 2015-2016 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

mod command;
mod directory;
mod file;
mod package;
mod service;
mod telemetry;

use czmq::{ZCert, ZFrame, ZMsg, ZSock, SocketType};
use error::Result;
use inapi::Host;
use self::command::CommandApi;
use self::directory::DirectoryApi;
use self::file::FileApi;
use self::package::PackageApi;
use self::service::ServiceApi;
use self::telemetry::TelemetryApi;
use std::cell::RefCell;
use std::rc::Rc;
use std::result::Result as StdResult;
use zdaemon::{Api, Error as DError, ZMsgExtended};

pub fn endpoint(api_port: u32, cert: &ZCert) -> Result<Api> {
    let mut api_sock = ZSock::new(SocketType::ROUTER);
    cert.apply(&mut api_sock);
    api_sock.set_zap_domain("agent.intecture");
    api_sock.set_curve_server(true);
    api_sock.set_linger(1000);
    api_sock.bind(&format!("tcp://*:{}", api_port))?;

    let mut api = Api::new(api_sock);

    let path: Option<String> = None;
    let host = Rc::new(RefCell::new(Host::local(path)?));

    let host_clone = host.clone();
    api.add("command::exec", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = CommandApi::exec(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    let directory_api = Rc::new(DirectoryApi::new(host.clone()));
    let directory_clone = directory_api.clone();
    api.add("directory::is_directory", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.is_directory(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::exists", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.exists(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::create", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.create(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::delete", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.delete(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::mv", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.mv(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::get_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.get_owner(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::set_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.set_owner(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::get_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.get_mode(sock, &i); error_handler(sock, r, &i) });
    let directory_clone = directory_api.clone();
    api.add("directory::set_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = directory_clone.set_mode(sock, &i); error_handler(sock, r, &i) });

    let file_api = Rc::new(FileApi::new(host.clone())?);
    let file_clone = file_api.clone();
    api.add("file::is_file", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.is_file(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::exists", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.exists(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::delete", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.delete(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::mv", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.mv(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::copy", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.copy(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::get_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.get_owner(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::set_owner", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.set_owner(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::get_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.get_mode(sock, &i); error_handler(sock, r, &i) });
    let file_clone = file_api.clone();
    api.add("file::set_mode", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = file_clone.set_mode(sock, &i); error_handler(sock, r, &i) });

    let host_clone = host.clone();
    api.add("package::default_provider", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = PackageApi::default_provider(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    let host_clone = host.clone();
    api.add("service::action", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = ServiceApi::action(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    let host_clone = host.clone();
    api.add("telemetry", move |sock: &mut ZSock, _: ZFrame, id: Option<Vec<u8>>| { let i = id.unwrap(); let r = TelemetryApi::get(sock, &mut host_clone.borrow_mut(), &i); error_handler(sock, r, &i) });

    Ok(api)
}

fn error_handler(sock: &mut ZSock, result: Result<()>, router_id: &[u8]) -> StdResult<(), DError> {
    match result {
        Ok(_) => Ok(()),
        Err(e) => {
            let derror: DError = e.into();
            let msg = ZMsg::new_err(&derror)?;
            msg.pushstr("")?;
            msg.pushbytes(router_id)?;
            msg.send(sock)?;
            Err(derror)
        }
    }
}

#[cfg(test)]
mod tests {
    use czmq::{ZMsg, ZSock};
    use error::Error;
    use std::error::Error as StdError;
    use super::error_handler;

    #[test]
    fn test_error_handler() {
        let mut client = ZSock::new_push("inproc://server_test_error_handler").unwrap();
        let mut server = ZSock::new_pull("inproc://server_test_error_handler").unwrap();
        server.set_rcvtimeo(Some(500));

        let e = server.send_str("fail").unwrap_err();
        let e_desc = e.description().to_string();
        assert!(error_handler(&mut client, Err(Error::Czmq(e)), b"router_id").is_err());

        let msg = ZMsg::recv(&mut server).unwrap();
        assert_eq!(msg.popstr().unwrap().unwrap(), "router_id");
        assert_eq!(msg.popstr().unwrap().unwrap(), "");
        assert_eq!(msg.popstr().unwrap().unwrap(), "Err");
        assert_eq!(msg.popstr().unwrap().unwrap(), e_desc);
    }
}
<|endoftext|>


<commit_before>mod api;
mod icons;
mod routes;
mod types;

use super::other::ConfAndFlags;
use routes::{call_route, Routes};
use tokio::time::{delay_for, Duration};

pub async fn setup(config: ConfAndFlags) -> Result<(), String> {
  println!("setting up the api...");
  if config.get_wg_key().len() == 0 {
    return Err(String::from("No wargaming api key defined"));
  }

  check_api(&config).await?;

  println!("Running api...");
  api::search_for_clan_ids(&config).await?;
  icons::get().await?;
  run_schedule(&config).await;

  Ok(())
}

async fn check_api(config: &ConfAndFlags) -> Result<types::NicknameAndClan, String> {
  let res: types::NicknameAndClan =
    call_route(Routes::NicknameAndClan("516673968".into()), config).await?;

  Ok(res)
}

// RunSchedule runs GetClanData every few hours
async fn run_schedule(config: &ConfAndFlags) {
  let mut count: u8 = 0;
  loop {
    let timeout_4_hours = Duration::from_secs(60 * 60 * 4);
    delay_for(timeout_4_hours).await;

    count += 1;
    if count == 12 {
      count = 0;
      if let Err(e) = api::search_for_clan_ids(config).await {
        println!("ERROR: [search_for_clan_ids]: {}", e);
      }
    } else {
      if let Err(e) = api::get_clan_data(config, None).await {
        println!("ERROR: [get_clan_data]: {}", e);
      }
    }
    if let Err(e) = icons::get().await {
      println!("ERROR: [icons::get]: {}", e);
    }
  }
}
<commit_msg>Made some logging a bit more simpel<commit_after>mod api;
mod icons;
mod routes;
mod types;

use super::other::ConfAndFlags;
use routes::{call_route, Routes};
use tokio::time::{delay_for, Duration};

pub async fn setup(config: ConfAndFlags) -> Result<(), String> {
  println!("setting up the api...");
  if config.get_wg_key().len() == 0 {
    return Err(String::from("No wargaming api key defined"));
  }

  check_api(&config).await?;

  println!("Running api...");
  api::search_for_clan_ids(&config).await?;
  icons::get().await?;
  println!("Completed bootstrapping");
  run_schedule(&config).await;

  Ok(())
}

async fn check_api(config: &ConfAndFlags) -> Result<types::NicknameAndClan, String> {
  let res: types::NicknameAndClan =
    call_route(Routes::NicknameAndClan("516673968".into()), config).await?;

  Ok(res)
}

// RunSchedule runs GetClanData every few hours
async fn run_schedule(config: &ConfAndFlags) {
  let mut count: u8 = 0;
  loop {
    let timeout_4_hours = Duration::from_secs(60 * 60 * 4);
    delay_for(timeout_4_hours).await;

    count += 1;
    if count == 12 {
      count = 0;
      if let Err(e) = api::search_for_clan_ids(config).await {
        println!("ERROR: [search_for_clan_ids]: {}", e);
      }
    } else {
      if let Err(e) = api::get_clan_data(config, None).await {
        println!("ERROR: [get_clan_data]: {}", e);
      }
    }
    if let Err(e) = icons::get().await {
      println!("ERROR: [icons::get]: {}", e);
    }
  }
}
<|endoftext|>


<commit_before>/// mush graph backend and abstraction to swap out with custom backend

use uuid::Uuid;
use std::collections::{HashMap,HashSet,VecDeque};

/// unidirectional edge, use two edges for bidirectional/undirected graph
/*
struct GraphEdge {
    to: Uuid,
    maybe_weight: Option<f64>,
}
impl GraphEdge {
    // this should be private, this should be called by a graph method
    fn new (uuid: Uuid, weight: Option<f64>) -> GraphEdge {
        GraphEdge { to: uuid,
                    maybe_weight:weight }
    }
    pub fn get_directed (&self) -> &Uuid {
        &self.to
    }
    pub fn get_weight (&self) -> Option<f64> {
        self.maybe_weight
    }
}*/

#[derive(Debug)]
struct GraphNode {
    uuid: Uuid,
    edges: HashMap<Uuid,Option<f64>>, //to,weight
}
impl GraphNode {
    fn new (uuid: Uuid) -> GraphNode {
        GraphNode { uuid: uuid,
                    edges: HashMap::new() }
    }
    /// direct the node towards another node
    // todo: rename me! sounds too similar to unidirectional
    fn direct (&mut self, to:&Uuid, weight: Option<f64>) -> bool {
        self.edges.insert(*to,weight).is_some()
    }
    fn undirect (&mut self, to:&Uuid) -> bool {
        self.edges.remove(to).is_some()
    }
}

//----

pub struct Graph {
    nodes: HashMap<Uuid,GraphNode>,

    // todo: as traits
    is_weighted: bool,
    is_directed: bool,
}
impl Graph {
    pub fn default() -> Graph {
        Graph { nodes: HashMap::new(),
                is_weighted: false,
                is_directed: true, }
    }

    /// manual accessors
    fn get_mut(&mut self, uuid: &Uuid) -> Option<&mut GraphNode> {
        self.nodes.get_mut(uuid)
    }
    fn get(&mut self, uuid: &Uuid) -> Option<&GraphNode> {
        self.nodes.get(uuid)
    }

    pub fn add (&mut self) -> Uuid { //todo: maybe_edges fn arg
        let uuid = Uuid::new_v4();
        let n = GraphNode::new(uuid);
        self.nodes.insert(uuid,n);
        uuid
    }
    pub fn remove(&mut self, node: &Uuid) { //-> Option<GraphNode> {
        self.nodes.remove(node);
    }

    pub fn direct(&mut self, from: &Uuid, to: &Uuid) -> bool {
        let mut r = false;
        if let Some(f) = self.nodes.get_mut(from) {
            r = f.direct(to,None);
        }

        if self.is_directed { return r }
        else if r {
            if let Some(t) = self.nodes.get_mut(to) {
                r = t.direct(from,None);
            }
        }

        return r
    }
    pub fn undirect(&mut self, from: &Uuid, to: &Uuid) -> bool {
        let mut r = false;
        if let Some(f) = self.nodes.get_mut(from) {
            r = f.undirect(to);
        }

        if self.is_directed { return r }
        else if r {
            if let Some(t) = self.nodes.get_mut(to) {
                r = t.undirect(from);
            }
        }

        return r
    }

    // search functions
    // todo: consider weights between nodes to direct search, cycle-detection
    pub fn get_path(&mut self, s: GraphSearch) -> Option<Vec<Uuid>> {
        let mut visited: HashSet<Uuid> = HashSet::new();
        let mut result = vec!();

        match s {
            GraphSearch::Depth(from,to) => {
                let mut stack = vec!();

                stack.push(from);
                visited.insert(from);
                result.push(from);

                let mut cursor = Some(from);

                while cursor.is_some() {
                    if let Some(ref node) = self.get(&cursor.unwrap()) {
                        //get first unvisited node
                        let not_visited = node.edges.iter().find(|&(&n,v)| !visited.contains(&n));

                        if let Some((&n,w)) = not_visited {
                            stack.push(n);
                            visited.insert(n);
                            result.push(n);

                            if let Some(to_node) = to {
                                if n == to_node { break; }
                            }

                            cursor = Some(n);
                        }
                        else { cursor = stack.pop(); }
                    }
                    else { cursor = stack.pop(); }
                }

                if let Some(to_node) = to {
                    if result.contains(&to_node) {
                        return Some(result)
                    }
                }

                return None
            },
            GraphSearch::Breadth(from,to) => { // breadth first search, uses a queue
                let mut queue = VecDeque::new();

                queue.push_back(from);
                visited.insert(from);
                result.push(from);

                let mut cursor = Some(from);

                while cursor.is_some() {
                    if let Some(ref node) = self.get(&cursor.unwrap()) {
                        //get unvisted nodes to queue up
                        let not_visited: Vec<Option<(Uuid,Option<f64>)>> = node.edges.iter().map(|(&n,&v)| {
                            if !visited.contains(&n) {
                                Some((n,v))
                            }
                            else { None }
                        }).collect();

                        for maybe_node in not_visited {
                            if let Some((n,w)) = maybe_node {
                                queue.push_back(n);
                                visited.insert(n);
                                result.push(n);

                                if let Some(to_node) = to {
                                    if n == to_node { break; }
                                }

                                cursor = Some(n);
                            }
                        }

                        cursor = queue.pop_front();
                    }
                    else { cursor = queue.pop_front(); }
                }

                if let Some(to_node) = to {
                    if result.contains(&to_node) {
                        return Some(result)
                    }
                }

                return None
            },
            _ => None, // todo: djk algo
        }

    }

    pub fn get_cycle(&mut self, s: GraphSearch) -> Option<Vec<Uuid>> {
        match s {
            GraphSearch::Depth(from,to) => {
                //const NEW: u8 = 0;
                const SEARCH: u8 = 1; // currently searching in this node
                const VISIT: u8 = 2; // has visited this node

                let mut status: HashMap<Uuid,u8> = HashMap::new();
                let mut result = vec!();
                let mut stack = vec!();

                stack.push(from);
                //status.insert(from,SEARCH);
                result.push(from);

                let mut cursor = Some(from);

                while cursor.is_some() {
                    status.insert(cursor.unwrap(),SEARCH);
                    println!("{:?}",status);
                    if let Some(ref node) = self.get(&cursor.unwrap()) {
                        //get first unvisited node
                        let not_visited = node.edges.iter().find(|&(&n,v)| !status.contains_key(&n));

                        let has_searching = node.edges.iter().find(|&(&n,v)| {
                            if let Some(_v) = status.get(&n) {
                                if *_v == SEARCH { true } //cycles found
                                else { false }
                            }
                            else { false }
                        });

                        if let Some((&n,w)) = has_searching {
                            result.push(n);
                            return Some(result);
                        }

                        if let Some((&n,w)) = not_visited {
                            stack.push(n);
                            status.insert(n,VISIT);
                            result.push(n);

                            if let Some(to_node) = to {
                                if n == to_node { break; }
                            }

                            cursor = Some(n);
                        }
                        else { cursor = stack.pop(); }
                    }
                    else { cursor = stack.pop(); }
                }

                return None
            },
            _ => None,
        }
    }

    /// get immediate next node from list of connected nodes for the current node
    pub fn get_next(&self, from: &Uuid) -> Option<&Uuid> {
        if let Some(n) = self.nodes.get(from) {
            if let Some(next_id) = n.edges.iter().next() {
                return Some(next_id.0) // grab uuid key
            }
        }

        return None

    }

    fn is_connected() -> bool { false }
    fn is_complete() -> bool { false }

    fn get_path_shortest(&self) -> bool {
        if !self.is_weighted { false } //must be weighted
        else { false } //todo: use bfs
    }
}

// todo: impl as trait instead?
pub enum GraphSearch {
    Depth(Uuid,Option<Uuid>), // used on part of graph for reachability, and all of graph for cycle-detection
    Breadth(Uuid,Option<Uuid>), // used on part of graph for reachability, and (unweighted) for shortest path
    Dijkstra(Uuid,Uuid), // used on part of graph (weighted) for shortest path
}

pub struct GraphBuilder(Graph);

impl GraphBuilder {
    pub fn new() -> GraphBuilder {
        GraphBuilder(Graph::default())
    }

    pub fn directed(mut self, d: bool) -> GraphBuilder {
        self.0.is_directed = d;
        self
    }
    pub fn weighted(mut self, w: bool) -> GraphBuilder {
        self.0.is_weighted = w;
        self
    }
    pub fn build(mut self) -> Graph {
        self.0
    }
}

// ----
// tests
// ----
#[cfg(test)]
mod tests {
    extern crate test;

    use ::{Graph,GraphSearch};

    #[test]
    fn test_basic() {
        let mut graph = Graph::default();
        let mut nodes = vec!();
        for i in 0..5 {
            nodes.push(graph.add());
        }

        graph.get_mut(&nodes[0]).unwrap().direct(&nodes[1],None); //note there is no verification that node[1] exists when doing this manually

        graph.direct(&nodes[3],&nodes[0]);
        graph.direct(&nodes[4],&nodes[3]);
        let n2 = graph.remove(&nodes[2]);
        assert!(!graph.direct(&nodes[2],&nodes[3]));

        let r = graph.get_path(GraphSearch::Depth(nodes[0],Some(nodes[4])));
        assert!(!r.is_some());

        let r = graph.get_path(GraphSearch::Depth(nodes[4],Some(nodes[0])));
        assert_eq!(r.unwrap().len(), 3);

        let r = graph.get_path(GraphSearch::Breadth(nodes[4],Some(nodes[0])));
        assert!(r.is_some());

        let r = graph.get_cycle(GraphSearch::Depth(nodes[4],Some(nodes[0])));
        assert!(!r.is_some());

        //find cycles
        let r = graph.get_cycle(GraphSearch::Depth(nodes[4],Some(nodes[0])));
        assert!(!r.is_some());
        let n2 = graph.add();
        graph.direct(&n2,&nodes[3]);
        graph.direct(&nodes[0],&n2);
        let r = graph.get_cycle(GraphSearch::Depth(nodes[4],None));
        assert!(r.is_some());

    }

}
<commit_msg>remove debug print statement<commit_after>/// mush graph backend and abstraction to swap out with custom backend

use uuid::Uuid;
use std::collections::{HashMap,HashSet,VecDeque};

/// unidirectional edge, use two edges for bidirectional/undirected graph
/*
struct GraphEdge {
    to: Uuid,
    maybe_weight: Option<f64>,
}
impl GraphEdge {
    // this should be private, this should be called by a graph method
    fn new (uuid: Uuid, weight: Option<f64>) -> GraphEdge {
        GraphEdge { to: uuid,
                    maybe_weight:weight }
    }
    pub fn get_directed (&self) -> &Uuid {
        &self.to
    }
    pub fn get_weight (&self) -> Option<f64> {
        self.maybe_weight
    }
}*/

#[derive(Debug)]
struct GraphNode {
    uuid: Uuid,
    edges: HashMap<Uuid,Option<f64>>, //to,weight
}
impl GraphNode {
    fn new (uuid: Uuid) -> GraphNode {
        GraphNode { uuid: uuid,
                    edges: HashMap::new() }
    }
    /// direct the node towards another node
    // todo: rename me! sounds too similar to unidirectional
    fn direct (&mut self, to:&Uuid, weight: Option<f64>) -> bool {
        self.edges.insert(*to,weight).is_some()
    }
    fn undirect (&mut self, to:&Uuid) -> bool {
        self.edges.remove(to).is_some()
    }
}

//----

pub struct Graph {
    nodes: HashMap<Uuid,GraphNode>,

    // todo: as traits
    is_weighted: bool,
    is_directed: bool,
}
impl Graph {
    pub fn default() -> Graph {
        Graph { nodes: HashMap::new(),
                is_weighted: false,
                is_directed: true, }
    }

    /// manual accessors
    fn get_mut(&mut self, uuid: &Uuid) -> Option<&mut GraphNode> {
        self.nodes.get_mut(uuid)
    }
    fn get(&mut self, uuid: &Uuid) -> Option<&GraphNode> {
        self.nodes.get(uuid)
    }

    pub fn add (&mut self) -> Uuid { //todo: maybe_edges fn arg
        let uuid = Uuid::new_v4();
        let n = GraphNode::new(uuid);
        self.nodes.insert(uuid,n);
        uuid
    }
    pub fn remove(&mut self, node: &Uuid) { //-> Option<GraphNode> {
        self.nodes.remove(node);
    }

    pub fn direct(&mut self, from: &Uuid, to: &Uuid) -> bool {
        let mut r = false;
        if let Some(f) = self.nodes.get_mut(from) {
            r = f.direct(to,None);
        }

        if self.is_directed { return r }
        else if r {
            if let Some(t) = self.nodes.get_mut(to) {
                r = t.direct(from,None);
            }
        }

        return r
    }
    pub fn undirect(&mut self, from: &Uuid, to: &Uuid) -> bool {
        let mut r = false;
        if let Some(f) = self.nodes.get_mut(from) {
            r = f.undirect(to);
        }

        if self.is_directed { return r }
        else if r {
            if let Some(t) = self.nodes.get_mut(to) {
                r = t.undirect(from);
            }
        }

        return r
    }

    // search functions
    // todo: consider weights between nodes to direct search, cycle-detection
    pub fn get_path(&mut self, s: GraphSearch) -> Option<Vec<Uuid>> {
        let mut visited: HashSet<Uuid> = HashSet::new();
        let mut result = vec!();

        match s {
            GraphSearch::Depth(from,to) => {
                let mut stack = vec!();

                stack.push(from);
                visited.insert(from);
                result.push(from);

                let mut cursor = Some(from);

                while cursor.is_some() {
                    if let Some(ref node) = self.get(&cursor.unwrap()) {
                        //get first unvisited node
                        let not_visited = node.edges.iter().find(|&(&n,v)| !visited.contains(&n));

                        if let Some((&n,w)) = not_visited {
                            stack.push(n);
                            visited.insert(n);
                            result.push(n);

                            if let Some(to_node) = to {
                                if n == to_node { break; }
                            }

                            cursor = Some(n);
                        }
                        else { cursor = stack.pop(); }
                    }
                    else { cursor = stack.pop(); }
                }

                if let Some(to_node) = to {
                    if result.contains(&to_node) {
                        return Some(result)
                    }
                }

                return None
            },
            GraphSearch::Breadth(from,to) => { // breadth first search, uses a queue
                let mut queue = VecDeque::new();

                queue.push_back(from);
                visited.insert(from);
                result.push(from);

                let mut cursor = Some(from);

                while cursor.is_some() {
                    if let Some(ref node) = self.get(&cursor.unwrap()) {
                        //get unvisted nodes to queue up
                        let not_visited: Vec<Option<(Uuid,Option<f64>)>> = node.edges.iter().map(|(&n,&v)| {
                            if !visited.contains(&n) {
                                Some((n,v))
                            }
                            else { None }
                        }).collect();

                        for maybe_node in not_visited {
                            if let Some((n,w)) = maybe_node {
                                queue.push_back(n);
                                visited.insert(n);
                                result.push(n);

                                if let Some(to_node) = to {
                                    if n == to_node { break; }
                                }

                                cursor = Some(n);
                            }
                        }

                        cursor = queue.pop_front();
                    }
                    else { cursor = queue.pop_front(); }
                }

                if let Some(to_node) = to {
                    if result.contains(&to_node) {
                        return Some(result)
                    }
                }

                return None
            },
            _ => None, // todo: djk algo
        }

    }

    pub fn get_cycle(&mut self, s: GraphSearch) -> Option<Vec<Uuid>> {
        match s {
            GraphSearch::Depth(from,to) => {
                //const NEW: u8 = 0;
                const SEARCH: u8 = 1; // currently searching in this node
                const VISIT: u8 = 2; // has visited this node

                let mut status: HashMap<Uuid,u8> = HashMap::new();
                let mut result = vec!();
                let mut stack = vec!();

                stack.push(from);
                //status.insert(from,SEARCH);
                result.push(from);

                let mut cursor = Some(from);

                while cursor.is_some() {
                    status.insert(cursor.unwrap(),SEARCH);

                    if let Some(ref node) = self.get(&cursor.unwrap()) {
                        //get first unvisited node
                        let not_visited = node.edges.iter().find(|&(&n,v)| !status.contains_key(&n));

                        let has_searching = node.edges.iter().find(|&(&n,v)| {
                            if let Some(_v) = status.get(&n) {
                                if *_v == SEARCH { true } //cycles found
                                else { false }
                            }
                            else { false }
                        });

                        if let Some((&n,w)) = has_searching {
                            result.push(n);
                            return Some(result);
                        }

                        if let Some((&n,w)) = not_visited {
                            stack.push(n);
                            status.insert(n,VISIT);
                            result.push(n);

                            if let Some(to_node) = to {
                                if n == to_node { break; }
                            }

                            cursor = Some(n);
                        }
                        else { cursor = stack.pop(); }
                    }
                    else { cursor = stack.pop(); }
                }

                return None
            },
            _ => None,
        }
    }

    /// get immediate next node from list of connected nodes for the current node
    pub fn get_next(&self, from: &Uuid) -> Option<&Uuid> {
        if let Some(n) = self.nodes.get(from) {
            if let Some(next_id) = n.edges.iter().next() {
                return Some(next_id.0) // grab uuid key
            }
        }

        return None

    }

    fn is_connected() -> bool { false }
    fn is_complete() -> bool { false }

    fn get_path_shortest(&self) -> bool {
        if !self.is_weighted { false } //must be weighted
        else { false } //todo: use bfs
    }
}

// todo: impl as trait instead?
pub enum GraphSearch {
    Depth(Uuid,Option<Uuid>), // used on part of graph for reachability, and all of graph for cycle-detection
    Breadth(Uuid,Option<Uuid>), // used on part of graph for reachability, and (unweighted) for shortest path
    Dijkstra(Uuid,Uuid), // used on part of graph (weighted) for shortest path
}

pub struct GraphBuilder(Graph);

impl GraphBuilder {
    pub fn new() -> GraphBuilder {
        GraphBuilder(Graph::default())
    }

    pub fn directed(mut self, d: bool) -> GraphBuilder {
        self.0.is_directed = d;
        self
    }
    pub fn weighted(mut self, w: bool) -> GraphBuilder {
        self.0.is_weighted = w;
        self
    }
    pub fn build(mut self) -> Graph {
        self.0
    }
}

// ----
// tests
// ----
#[cfg(test)]
mod tests {
    extern crate test;

    use ::{Graph,GraphSearch};

    #[test]
    fn test_basic() {
        let mut graph = Graph::default();
        let mut nodes = vec!();
        for i in 0..5 {
            nodes.push(graph.add());
        }

        graph.get_mut(&nodes[0]).unwrap().direct(&nodes[1],None); //note there is no verification that node[1] exists when doing this manually

        graph.direct(&nodes[3],&nodes[0]);
        graph.direct(&nodes[4],&nodes[3]);
        let n2 = graph.remove(&nodes[2]);
        assert!(!graph.direct(&nodes[2],&nodes[3]));

        let r = graph.get_path(GraphSearch::Depth(nodes[0],Some(nodes[4])));
        assert!(!r.is_some());

        let r = graph.get_path(GraphSearch::Depth(nodes[4],Some(nodes[0])));
        assert_eq!(r.unwrap().len(), 3);

        let r = graph.get_path(GraphSearch::Breadth(nodes[4],Some(nodes[0])));
        assert!(r.is_some());

        let r = graph.get_cycle(GraphSearch::Depth(nodes[4],Some(nodes[0])));
        assert!(!r.is_some());

        //find cycles
        let r = graph.get_cycle(GraphSearch::Depth(nodes[4],Some(nodes[0])));
        assert!(!r.is_some());
        let n2 = graph.add();
        graph.direct(&n2,&nodes[3]);
        graph.direct(&nodes[0],&n2);
        let r = graph.get_cycle(GraphSearch::Depth(nodes[4],None));
        assert!(r.is_some());

    }

}
<|endoftext|>


<commit_before>use std::f64;
use std::cmp::min;
use std::num::Float;
use std::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull};
use elem::Molecule;
use error::{CTResult, CTError};
use error::CTErrorKind::InputError;

macro_rules! impl_matrix_index {
    ($idx:ty, $out:ty) => {
        impl Index<$idx> for Matrix {
            type Output = $out;

            fn index(&self, index: &$idx) -> &$out {
                &self.buf[*index]
            }
        }
    }
}

macro_rules! impl_matrix_index_mut {
    ($idx:ty, $out:ty) => {
        impl IndexMut<$idx> for Matrix {
            fn index_mut(&mut self, index: &$idx) -> &mut $out {
                &mut self.buf[*index]
            }
        }
    }
}

/// Balances a chemical reaction using Gaussian elimination
pub fn balance_reaction(reaction: (Vec<Molecule>, Vec<Molecule>)) -> CTResult<Vec<u32>> {
    let reac_mat = Matrix::from_reaction(&reaction);
    let reduced_mat = try!(forward_elim(reac_mat));
    let coefs = back_substitute(&reduced_mat);

    // if any of the coefs are 0, then an element in that molecule is missing on the other side
    // of the reaction
    if let Some(pos) = coefs.iter().position(|&c| c == 0.0) {
        let (ref lhs, ref rhs) = reaction;
        let molecule = lhs.iter().chain(rhs.iter()).nth(pos).unwrap();
        let begin = molecule.first().unwrap().pos;
        let end = molecule.last().unwrap().pos + molecule.last().unwrap().len;
        return Err(CTError {
            kind: InputError,
            desc: format!("An element in {} is missing on the other side of the reaction", molecule),
            pos: Some((begin, end)),
        })
    }

    // we find the minimum element with fold, since f64 does not implement Ord...
    let min = coefs.iter().fold(f64::INFINITY, |crnt, &num| {
        if num < crnt {
            num
        } else {
            crnt
        }
    });
    // now we divide all elements by the minimum to (hopefully) convert them all to intergers
    // after which we do the actual conversion
    // we also reverse the Vec so the coefs are in the right order
    Ok(coefs.iter().rev().map(|n| (n / min) as u32).collect())
}

fn column_abs_max_index(columns: &[Vec<f64>], column: usize) -> usize {
    let mut max = 0;
    for i in 1..columns.len() {
        if columns[i][column].abs() > columns[max][column].abs() {
            max = i;
        }
    }
    max
}

fn forward_elim(mut mat: Matrix) -> CTResult<Matrix> {
    for k in 0..min(mat.width(), mat.height()) {
        // locate the pivot
        let pivot = column_abs_max_index(&mat[k..], k) + k;
        if mat[pivot][k] == 0_f64 {
            return Err(CTError {
                kind: InputError,
                desc: "Could not balance reaction".to_string(),
                pos: None,
            })
        }
        // move the pivot to its new position
        mat.switch_rows(k, pivot);

        // zero out the rest of the column
        for i in k + 1..mat.height() {
            let mult = -(mat[i][k] / mat[k][k]);
            mat.add_row_to_row(i, k, mult);
        }
    }
    Ok(mat)
}

fn back_substitute(mat: &Matrix) -> Vec<f64> {
    let mut vars = Vec::with_capacity(mat.width());
    for k in (0..mat.width()).rev() {
        if k >= mat.height() {
            // if this coef does not have a corresponding row in the matrix, then treat is as a
            // free variable and set it to 1
            vars.push(1_f64);
        } else {
            let mut var = 0f64;
            for i in k + 1..mat.width() {
                var -= mat[k][i] * vars[mat.width() - 1 - i];
            }
            vars.push(var / mat[k][k]);
        }
    }
    vars
}

#[derive(Debug, PartialEq)]
struct Matrix {
    buf: Vec<Vec<f64>>,
    height: usize,
    width: usize,
}

impl Matrix {
    fn from_reaction(reaction: &(Vec<Molecule>, Vec<Molecule>)) -> Matrix {
        let &(ref lhs, ref rhs) = reaction;
        let mut names = Vec::<&str>::new();
        for molecule in lhs.iter().chain(rhs.iter()) {
            for elem in molecule.iter() {
                if names.iter().find(|e| **e == elem.name).is_none() {
                    names.push(elem.name.as_slice());
                }
            }
        }
        let mut buf = Vec::with_capacity(names.len());
        for name in names.iter() {
            let mut row = Vec::with_capacity(lhs.len() + rhs.len());
            for molecule in lhs.iter() {
                row.push(molecule.iter()
                                 .find(|e| e.name == *name)
                                 .and_then(|e| Some(e.coef as f64))
                                 .unwrap_or(0_f64));
            }
            // we loop over rhs seperately, since we need to multiply the coefs with -1
            for molecule in rhs.iter() {
                row.push(molecule.iter()
                                 .find(|e| e.name == *name)
                                 .and_then(|e| Some(-1.0*(e.coef as f64)))
                                 .unwrap_or(0_f64));
            }
            buf.push(row);
        }
        let height = names.len();
        let width = lhs.len() + rhs.len();
        Matrix {
            buf: buf,
            height: height,
            width: width,
        }
    }

    fn add_row_to_row(&mut self, dest: usize, row: usize, mult: f64) {
        for i in 0..self.width {
            let incr = self.buf[row][i] * mult;
            self.buf[dest][i] += incr;
        }
    }

    fn switch_rows(&mut self, row1: usize, row2: usize)  {
        self.buf.swap(row1, row2);
    }

    fn width(&self) -> usize {
        self.width
    }

    fn height(&self) -> usize {
        self.height
    }
}

impl_matrix_index!(usize, Vec<f64>);
impl_matrix_index!(Range<usize>, [Vec<f64>]);
impl_matrix_index!(RangeTo<usize>, [Vec<f64>]);
impl_matrix_index!(RangeFrom<usize>, [Vec<f64>]);
impl_matrix_index!(RangeFull, [Vec<f64>]);

impl_matrix_index_mut!(usize, Vec<f64>);
impl_matrix_index_mut!(Range<usize>, [Vec<f64>]);
impl_matrix_index_mut!(RangeTo<usize>, [Vec<f64>]);
impl_matrix_index_mut!(RangeFrom<usize>, [Vec<f64>]);
impl_matrix_index_mut!(RangeFull, [Vec<f64>]);

mod test {
    use super::*;
    use elem::PerElem;
    use error::CTErrorKind::InputError;

    macro_rules! dummy_elem(
        ($name:expr) => (
            PerElem { name: $name.to_string(), coef: 1, pos: 0, len: 1 }
        );
        ($name:expr, $coef:expr) => (
            PerElem { name: $name.to_string(), coef: $coef, pos: 0, len: 1 }
        );
    );

    #[test]
    fn balance() {
        // attempt to balance C3H8 + O2 -> CO2 + H2O
        let reaction = (vec!(vec!(dummy_elem!("C", 3), dummy_elem!("H", 8)),
                             vec!(dummy_elem!("O", 2))),
                        vec!(vec!(dummy_elem!("C", 1), dummy_elem!("O", 2)),
                             vec!(dummy_elem!("H", 2), dummy_elem!("O", 1))));
        let result = balance_reaction(reaction);
        let expected = Ok(vec!(1, 5, 3, 4));
        assert_eq!(result, expected);
    }

    #[test]
    fn no_balance_needed() {
        let reaction = (vec!(vec!(dummy_elem!("C", 1)), vec!(dummy_elem!("H", 1))),
                        vec!(vec!(dummy_elem!("C", 1)), vec!(dummy_elem!("H", 1))));
        let result = balance_reaction(reaction);
        let expected = Ok(vec!(1, 1, 1, 1));
        assert_eq!(result, expected);
    }

    #[test]
    fn missing_elem() {
        let reaction = (vec!(vec!(dummy_elem!("C", 1)), vec!(dummy_elem!("H", 1))),
                        vec!(vec!(dummy_elem!("C", 1))));
        let result = balance_reaction(reaction);
        println!("{:?}", result);
        assert!(result.is_err());
        assert_eq!(result.err().unwrap().kind, InputError);
    }
}<commit_msg>Fix bug in balancer error reporting<commit_after>use std::f64;
use std::cmp::min;
use std::num::Float;
use std::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull};
use elem::Molecule;
use error::{CTResult, CTError};
use error::CTErrorKind::InputError;

macro_rules! impl_matrix_index {
    ($idx:ty, $out:ty) => {
        impl Index<$idx> for Matrix {
            type Output = $out;

            fn index(&self, index: &$idx) -> &$out {
                &self.buf[*index]
            }
        }
    }
}

macro_rules! impl_matrix_index_mut {
    ($idx:ty, $out:ty) => {
        impl IndexMut<$idx> for Matrix {
            fn index_mut(&mut self, index: &$idx) -> &mut $out {
                &mut self.buf[*index]
            }
        }
    }
}

/// Balances a chemical reaction using Gaussian elimination
pub fn balance_reaction(reaction: (Vec<Molecule>, Vec<Molecule>)) -> CTResult<Vec<u32>> {
    let reac_mat = Matrix::from_reaction(&reaction);
    let reduced_mat = try!(forward_elim(reac_mat));
    let coefs = back_substitute(&reduced_mat);

    // if any of the coefs are 0, then an element in that molecule is missing on the other side
    // of the reaction
    if let Some(pos) = coefs.iter().position(|&c| c == 0.0) {
        let (ref lhs, ref rhs) = reaction;
        let molecule = lhs.iter().chain(rhs.iter()).nth(pos).unwrap();
        let begin = molecule.first().unwrap().pos;
        let len = molecule.last().unwrap().pos + molecule.last().unwrap().len - begin;
        return Err(CTError {
            kind: InputError,
            desc: format!("An element in {} is missing on the other side of the reaction", molecule),
            pos: Some((begin, len)),
        })
    }

    // we find the minimum element with fold, since f64 does not implement Ord...
    let min = coefs.iter().fold(f64::INFINITY, |crnt, &num| {
        if num < crnt {
            num
        } else {
            crnt
        }
    });
    // now we divide all elements by the minimum to (hopefully) convert them all to intergers
    // after which we do the actual conversion
    // we also reverse the Vec so the coefs are in the right order
    Ok(coefs.iter().rev().map(|n| (n / min) as u32).collect())
}

fn column_abs_max_index(columns: &[Vec<f64>], column: usize) -> usize {
    let mut max = 0;
    for i in 1..columns.len() {
        if columns[i][column].abs() > columns[max][column].abs() {
            max = i;
        }
    }
    max
}

fn forward_elim(mut mat: Matrix) -> CTResult<Matrix> {
    for k in 0..min(mat.width(), mat.height()) {
        // locate the pivot
        let pivot = column_abs_max_index(&mat[k..], k) + k;
        if mat[pivot][k] == 0_f64 {
            return Err(CTError {
                kind: InputError,
                desc: "Could not balance reaction".to_string(),
                pos: None,
            })
        }
        // move the pivot to its new position
        mat.switch_rows(k, pivot);

        // zero out the rest of the column
        for i in k + 1..mat.height() {
            let mult = -(mat[i][k] / mat[k][k]);
            mat.add_row_to_row(i, k, mult);
        }
    }
    Ok(mat)
}

fn back_substitute(mat: &Matrix) -> Vec<f64> {
    let mut vars = Vec::with_capacity(mat.width());
    for k in (0..mat.width()).rev() {
        if k >= mat.height() {
            // if this coef does not have a corresponding row in the matrix, then treat is as a
            // free variable and set it to 1
            vars.push(1_f64);
        } else {
            let mut var = 0f64;
            for i in k + 1..mat.width() {
                var -= mat[k][i] * vars[mat.width() - 1 - i];
            }
            vars.push(var / mat[k][k]);
        }
    }
    vars
}

#[derive(Debug, PartialEq)]
struct Matrix {
    buf: Vec<Vec<f64>>,
    height: usize,
    width: usize,
}

impl Matrix {
    fn from_reaction(reaction: &(Vec<Molecule>, Vec<Molecule>)) -> Matrix {
        let &(ref lhs, ref rhs) = reaction;
        let mut names = Vec::<&str>::new();
        for molecule in lhs.iter().chain(rhs.iter()) {
            for elem in molecule.iter() {
                if names.iter().find(|e| **e == elem.name).is_none() {
                    names.push(elem.name.as_slice());
                }
            }
        }
        let mut buf = Vec::with_capacity(names.len());
        for name in names.iter() {
            let mut row = Vec::with_capacity(lhs.len() + rhs.len());
            for molecule in lhs.iter() {
                row.push(molecule.iter()
                                 .find(|e| e.name == *name)
                                 .and_then(|e| Some(e.coef as f64))
                                 .unwrap_or(0_f64));
            }
            // we loop over rhs seperately, since we need to multiply the coefs with -1
            for molecule in rhs.iter() {
                row.push(molecule.iter()
                                 .find(|e| e.name == *name)
                                 .and_then(|e| Some(-1.0*(e.coef as f64)))
                                 .unwrap_or(0_f64));
            }
            buf.push(row);
        }
        let height = names.len();
        let width = lhs.len() + rhs.len();
        Matrix {
            buf: buf,
            height: height,
            width: width,
        }
    }

    fn add_row_to_row(&mut self, dest: usize, row: usize, mult: f64) {
        for i in 0..self.width {
            let incr = self.buf[row][i] * mult;
            self.buf[dest][i] += incr;
        }
    }

    fn switch_rows(&mut self, row1: usize, row2: usize)  {
        self.buf.swap(row1, row2);
    }

    fn width(&self) -> usize {
        self.width
    }

    fn height(&self) -> usize {
        self.height
    }
}

impl_matrix_index!(usize, Vec<f64>);
impl_matrix_index!(Range<usize>, [Vec<f64>]);
impl_matrix_index!(RangeTo<usize>, [Vec<f64>]);
impl_matrix_index!(RangeFrom<usize>, [Vec<f64>]);
impl_matrix_index!(RangeFull, [Vec<f64>]);

impl_matrix_index_mut!(usize, Vec<f64>);
impl_matrix_index_mut!(Range<usize>, [Vec<f64>]);
impl_matrix_index_mut!(RangeTo<usize>, [Vec<f64>]);
impl_matrix_index_mut!(RangeFrom<usize>, [Vec<f64>]);
impl_matrix_index_mut!(RangeFull, [Vec<f64>]);

mod test {
    use super::*;
    use elem::PerElem;
    use error::CTErrorKind::InputError;

    macro_rules! dummy_elem(
        ($name:expr) => (
            PerElem { name: $name.to_string(), coef: 1, pos: 0, len: 1 }
        );
        ($name:expr, $coef:expr) => (
            PerElem { name: $name.to_string(), coef: $coef, pos: 0, len: 1 }
        );
    );

    #[test]
    fn balance() {
        // attempt to balance C3H8 + O2 -> CO2 + H2O
        let reaction = (vec!(vec!(dummy_elem!("C", 3), dummy_elem!("H", 8)),
                             vec!(dummy_elem!("O", 2))),
                        vec!(vec!(dummy_elem!("C", 1), dummy_elem!("O", 2)),
                             vec!(dummy_elem!("H", 2), dummy_elem!("O", 1))));
        let result = balance_reaction(reaction);
        let expected = Ok(vec!(1, 5, 3, 4));
        assert_eq!(result, expected);
    }

    #[test]
    fn no_balance_needed() {
        let reaction = (vec!(vec!(dummy_elem!("C", 1)), vec!(dummy_elem!("H", 1))),
                        vec!(vec!(dummy_elem!("C", 1)), vec!(dummy_elem!("H", 1))));
        let result = balance_reaction(reaction);
        let expected = Ok(vec!(1, 1, 1, 1));
        assert_eq!(result, expected);
    }

    #[test]
    fn missing_elem() {
        let reaction = (vec!(vec!(dummy_elem!("C", 1)), vec!(dummy_elem!("H", 1))),
                        vec!(vec!(dummy_elem!("C", 1))));
        let result = balance_reaction(reaction);
        println!("{:?}", result);
        assert!(result.is_err());
        assert_eq!(result.err().unwrap().kind, InputError);
    }
}<|endoftext|>


<commit_before>use crate::common::util::*;
use rust_users::*;
use std::os::unix::fs::PermissionsExt;

#[test]
fn test_install_help() {
    let (_, mut ucmd) = at_and_ucmd!();

    assert!(ucmd
        .arg("--help")
        .succeeds()
        .no_stderr()
        .stdout
        .contains("FLAGS:"));
}

#[test]
fn test_install_basic() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_install_target_dir_dir_a";
    let file1 = "test_install_target_dir_file_a1";
    let file2 = "test_install_target_dir_file_a2";

    at.touch(file1);
    at.touch(file2);
    at.mkdir(dir);
    ucmd.arg(file1).arg(file2).arg(dir).succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));
    assert!(at.file_exists(&format!("{}/{}", dir, file1)));
    assert!(at.file_exists(&format!("{}/{}", dir, file2)));
}

#[test]
fn test_install_twice_dir() {
    let dir = "test_install_target_dir_dir_a";
    let scene = TestScenario::new(util_name!());

    scene.ucmd().arg("-d").arg(dir).succeeds();
    scene.ucmd().arg("-d").arg(dir).succeeds();
    let at = &scene.fixtures;

    assert!(at.dir_exists(dir));
}

#[test]
fn test_install_failing_not_dir() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "test_install_target_dir_file_a1";
    let file2 = "test_install_target_dir_file_a2";
    let file3 = "test_install_target_dir_file_a3";

    at.touch(file1);
    at.touch(file2);
    at.touch(file3);
    assert!(ucmd
        .arg(file1)
        .arg(file2)
        .arg(file3)
        .fails()
        .stderr
        .contains("not a directory"));
}

#[test]
fn test_install_unimplemented_arg() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_install_target_dir_dir_b";
    let file = "test_install_target_dir_file_b";
    let context_arg = "--context";

    at.touch(file);
    at.mkdir(dir);
    assert!(ucmd
        .arg(context_arg)
        .arg(file)
        .arg(dir)
        .fails()
        .stderr
        .contains("Unimplemented"));

    assert!(!at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_component_directories() {
    let (at, mut ucmd) = at_and_ucmd!();
    let component1 = "test_install_target_dir_component_c1";
    let component2 = "test_install_target_dir_component_c2";
    let component3 = "test_install_target_dir_component_c3";
    let directories_arg = "-d";

    ucmd.args(&[directories_arg, component1, component2, component3])
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists(component1));
    assert!(at.dir_exists(component2));
    assert!(at.dir_exists(component3));
}

#[test]
fn test_install_mode_numeric() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    let dir = "test_install_target_dir_dir_e";
    let dir2 = "test_install_target_dir_dir_e2";

    let file = "test_install_target_dir_file_e";
    let mode_arg = "--mode=333";

    at.touch(file);
    at.mkdir(dir);
    scene
        .ucmd()
        .arg(file)
        .arg(dir)
        .arg(mode_arg)
        .succeeds()
        .no_stderr();

    let dest_file = &format!("{}/{}", dir, file);
    assert!(at.file_exists(file));
    assert!(at.file_exists(dest_file));
    let permissions = at.metadata(dest_file).permissions();
    assert_eq!(0o100333 as u32, PermissionsExt::mode(&permissions));

    let mode_arg = "-m 0333";
    at.mkdir(dir2);

    let result = scene.ucmd().arg(mode_arg).arg(file).arg(dir2).run();

    println!("stderr = {:?}", result.stderr);
    println!("stdout = {:?}", result.stdout);

    assert!(result.success);
    let dest_file = &format!("{}/{}", dir2, file);
    assert!(at.file_exists(file));
    assert!(at.file_exists(dest_file));
    let permissions = at.metadata(dest_file).permissions();
    assert_eq!(0o100333 as u32, PermissionsExt::mode(&permissions));
}

#[test]
fn test_install_mode_symbolic() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_install_target_dir_dir_f";
    let file = "test_install_target_dir_file_f";
    let mode_arg = "--mode=o+wx";

    at.touch(file);
    at.mkdir(dir);
    ucmd.arg(file).arg(dir).arg(mode_arg).succeeds().no_stderr();

    let dest_file = &format!("{}/{}", dir, file);
    assert!(at.file_exists(file));
    assert!(at.file_exists(dest_file));
    let permissions = at.metadata(dest_file).permissions();
    assert_eq!(0o100003 as u32, PermissionsExt::mode(&permissions));
}

#[test]
fn test_install_mode_failing() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_install_target_dir_dir_g";
    let file = "test_install_target_dir_file_g";
    let mode_arg = "--mode=999";

    at.touch(file);
    at.mkdir(dir);
    assert!(ucmd
        .arg(file)
        .arg(dir)
        .arg(mode_arg)
        .fails()
        .stderr
        .contains("Invalid mode string: invalid digit found in string"));

    let dest_file = &format!("{}/{}", dir, file);
    assert!(at.file_exists(file));
    assert!(!at.file_exists(dest_file));
}

#[test]
fn test_install_mode_directories() {
    let (at, mut ucmd) = at_and_ucmd!();
    let component = "test_install_target_dir_component_h";
    let directories_arg = "-d";
    let mode_arg = "--mode=333";

    ucmd.arg(directories_arg)
        .arg(component)
        .arg(mode_arg)
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists(component));
    let permissions = at.metadata(component).permissions();
    assert_eq!(0o040333 as u32, PermissionsExt::mode(&permissions));
}

#[test]
fn test_install_target_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "test_install_target_file_file_i1";
    let file2 = "test_install_target_file_file_i2";

    at.touch(file1);
    at.touch(file2);
    ucmd.arg(file1).arg(file2).succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));
}

#[test]
fn test_install_target_new_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "test_install_target_new_filer_file_j";
    let dir = "test_install_target_new_file_dir_j";

    at.touch(file);
    at.mkdir(dir);
    ucmd.arg(file)
        .arg(format!("{}/{}", dir, file))
        .succeeds()
        .no_stderr();

    assert!(at.file_exists(file));
    assert!(at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_target_new_file_with_group() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "test_install_target_new_filer_file_j";
    let dir = "test_install_target_new_file_dir_j";
    let gid = get_effective_gid();

    at.touch(file);
    at.mkdir(dir);
    let result = ucmd
        .arg(file)
        .arg("--group")
        .arg(gid.to_string())
        .arg(format!("{}/{}", dir, file))
        .run();

    println!("stderr = {:?}", result.stderr);
    println!("stdout = {:?}", result.stdout);

    if is_ci() && result.stderr.contains("error: no such group:") {
        // In the CI, some server are failing to return the group.
        // As seems to be a configuration issue, ignoring it
        return;
    }

    assert!(result.success);
    assert!(at.file_exists(file));
    assert!(at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_target_new_file_with_owner() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "test_install_target_new_filer_file_j";
    let dir = "test_install_target_new_file_dir_j";
    let uid = get_effective_uid();

    at.touch(file);
    at.mkdir(dir);
    let result = ucmd
        .arg(file)
        .arg("--owner")
        .arg(uid.to_string())
        .arg(format!("{}/{}", dir, file))
        .run();

    println!("stderr = {:?}", result.stderr);
    println!("stdout = {:?}", result.stdout);

    if is_ci() && result.stderr.contains("error: no such user:") {
        // In the CI, some server are failing to return the user id.
        // As seems to be a configuration issue, ignoring it
        return;
    }

    assert!(result.success);
    assert!(at.file_exists(file));
    assert!(at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_target_new_file_failing_nonexistent_parent() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "test_install_target_new_file_failing_file_k1";
    let file2 = "test_install_target_new_file_failing_file_k2";
    let dir = "test_install_target_new_file_failing_dir_k";

    at.touch(file1);

    let err = ucmd
        .arg(file1)
        .arg(format!("{}/{}", dir, file2))
        .fails()
        .stderr;

    assert!(err.contains("not a directory"))
}

#[test]
fn test_install_preserve_timestamps() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "test_install_target_dir_file_a1";
    let file2 = "test_install_target_dir_file_a2";
    at.touch(file1);

    ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));

    let file1_metadata = at.metadata(file1);
    let file2_metadata = at.metadata(file2);

    assert_eq!(
        file1_metadata.accessed().ok(),
        file2_metadata.accessed().ok()
    );
    assert_eq!(
        file1_metadata.modified().ok(),
        file2_metadata.modified().ok()
    );
}

// These two tests are failing but should work
#[test]
fn test_install_copy_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "test_install_target_dir_file_a1";
    let file2 = "test_install_target_dir_file_a2";

    at.touch(file1);
    ucmd.arg(file1).arg(file2).succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));
}

#[test]
#[cfg(target_os = "linux")]
fn test_install_target_file_dev_null() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "/dev/null";
    let file2 = "test_install_target_file_file_i2";

    ucmd.arg(file1).arg(file2).succeeds().no_stderr();
    assert!(at.file_exists(file2));
}

#[test]
fn test_install_nested_paths_copy_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir1 = "test_install_target_new_file_dir_l";
    let dir2 = "test_install_target_new_file_dir_m";
    let file1 = "test_install_target_file_file_l1";

    at.mkdir(dir1);
    at.mkdir(dir2);
    at.touch(&format!("{}/{}", dir1, file1));

    ucmd.arg(format!("{}/{}", dir1, file1))
        .arg(dir2)
        .succeeds()
        .no_stderr();
    assert!(at.file_exists(&format!("{}/{}", dir2, file1)));
}

#[test]
fn test_install_failing_omitting_directory() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir1 = "source_dir";
    let file1 = "source_file";
    let dir2 = "target_dir";

    at.mkdir(dir1);
    at.mkdir(dir2);
    at.touch(file1);

    let r = ucmd.arg(dir1).arg(file1).arg(dir2).run();
    assert!(r.code == Some(1));
    assert!(r.stderr.contains("omitting directory"));
}

#[test]
fn test_install_failing_no_such_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let file2 = "inexistent_file";
    let dir1 = "target_dir";

    at.mkdir(dir1);
    at.touch(file1);

    let r = ucmd.arg(file1).arg(file2).arg(dir1).run();
    assert!(r.code == Some(1));
    assert!(r.stderr.contains("No such file or directory"));
}
<commit_msg>install: normalize test filenames<commit_after>use crate::common::util::*;
use rust_users::*;
use std::os::unix::fs::PermissionsExt;

#[test]
fn test_install_help() {
    let (_, mut ucmd) = at_and_ucmd!();

    assert!(ucmd
        .arg("--help")
        .succeeds()
        .no_stderr()
        .stdout
        .contains("FLAGS:"));
}

#[test]
fn test_install_basic() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "target_dir";
    let file1 = "source_file1";
    let file2 = "source_file2";

    at.touch(file1);
    at.touch(file2);
    at.mkdir(dir);
    ucmd.arg(file1).arg(file2).arg(dir).succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));
    assert!(at.file_exists(&format!("{}/{}", dir, file1)));
    assert!(at.file_exists(&format!("{}/{}", dir, file2)));
}

#[test]
fn test_install_twice_dir() {
    let dir = "dir";
    let scene = TestScenario::new(util_name!());

    scene.ucmd().arg("-d").arg(dir).succeeds();
    scene.ucmd().arg("-d").arg(dir).succeeds();
    let at = &scene.fixtures;

    assert!(at.dir_exists(dir));
}

#[test]
fn test_install_failing_not_dir() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "file1";
    let file2 = "file2";
    let file3 = "file3";

    at.touch(file1);
    at.touch(file2);
    at.touch(file3);
    assert!(ucmd
        .arg(file1)
        .arg(file2)
        .arg(file3)
        .fails()
        .stderr
        .contains("not a directory"));
}

#[test]
fn test_install_unimplemented_arg() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "target_dir";
    let file = "source_file";
    let context_arg = "--context";

    at.touch(file);
    at.mkdir(dir);
    assert!(ucmd
        .arg(context_arg)
        .arg(file)
        .arg(dir)
        .fails()
        .stderr
        .contains("Unimplemented"));

    assert!(!at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_component_directories() {
    let (at, mut ucmd) = at_and_ucmd!();
    let component1 = "component1";
    let component2 = "component2";
    let component3 = "component3";
    let directories_arg = "-d";

    ucmd.args(&[directories_arg, component1, component2, component3])
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists(component1));
    assert!(at.dir_exists(component2));
    assert!(at.dir_exists(component3));
}

#[test]
fn test_install_mode_numeric() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    let dir = "dir1";
    let dir2 = "dir2";

    let file = "file";
    let mode_arg = "--mode=333";

    at.touch(file);
    at.mkdir(dir);
    scene
        .ucmd()
        .arg(file)
        .arg(dir)
        .arg(mode_arg)
        .succeeds()
        .no_stderr();

    let dest_file = &format!("{}/{}", dir, file);
    assert!(at.file_exists(file));
    assert!(at.file_exists(dest_file));
    let permissions = at.metadata(dest_file).permissions();
    assert_eq!(0o100333 as u32, PermissionsExt::mode(&permissions));

    let mode_arg = "-m 0333";
    at.mkdir(dir2);

    let result = scene.ucmd().arg(mode_arg).arg(file).arg(dir2).run();

    println!("stderr = {:?}", result.stderr);
    println!("stdout = {:?}", result.stdout);

    assert!(result.success);
    let dest_file = &format!("{}/{}", dir2, file);
    assert!(at.file_exists(file));
    assert!(at.file_exists(dest_file));
    let permissions = at.metadata(dest_file).permissions();
    assert_eq!(0o100333 as u32, PermissionsExt::mode(&permissions));
}

#[test]
fn test_install_mode_symbolic() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "target_dir";
    let file = "source_file";
    let mode_arg = "--mode=o+wx";

    at.touch(file);
    at.mkdir(dir);
    ucmd.arg(file).arg(dir).arg(mode_arg).succeeds().no_stderr();

    let dest_file = &format!("{}/{}", dir, file);
    assert!(at.file_exists(file));
    assert!(at.file_exists(dest_file));
    let permissions = at.metadata(dest_file).permissions();
    assert_eq!(0o100003 as u32, PermissionsExt::mode(&permissions));
}

#[test]
fn test_install_mode_failing() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "target_dir";
    let file = "source_file";
    let mode_arg = "--mode=999";

    at.touch(file);
    at.mkdir(dir);
    assert!(ucmd
        .arg(file)
        .arg(dir)
        .arg(mode_arg)
        .fails()
        .stderr
        .contains("Invalid mode string: invalid digit found in string"));

    let dest_file = &format!("{}/{}", dir, file);
    assert!(at.file_exists(file));
    assert!(!at.file_exists(dest_file));
}

#[test]
fn test_install_mode_directories() {
    let (at, mut ucmd) = at_and_ucmd!();
    let component = "component";
    let directories_arg = "-d";
    let mode_arg = "--mode=333";

    ucmd.arg(directories_arg)
        .arg(component)
        .arg(mode_arg)
        .succeeds()
        .no_stderr();

    assert!(at.dir_exists(component));
    let permissions = at.metadata(component).permissions();
    assert_eq!(0o040333 as u32, PermissionsExt::mode(&permissions));
}

#[test]
fn test_install_target_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let file2 = "target_file";

    at.touch(file1);
    at.touch(file2);
    ucmd.arg(file1).arg(file2).succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));
}

#[test]
fn test_install_target_new_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "file";
    let dir = "target_dir";

    at.touch(file);
    at.mkdir(dir);
    ucmd.arg(file)
        .arg(format!("{}/{}", dir, file))
        .succeeds()
        .no_stderr();

    assert!(at.file_exists(file));
    assert!(at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_target_new_file_with_group() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "file";
    let dir = "target_dir";
    let gid = get_effective_gid();

    at.touch(file);
    at.mkdir(dir);
    let result = ucmd
        .arg(file)
        .arg("--group")
        .arg(gid.to_string())
        .arg(format!("{}/{}", dir, file))
        .run();

    println!("stderr = {:?}", result.stderr);
    println!("stdout = {:?}", result.stdout);

    if is_ci() && result.stderr.contains("error: no such group:") {
        // In the CI, some server are failing to return the group.
        // As seems to be a configuration issue, ignoring it
        return;
    }

    assert!(result.success);
    assert!(at.file_exists(file));
    assert!(at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_target_new_file_with_owner() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "file";
    let dir = "target_dir";
    let uid = get_effective_uid();

    at.touch(file);
    at.mkdir(dir);
    let result = ucmd
        .arg(file)
        .arg("--owner")
        .arg(uid.to_string())
        .arg(format!("{}/{}", dir, file))
        .run();

    println!("stderr = {:?}", result.stderr);
    println!("stdout = {:?}", result.stdout);

    if is_ci() && result.stderr.contains("error: no such user:") {
        // In the CI, some server are failing to return the user id.
        // As seems to be a configuration issue, ignoring it
        return;
    }

    assert!(result.success);
    assert!(at.file_exists(file));
    assert!(at.file_exists(&format!("{}/{}", dir, file)));
}

#[test]
fn test_install_target_new_file_failing_nonexistent_parent() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let file2 = "target_file";
    let dir = "target_dir";

    at.touch(file1);

    let err = ucmd
        .arg(file1)
        .arg(format!("{}/{}", dir, file2))
        .fails()
        .stderr;

    assert!(err.contains("not a directory"))
}

#[test]
fn test_install_preserve_timestamps() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let file2 = "target_file";
    at.touch(file1);

    ucmd.arg(file1).arg(file2).arg("-p").succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));

    let file1_metadata = at.metadata(file1);
    let file2_metadata = at.metadata(file2);

    assert_eq!(
        file1_metadata.accessed().ok(),
        file2_metadata.accessed().ok()
    );
    assert_eq!(
        file1_metadata.modified().ok(),
        file2_metadata.modified().ok()
    );
}

// These two tests are failing but should work
#[test]
fn test_install_copy_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let file2 = "target_file";

    at.touch(file1);
    ucmd.arg(file1).arg(file2).succeeds().no_stderr();

    assert!(at.file_exists(file1));
    assert!(at.file_exists(file2));
}

#[test]
#[cfg(target_os = "linux")]
fn test_install_target_file_dev_null() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "/dev/null";
    let file2 = "target_file";

    ucmd.arg(file1).arg(file2).succeeds().no_stderr();
    assert!(at.file_exists(file2));
}

#[test]
fn test_install_nested_paths_copy_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let dir1 = "source_dir";
    let dir2 = "target_dir";

    at.mkdir(dir1);
    at.mkdir(dir2);
    at.touch(&format!("{}/{}", dir1, file1));

    ucmd.arg(format!("{}/{}", dir1, file1))
        .arg(dir2)
        .succeeds()
        .no_stderr();
    assert!(at.file_exists(&format!("{}/{}", dir2, file1)));
}

#[test]
fn test_install_failing_omitting_directory() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let dir1 = "source_dir";
    let dir2 = "target_dir";

    at.mkdir(dir1);
    at.mkdir(dir2);
    at.touch(file1);

    let r = ucmd.arg(dir1).arg(file1).arg(dir2).run();
    assert!(r.code == Some(1));
    assert!(r.stderr.contains("omitting directory"));
}

#[test]
fn test_install_failing_no_such_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file1 = "source_file";
    let file2 = "inexistent_file";
    let dir1 = "target_dir";

    at.mkdir(dir1);
    at.touch(file1);

    let r = ucmd.arg(file1).arg(file2).arg(dir1).run();
    assert!(r.code == Some(1));
    assert!(r.stderr.contains("No such file or directory"));
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate coreutils;
extern crate extra;

use std::cell::Cell; // Provide mutable fields in immutable structs
use std::env;
use std::error::Error;
use std::fs;
use std::io::{self, BufReader, Read, Stderr, StdoutLock, Write};
use std::process::exit;
use extra::option::OptionalExt;
use coreutils::ArgParser;

const MAN_PAGE: &'static str = /* @MANSTART{cat} */ r#"NAME
    cat - concatenate files and print on the standard output

SYNOPSIS
    cat [-h | --help] [-A | --show-all] [-b | --number-nonblank] [-e] [-E | --show-ends]
        [-n | --number] [-s | --squeeze-blank] [-t] [-T] FILES...

DESCRIPTION
    Concatenates all files to the standard output.

    If no file is given, or if FILE is '-', read from standard input.

OPTIONS
    -A
    --show-all
        equivalent to -vET

    -b
    --number-nonblank
        number nonempty output lines, overriding -n

    -e
        equivalent to -vE

    -E
    --show-ends
        display $ at the end of each line

    -n
    --number
        number all output lines

    -s
    --squeeze-blank
        supress repeated empty output lines

    -t
        equivalent to -vT

    -T
    --show_tabs
        display TAB characters as ^I

    -v
    --show-nonprinting
        use caret (^) and M- notation, except for LFD and TAB.

    -h
    --help
        display this help and exit

AUTHOR
    Written by Michael Murphy.
"#; /* @MANEND */

struct Program {
    exit_status:      Cell<i32>,
    number:           bool,
    number_nonblank:  bool,
    show_ends:        bool,
    show_tabs:        bool,
    show_nonprinting: bool,
    squeeze_blank:    bool,
    paths:            Vec<String>,
}

impl Program {
    /// Initialize the program's arguments and flags.
    fn initialize(stdout: &mut StdoutLock, stderr: &mut Stderr) -> Program {
        let mut parser = ArgParser::new(10).
            add_flag(&["A", "show-all"]). //vET
            add_flag(&["b", "number-nonblank"]).
            add_flag(&["e"]). //vE
            add_flag(&["E", "show-ends"]).
            add_flag(&["n", "number"]).
            add_flag(&["s", "squeeze-blank"]).
            add_flag(&["t"]). //vT
            add_flag(&["T", "show-tabs"]).
            add_flag(&["v", "show-nonprinting"]).
            add_flag(&["h", "help"]);
        parser.parse(env::args());

        let mut cat = Program {
            exit_status:      Cell::new(0i32),
            number:           false,
            number_nonblank:  false,
            show_ends:        false,
            show_tabs:        false,
            show_nonprinting: false,
            squeeze_blank:    false,
            paths:            Vec::with_capacity(parser.args.len()),
        };

        if parser.found("help") {
            stdout.write(MAN_PAGE.as_bytes()).try(stderr);
            stdout.flush().try(stderr);
            exit(0);
        }

        if parser.found("show-all") {
            cat.show_nonprinting = true;
            cat.show_ends = true;
            cat.show_tabs = true;
        }

        if parser.found("number") {
            cat.number = true;
            cat.number_nonblank = false;
        }

        if parser.found("number-nonblank") {
            cat.number_nonblank = true;
            cat.number = false;
        }

        if parser.found("show-ends") || parser.found(&'e') {
            cat.show_ends = true;
        }

        if parser.found("squeeze-blank") {
            cat.squeeze_blank = true;
        }

        if parser.found("show-tabs") || parser.found(&'t') {
            cat.show_tabs = true;
        }

        if parser.found("show-nonprinting") || parser.found(&'e') || parser.found(&'t') {
            cat.show_nonprinting = true;
        }

        if !parser.args.is_empty() {
            cat.paths = parser.args;
        }
        cat
    }

    /// Execute the parameters given to the program.
    fn and_execute(&self, stdout: &mut StdoutLock, stderr: &mut Stderr) -> i32 {
        let stdin = io::stdin();
        let line_count = &mut 0usize;
        let flags_enabled = self.number || self.number_nonblank || self.show_ends || self.show_tabs ||
                            self.squeeze_blank || self.show_nonprinting;

        if self.paths.is_empty() && flags_enabled {
            self.cat(&mut stdin.lock(), line_count, stdout, stderr);
        } else if self.paths.is_empty() {
            io::copy(&mut stdin.lock(), stdout).try(stderr);
        } else {
            for path in &self.paths {
                if flags_enabled && path == "-" {
                    self.cat(&mut stdin.lock(), line_count, stdout, stderr);
                } else if flags_enabled {
                    fs::File::open(&path)
                        // Open the file and copy the file's contents to standard output based input arguments.
                        .map(|file| self.cat(BufReader::new(file), line_count, stdout, stderr))
                        // If an error occurred, print the error and set the exit status.
                        .unwrap_or_else(|message| {
                            stderr.write(path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);
                        });
                } else if path == "-" {
                    // Copy the standard input directly to the standard output.
                    io::copy(&mut stdin.lock(), stdout).try(stderr);
                } else {
                    // Open a file and copy the contents directly to standard output.
                    fs::File::open(&path).map(|ref mut file| { io::copy(file, stdout).try(stderr); })
                        // If an error occurs, print the error and set the exit status.
                        .unwrap_or_else(|message| {
                            stderr.write(path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);
                        });
                }
            }
        }
        self.exit_status.get()
    }

    /// Cats either a file or stdin based on the flag arguments given to the program.
    fn cat<F: Read>(&self, file: F, line_count: &mut usize, stdout: &mut StdoutLock, stderr: &mut Stderr) {
        let mut character_count = 0;
        let mut last_line_was_blank = false;

        for byte in file.bytes().map(|x| x.unwrap()) {
            if (self.number && character_count == 0) || (character_count == 0 && self.number_nonblank && byte != b'\n') {
                stdout.write(b"     ").try(stderr);
                stdout.write(line_count.to_string().as_bytes()).try(stderr);
                stdout.write(b"  ").try(stderr);
                *line_count += 1;
            }
            match byte {
                0...8 | 11...31 => if self.show_nonprinting {
                    push_caret(stdout, stderr, byte+64);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                9 => {
                    if self.show_tabs {
                        push_caret(stdout, stderr, b'I');
                    } else {
                        stdout.write(&[byte]).try(stderr);
                    }
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                }
                10 => {
                    if character_count == 0 {
                        if self.squeeze_blank && last_line_was_blank {
                            continue
                        } else if !last_line_was_blank {
                            last_line_was_blank = true;
                        }
                    } else {
                        last_line_was_blank = false;
                        character_count = 0;
                    }
                    if self.show_ends {
                        stdout.write(b"$\n").try(stderr);
                    } else {
                        stdout.write(b"\n").try(stderr);
                    }
                    stdout.flush().try(stderr);
                },
                32...126 => {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                127 => if self.show_nonprinting {
                    push_caret(stdout, stderr, b'?');
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                128...159 => if self.show_nonprinting {
                    stdout.write(b"M-^").try(stderr);
                    stdout.write(&[byte-64]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                _ => if self.show_nonprinting {
                    stdout.write(b"M-").try(stderr);
                    stdout.write(&[byte-128]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
            }
        }
    }
}
/// Increase the character count by one if number printing is enabled.
fn count_character(character_count: &mut usize, number: &bool, number_nonblank: &bool) {
    if *number || *number_nonblank {
        *character_count += 1;
    }
}

/// Print a caret notation to stdout.
fn push_caret(stdout: &mut StdoutLock, stderr: &mut Stderr, notation: u8) {
    stdout.write(&[b'^']).try(stderr);
    stdout.write(&[notation]).try(stderr);
}

fn main() {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    exit(Program::initialize(&mut stdout, &mut stderr).and_execute(&mut stdout, &mut stderr));
}
<commit_msg>Fix cat error with folders<commit_after>#![deny(warnings)]

extern crate coreutils;
extern crate extra;

use std::cell::Cell; // Provide mutable fields in immutable structs
use std::env;
use std::error::Error;
use std::fs;
use std::io::{self, BufReader, Read, Stderr, StdoutLock, Write};
use std::process::exit;
use extra::option::OptionalExt;
use coreutils::ArgParser;

const MAN_PAGE: &'static str = /* @MANSTART{cat} */ r#"NAME
    cat - concatenate files and print on the standard output

SYNOPSIS
    cat [-h | --help] [-A | --show-all] [-b | --number-nonblank] [-e] [-E | --show-ends]
        [-n | --number] [-s | --squeeze-blank] [-t] [-T] FILES...

DESCRIPTION
    Concatenates all files to the standard output.

    If no file is given, or if FILE is '-', read from standard input.

OPTIONS
    -A
    --show-all
        equivalent to -vET

    -b
    --number-nonblank
        number nonempty output lines, overriding -n

    -e
        equivalent to -vE

    -E
    --show-ends
        display $ at the end of each line

    -n
    --number
        number all output lines

    -s
    --squeeze-blank
        supress repeated empty output lines

    -t
        equivalent to -vT

    -T
    --show_tabs
        display TAB characters as ^I

    -v
    --show-nonprinting
        use caret (^) and M- notation, except for LFD and TAB.

    -h
    --help
        display this help and exit

AUTHOR
    Written by Michael Murphy.
"#; /* @MANEND */

struct Program {
    exit_status:      Cell<i32>,
    number:           bool,
    number_nonblank:  bool,
    show_ends:        bool,
    show_tabs:        bool,
    show_nonprinting: bool,
    squeeze_blank:    bool,
    paths:            Vec<String>,
}

impl Program {
    /// Initialize the program's arguments and flags.
    fn initialize(stdout: &mut StdoutLock, stderr: &mut Stderr) -> Program {
        let mut parser = ArgParser::new(10).
            add_flag(&["A", "show-all"]). //vET
            add_flag(&["b", "number-nonblank"]).
            add_flag(&["e"]). //vE
            add_flag(&["E", "show-ends"]).
            add_flag(&["n", "number"]).
            add_flag(&["s", "squeeze-blank"]).
            add_flag(&["t"]). //vT
            add_flag(&["T", "show-tabs"]).
            add_flag(&["v", "show-nonprinting"]).
            add_flag(&["h", "help"]);
        parser.parse(env::args());

        let mut cat = Program {
            exit_status:      Cell::new(0i32),
            number:           false,
            number_nonblank:  false,
            show_ends:        false,
            show_tabs:        false,
            show_nonprinting: false,
            squeeze_blank:    false,
            paths:            Vec::with_capacity(parser.args.len()),
        };

        if parser.found("help") {
            stdout.write(MAN_PAGE.as_bytes()).try(stderr);
            stdout.flush().try(stderr);
            exit(0);
        }

        if parser.found("show-all") {
            cat.show_nonprinting = true;
            cat.show_ends = true;
            cat.show_tabs = true;
        }

        if parser.found("number") {
            cat.number = true;
            cat.number_nonblank = false;
        }

        if parser.found("number-nonblank") {
            cat.number_nonblank = true;
            cat.number = false;
        }

        if parser.found("show-ends") || parser.found(&'e') {
            cat.show_ends = true;
        }

        if parser.found("squeeze-blank") {
            cat.squeeze_blank = true;
        }

        if parser.found("show-tabs") || parser.found(&'t') {
            cat.show_tabs = true;
        }

        if parser.found("show-nonprinting") || parser.found(&'e') || parser.found(&'t') {
            cat.show_nonprinting = true;
        }

        if !parser.args.is_empty() {
            cat.paths = parser.args;
        }
        cat
    }

    /// Execute the parameters given to the program.
    fn and_execute(&self, stdout: &mut StdoutLock, stderr: &mut Stderr) -> i32 {
        let stdin = io::stdin();
        let line_count = &mut 0usize;
        let flags_enabled = self.number || self.number_nonblank || self.show_ends || self.show_tabs ||
                            self.squeeze_blank || self.show_nonprinting;

        if self.paths.is_empty() && flags_enabled {
            self.cat(&mut stdin.lock(), line_count, stdout, stderr);
        } else if self.paths.is_empty() {
            io::copy(&mut stdin.lock(), stdout).try(stderr);
        } else {
            for path in &self.paths {
                if flags_enabled && path == "-" {
                    self.cat(&mut stdin.lock(), line_count, stdout, stderr);
                } else if path == "-" {
                    // Copy the standard input directly to the standard output.
                    io::copy(&mut stdin.lock(), stdout).try(stderr);
                } else if fs::metadata(&path).map(|m| m.is_dir()).unwrap_or(false) {
                    stderr.write(path.as_bytes()).try(stderr);
                    stderr.write(b": Is a directory\n").try(stderr);
                    stderr.flush().try(stderr);
                    self.exit_status.set(1i32);
                } else if flags_enabled {
                    fs::File::open(&path)
                        // Open the file and copy the file's contents to standard output based input arguments.
                        .map(|file| self.cat(BufReader::new(file), line_count, stdout, stderr))
                        // If an error occurred, print the error and set the exit status.
                        .unwrap_or_else(|message| {
                            stderr.write(path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);
                        });
                } else {
                    // Open a file and copy the contents directly to standard output.
                    fs::File::open(&path).map(|ref mut file| { io::copy(file, stdout).try(stderr); })
                        // If an error occurs, print the error and set the exit status.
                        .unwrap_or_else(|message| {
                            stderr.write(path.as_bytes()).try(stderr);
                            stderr.write(b": ").try(stderr);
                            stderr.write(message.description().as_bytes()).try(stderr);
                            stderr.write(b"\n").try(stderr);
                            stderr.flush().try(stderr);
                            self.exit_status.set(1i32);
                        });
                }
            }
        }
        self.exit_status.get()
    }

    /// Cats either a file or stdin based on the flag arguments given to the program.
    fn cat<F: Read>(&self, file: F, line_count: &mut usize, stdout: &mut StdoutLock, stderr: &mut Stderr) {
        let mut character_count = 0;
        let mut last_line_was_blank = false;

        for byte in file.bytes().map(|x| x.unwrap()) {
            if (self.number && character_count == 0) || (character_count == 0 && self.number_nonblank && byte != b'\n') {
                stdout.write(b"     ").try(stderr);
                stdout.write(line_count.to_string().as_bytes()).try(stderr);
                stdout.write(b"  ").try(stderr);
                *line_count += 1;
            }
            match byte {
                0...8 | 11...31 => if self.show_nonprinting {
                    push_caret(stdout, stderr, byte+64);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                9 => {
                    if self.show_tabs {
                        push_caret(stdout, stderr, b'I');
                    } else {
                        stdout.write(&[byte]).try(stderr);
                    }
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                }
                10 => {
                    if character_count == 0 {
                        if self.squeeze_blank && last_line_was_blank {
                            continue
                        } else if !last_line_was_blank {
                            last_line_was_blank = true;
                        }
                    } else {
                        last_line_was_blank = false;
                        character_count = 0;
                    }
                    if self.show_ends {
                        stdout.write(b"$\n").try(stderr);
                    } else {
                        stdout.write(b"\n").try(stderr);
                    }
                    stdout.flush().try(stderr);
                },
                32...126 => {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                127 => if self.show_nonprinting {
                    push_caret(stdout, stderr, b'?');
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                128...159 => if self.show_nonprinting {
                    stdout.write(b"M-^").try(stderr);
                    stdout.write(&[byte-64]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
                _ => if self.show_nonprinting {
                    stdout.write(b"M-").try(stderr);
                    stdout.write(&[byte-128]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                } else {
                    stdout.write(&[byte]).try(stderr);
                    count_character(&mut character_count, &self.number, &self.number_nonblank);
                },
            }
        }
    }
}
/// Increase the character count by one if number printing is enabled.
fn count_character(character_count: &mut usize, number: &bool, number_nonblank: &bool) {
    if *number || *number_nonblank {
        *character_count += 1;
    }
}

/// Print a caret notation to stdout.
fn push_caret(stdout: &mut StdoutLock, stderr: &mut Stderr, notation: u8) {
    stdout.write(&[b'^']).try(stderr);
    stdout.write(&[notation]).try(stderr);
}

fn main() {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    let mut stderr = io::stderr();
    exit(Program::initialize(&mut stdout, &mut stderr).and_execute(&mut stdout, &mut stderr));
}
<|endoftext|>