<commit_before>//! # Beamium.
//!
//! Beamium scrap Prometheus endpoint and forward metrics to Warp10.
extern crate clap;
extern crate yaml_rust;
extern crate time;
extern crate hyper;
extern crate hyper_native_tls;
extern crate cast;
extern crate regex;
#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error, slog_crit)]
extern crate slog;
#[macro_use]
extern crate slog_scope;
extern crate slog_term;
extern crate slog_stream;
extern crate slog_json;
extern crate nix;

use clap::App;
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::fs;
use nix::sys::signal;
use std::time::Duration;

mod config;
mod source;
mod router;
mod sink;
mod log;

include!("version.rs");

static mut SIGINT: bool = false;

extern "C" fn handle_sigint(_: i32) {
    unsafe {
        SIGINT = true;
    }
}

/// Main loop.
fn main() {
    unsafe {
        let sig_action = signal::SigAction::new(signal::SigHandler::Handler(handle_sigint),
                                                signal::SaFlags::empty(),
                                                signal::SigSet::empty());
        signal::sigaction(signal::SIGINT, &sig_action).unwrap();
    }

    // Setup a bare logger
    log::bootstrap();

    let matches = App::new("beamium")
        .version(&*format!("{} ({})", env!("CARGO_PKG_VERSION"), COMMIT))
        .author("d33d33 <kevin@d33d33.fr>")
        .about("Send Prometheus metrics to Warp10")
        .args_from_usage("-c, --config=[FILE] 'Sets a custom config file'
                              \
                          -v...                'Increase verbosity level (console only)'")
        .get_matches();

    info!("starting");

    // Bootstrap config
    let config_path = matches.value_of("config").unwrap_or("");
    let config = config::load_config(&config_path);
    if config.is_err() {
        crit!("Fail to load config {}: {}",
              &config_path,
              config.err().unwrap());
        std::process::exit(-1);
    }
    let config = config.ok().unwrap();

    // Setup logging
    log::log(&config.parameters, matches.occurrences_of("v"));

    // Ensure dirs
    let dir = fs::create_dir_all(&config.parameters.source_dir);
    if dir.is_err() {
        crit!("Fail to create source directory {}: {}",
              &config.parameters.source_dir,
              dir.err().unwrap());
        std::process::exit(-1);
    }
    let dir = fs::create_dir_all(&config.parameters.sink_dir);
    if dir.is_err() {
        crit!("Fail to create sink directory {}: {}",
              &config.parameters.sink_dir,
              dir.err().unwrap());
        std::process::exit(-1);
    }

    // Synchronisation stuff
    // let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    let sigint = Arc::new(AtomicBool::new(false));
    let mut handles = Vec::with_capacity(config.sources.len() + 1 + config.sinks.len());

    // Spawn sources
    info!("spawning sources");
    for source in config.sources {
        let (parameters, sigint) = (config.parameters.clone(), sigint.clone());
        handles.push(thread::spawn(move || {
            slog_scope::scope(slog_scope::logger().new(o!("source" => source.name.clone())),
                              || source::source(&source, &parameters, sigint));
        }));
    }

    // Spawn router
    info!("spawning router");
    {
        let (sinks, labels, parameters, sigint) = (config.sinks.clone(),
                                                   config.labels.clone(),
                                                   config.parameters.clone(),
                                                   sigint.clone());
        handles.push(thread::spawn(move || {
            slog_scope::scope(slog_scope::logger().new(o!()),
                              || router::router(&sinks, &labels, &parameters, sigint));
        }));
    }

    // Spawn sinks
    info!("spawning sinks");
    for sink in config.sinks {
        let (parameters, sigint) = (config.parameters.clone(), sigint.clone());
        handles.push(thread::spawn(move || {
            slog_scope::scope(slog_scope::logger().new(o!("sink" => sink.name.clone())),
                              || sink::sink(&sink, &parameters, sigint));
        }));
    }

    info!("started");
    // Wait for sigint
    loop {
        thread::sleep(Duration::from_millis(10));

        unsafe {
            if SIGINT {
                sigint.store(true, Ordering::Relaxed);
            }
        }

        if sigint.load(Ordering::Relaxed) {
            break;
        }
    }

    info!("shutding down");
    for handle in handles {
        handle.join().unwrap();
    }
    info!("halted");
}
<commit_msg>fix: cleanup<commit_after>//! # Beamium.
//!
//! Beamium scrap Prometheus endpoint and forward metrics to Warp10.
extern crate clap;
extern crate yaml_rust;
extern crate time;
extern crate hyper;
extern crate hyper_native_tls;
extern crate cast;
extern crate regex;
#[macro_use(o, slog_log, slog_trace, slog_debug, slog_info, slog_warn, slog_error, slog_crit)]
extern crate slog;
#[macro_use]
extern crate slog_scope;
extern crate slog_term;
extern crate slog_stream;
extern crate slog_json;
extern crate nix;

use clap::App;
use std::thread;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::fs;
use nix::sys::signal;
use std::time::Duration;

mod config;
mod source;
mod router;
mod sink;
mod log;

include!("version.rs");

static mut SIGINT: bool = false;

extern "C" fn handle_sigint(_: i32) {
    unsafe {
        SIGINT = true;
    }
}

/// Main loop.
fn main() {
    unsafe {
        let sig_action = signal::SigAction::new(signal::SigHandler::Handler(handle_sigint),
                                                signal::SaFlags::empty(),
                                                signal::SigSet::empty());
        signal::sigaction(signal::SIGINT, &sig_action).unwrap();
    }

    // Setup a bare logger
    log::bootstrap();

    let matches = App::new("beamium")
        .version(&*format!("{} ({})", env!("CARGO_PKG_VERSION"), COMMIT))
        .author("d33d33 <kevin@d33d33.fr>")
        .about("Send Prometheus metrics to Warp10")
        .args_from_usage("-c, --config=[FILE] 'Sets a custom config file'
                              \
                          -v...                'Increase verbosity level (console only)'")
        .get_matches();

    info!("starting");

    // Bootstrap config
    let config_path = matches.value_of("config").unwrap_or("");
    let config = config::load_config(&config_path);
    if config.is_err() {
        crit!("Fail to load config {}: {}",
              &config_path,
              config.err().unwrap());
        std::process::exit(-1);
    }
    let config = config.ok().unwrap();

    // Setup logging
    log::log(&config.parameters, matches.occurrences_of("v"));

    // Ensure dirs
    let dir = fs::create_dir_all(&config.parameters.source_dir);
    if dir.is_err() {
        crit!("Fail to create source directory {}: {}",
              &config.parameters.source_dir,
              dir.err().unwrap());
        std::process::exit(-1);
    }
    let dir = fs::create_dir_all(&config.parameters.sink_dir);
    if dir.is_err() {
        crit!("Fail to create sink directory {}: {}",
              &config.parameters.sink_dir,
              dir.err().unwrap());
        std::process::exit(-1);
    }

    // Synchronisation stuff
    let sigint = Arc::new(AtomicBool::new(false));
    let mut handles = Vec::with_capacity(config.sources.len() + 1 + config.sinks.len());

    // Spawn sources
    info!("spawning sources");
    for source in config.sources {
        let (parameters, sigint) = (config.parameters.clone(), sigint.clone());
        handles.push(thread::spawn(move || {
            slog_scope::scope(slog_scope::logger().new(o!("source" => source.name.clone())),
                              || source::source(&source, &parameters, sigint));
        }));
    }

    // Spawn router
    info!("spawning router");
    {
        let (sinks, labels, parameters, sigint) = (config.sinks.clone(),
                                                   config.labels.clone(),
                                                   config.parameters.clone(),
                                                   sigint.clone());
        handles.push(thread::spawn(move || {
            slog_scope::scope(slog_scope::logger().new(o!()),
                              || router::router(&sinks, &labels, &parameters, sigint));
        }));
    }

    // Spawn sinks
    info!("spawning sinks");
    for sink in config.sinks {
        let (parameters, sigint) = (config.parameters.clone(), sigint.clone());
        handles.push(thread::spawn(move || {
            slog_scope::scope(slog_scope::logger().new(o!("sink" => sink.name.clone())),
                              || sink::sink(&sink, &parameters, sigint));
        }));
    }

    info!("started");
    // Wait for sigint
    loop {
        thread::sleep(Duration::from_millis(10));

        unsafe {
            if SIGINT {
                sigint.store(true, Ordering::Relaxed);
            }
        }

        if sigint.load(Ordering::Relaxed) {
            break;
        }
    }

    info!("shutding down");
    for handle in handles {
        handle.join().unwrap();
    }
    info!("halted");
}
<|endoftext|>


<commit_before>extern crate zoom;
extern crate rand;

use super::bot::*;
use super::Vec3;

const BOTS_RADIUS_MULTIPLIER: f32 = 5.0;
const RADIUS_STATIC: f32 = 5.0;
const ENERGY_RATIO: f64 = 0.002;
const ENERGY_VARIATION: f64 = 0.1;
pub const ENERGY_THRESHOLD: i64 = 500000;
const ENERGY_FULL_COST: i64 = 5000;
const PHYSICS_RADIUS: f64 = 1.0;

const EDGE_FOOD_BENEFIT: f64 = 2.0;
const EDGE_DIFFUSION_COEFFICIENT: f64 = 0.01;

const DRAG: f64 = 0.1;

#[derive(Clone)]
pub struct RadParticle {
    pub p: zoom::BasicParticle<Vec3, f64>,
}

impl zoom::Position<Vec3> for RadParticle {
    fn position(&self) -> Vec3 {
        self.p.position()
    }
}

impl zoom::Velocity<Vec3> for RadParticle {
    fn velocity(&self) -> Vec3 {
        self.p.velocity()
    }
}

impl zoom::Particle<Vec3, f64> for RadParticle {
    fn impulse(&self, v: &Vec3) {
        self.p.impulse(v);
    }

    fn advance(&mut self, time: f64) {
        self.p.advance(time);
    }
}

impl zoom::Inertia<f64> for RadParticle {
    fn inertia(&self) -> f64 {
        self.p.inertia()
    }
}

impl zoom::Quanta<f64> for RadParticle {
    fn quanta(&self) -> f64 {
        self.p.quanta()
    }
}

impl zoom::PhysicsParticle<Vec3, f64> for RadParticle {
}

impl zoom::Ball<f64> for RadParticle {
    fn radius(&self) -> f64 {
        PHYSICS_RADIUS
    }
}

pub struct Node {
    pub particle: RadParticle,
    pub energy: i64,
    pub bots: Vec<Box<Bot>>,
    pub moved_bots: Vec<Box<Bot>>,
    pub deaths: i64,
    pub moves: i64,
    pub connections: i64,
    pub pull: i64,
    pub diffuse: i64,
}

impl Node {
    pub fn new(energy: i64, particle: zoom::BasicParticle<Vec3, f64>) -> Self {
        Node{
            energy: energy,
            particle: RadParticle{p: particle},
            bots: Vec::new(),
            moved_bots: Vec::new(),
            deaths: 0,
            moves: 0,
            connections: 0,
            pull: 0,
            diffuse: 0,
        }
    }

    pub fn diffuse(&mut self) {
        self.diffuse = self.connections * (self.energy as f64 * EDGE_DIFFUSION_COEFFICIENT) as i64;
        self.energy -= self.diffuse;
    }

    pub fn grow(&mut self, full: bool, rng: &mut rand::Isaac64Rng) {
        use rand::Rng;
        if full {
            if self.bots.is_empty() {
                self.energy = self.energy.saturating_sub((ENERGY_FULL_COST as f64 *
                    (1.0 + rng.gen_range(-ENERGY_VARIATION, ENERGY_VARIATION))) as i64);
            }
        } else {
            self.energy = self.energy.saturating_add((self.energy as f64 * ENERGY_RATIO *
                //Create rate differential
                (1.0 + rng.gen_range(-ENERGY_VARIATION, ENERGY_VARIATION) +
                    //Add food for having more connections
                    EDGE_FOOD_BENEFIT * self.connections as f64)
                ) as i64);
        }
    }

    pub fn advance(&mut self) {
        use zoom::{Particle, PhysicsParticle};
        self.particle.drag(DRAG);
        self.particle.advance(1.0);
    }

    pub fn should_split(&self) -> bool {
        self.energy >= ENERGY_THRESHOLD
    }
    pub fn should_obliterate(&self) -> bool {
        use std::num::FpCategory;
        use zoom::Position;
        let clos = |x: f64| {
            match x.classify() {
                FpCategory::Nan | FpCategory::Infinite => true,
                _ => false,
            }
        };
        self.energy <= 0 || match self.particle.position() {
            Vec3{x, y, z} => {clos(x) || clos(y) || clos(z)}
        }
    }

    pub fn color(&self) -> [f32; 4] {
        [
            1.0 - self.energy as f32 / ENERGY_THRESHOLD as f32,
            self.energy as f32 / ENERGY_THRESHOLD as f32,
            if self.bots.len() == 0 {
                0.0
            } else {
                self.moves as f32 / self.bots.len() as f32
            },
            1.0,
        ]
    }
    pub fn radius(&self) -> f32 {
        RADIUS_STATIC + BOTS_RADIUS_MULTIPLIER * (self.bots.len() as f32).sqrt()
    }
}
<commit_msg>return multiplier back<commit_after>extern crate zoom;
extern crate rand;

use super::bot::*;
use super::Vec3;

const BOTS_RADIUS_MULTIPLIER: f32 = 5.0;
const RADIUS_STATIC: f32 = 5.0;
const ENERGY_RATIO: f64 = 0.002;
const ENERGY_VARIATION: f64 = 0.1;
pub const ENERGY_THRESHOLD: i64 = 500000;
const ENERGY_FULL_COST: i64 = 5000;
const PHYSICS_RADIUS: f64 = 1.0;

const EDGE_FOOD_BENEFIT: f64 = 1.0;
const EDGE_DIFFUSION_COEFFICIENT: f64 = 0.01;

const DRAG: f64 = 0.1;

#[derive(Clone)]
pub struct RadParticle {
    pub p: zoom::BasicParticle<Vec3, f64>,
}

impl zoom::Position<Vec3> for RadParticle {
    fn position(&self) -> Vec3 {
        self.p.position()
    }
}

impl zoom::Velocity<Vec3> for RadParticle {
    fn velocity(&self) -> Vec3 {
        self.p.velocity()
    }
}

impl zoom::Particle<Vec3, f64> for RadParticle {
    fn impulse(&self, v: &Vec3) {
        self.p.impulse(v);
    }

    fn advance(&mut self, time: f64) {
        self.p.advance(time);
    }
}

impl zoom::Inertia<f64> for RadParticle {
    fn inertia(&self) -> f64 {
        self.p.inertia()
    }
}

impl zoom::Quanta<f64> for RadParticle {
    fn quanta(&self) -> f64 {
        self.p.quanta()
    }
}

impl zoom::PhysicsParticle<Vec3, f64> for RadParticle {
}

impl zoom::Ball<f64> for RadParticle {
    fn radius(&self) -> f64 {
        PHYSICS_RADIUS
    }
}

pub struct Node {
    pub particle: RadParticle,
    pub energy: i64,
    pub bots: Vec<Box<Bot>>,
    pub moved_bots: Vec<Box<Bot>>,
    pub deaths: i64,
    pub moves: i64,
    pub connections: i64,
    pub pull: i64,
    pub diffuse: i64,
}

impl Node {
    pub fn new(energy: i64, particle: zoom::BasicParticle<Vec3, f64>) -> Self {
        Node{
            energy: energy,
            particle: RadParticle{p: particle},
            bots: Vec::new(),
            moved_bots: Vec::new(),
            deaths: 0,
            moves: 0,
            connections: 0,
            pull: 0,
            diffuse: 0,
        }
    }

    pub fn diffuse(&mut self) {
        self.diffuse = self.connections * (self.energy as f64 * EDGE_DIFFUSION_COEFFICIENT) as i64;
        self.energy -= self.diffuse;
    }

    pub fn grow(&mut self, full: bool, rng: &mut rand::Isaac64Rng) {
        use rand::Rng;
        if full {
            if self.bots.is_empty() {
                self.energy = self.energy.saturating_sub((ENERGY_FULL_COST as f64 *
                    (1.0 + rng.gen_range(-ENERGY_VARIATION, ENERGY_VARIATION))) as i64);
            }
        } else {
            self.energy = self.energy.saturating_add((self.energy as f64 * ENERGY_RATIO *
                //Create rate differential
                (1.0 + rng.gen_range(-ENERGY_VARIATION, ENERGY_VARIATION) +
                    //Add food for having more connections
                    EDGE_FOOD_BENEFIT * self.connections as f64)
                ) as i64);
        }
    }

    pub fn advance(&mut self) {
        use zoom::{Particle, PhysicsParticle};
        self.particle.drag(DRAG);
        self.particle.advance(1.0);
    }

    pub fn should_split(&self) -> bool {
        self.energy >= ENERGY_THRESHOLD
    }
    pub fn should_obliterate(&self) -> bool {
        use std::num::FpCategory;
        use zoom::Position;
        let clos = |x: f64| {
            match x.classify() {
                FpCategory::Nan | FpCategory::Infinite => true,
                _ => false,
            }
        };
        self.energy <= 0 || match self.particle.position() {
            Vec3{x, y, z} => {clos(x) || clos(y) || clos(z)}
        }
    }

    pub fn color(&self) -> [f32; 4] {
        [
            1.0 - self.energy as f32 / ENERGY_THRESHOLD as f32,
            self.energy as f32 / ENERGY_THRESHOLD as f32,
            if self.bots.len() == 0 {
                0.0
            } else {
                self.moves as f32 / self.bots.len() as f32
            },
            1.0,
        ]
    }
    pub fn radius(&self) -> f32 {
        RADIUS_STATIC + BOTS_RADIUS_MULTIPLIER * (self.bots.len() as f32).sqrt()
    }
}
<|endoftext|>


<commit_before>//! Option parsing and management.
//!
//! Use the `Options::parse()` function to get the program's configuration,
//! as parsed from the commandline.
//!
//! # Examples
//!
//! ```no_run
//! # use cargo_update::Options;
//! let opts = Options::parse();
//! println!("{:#?}", opts);
//! ```

use self::super::ops::{PackageFilterElement, ConfigOperation};
use semver::{VersionReq as SemverReq, Version as Semver};
use clap::{self, AppSettings, SubCommand, App, Arg};
use array_tool::vec::Uniq;
use std::path::PathBuf;
use std::str::FromStr;
use dirs::home_dir;
use std::env;
use std::fs;

/// Representation of the application's all configurable values.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Options {
    /// (Additional) packages to update. Default: `[]`
    pub to_update: Vec<(String, Option<Semver>)>,
    /// Whether to update all packages. Default: `false`
    pub all: bool,
    /// Whether to update packages or just list them. Default: `true`
    pub update: bool,
    /// Whether to allow for just installing packages. Default: `false`
    pub install: bool,
    /// Update all packages. Default: `false`
    pub force: bool,
    /// Update git packages too (it's expensive). Default: `false`
    pub update_git: bool,
    /// Update all packages. Default: empty
    pub filter: Vec<PackageFilterElement>,
    /// The `.crates.toml` file in the `cargo` home directory.
    /// Default: in `"$CARGO_INSTALL_ROOT"`, then `"$CARGO_HOME"`, then `"$HOME/.cargo"`
    pub crates_file: (String, PathBuf),
    /// The `cargo` home directory. Default: `"$CARGO_HOME"`, then `"$HOME/.cargo"`
    pub cargo_dir: (String, PathBuf),
    /// The temporary directory to clone git repositories to. Default: `"$TEMP/cargo-update"`
    pub temp_dir: (String, PathBuf),
}

/// Representation of the config application's all configurable values.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ConfigOptions {
    /// The `config` file in the `cargo` home directory.
    /// Default: in `"$CARGO_INSTALL_ROOT"`, then `"$CARGO_HOME"`, then `"$HOME/.cargo"`
    pub crates_file: (String, PathBuf),
    /// Crate to modify config for
    pub package: String,
    /// What to do to the config, or display with empty
    pub ops: Vec<ConfigOperation>,
}

impl Options {
    /// Parse `env`-wide command-line arguments into an `Options` instance
    pub fn parse() -> Options {
        let matches = App::new("cargo-install-update")
            .bin_name("cargo")
            .settings(&[AppSettings::ColoredHelp, AppSettings::ArgRequiredElseHelp, AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
            .subcommand(SubCommand::with_name("install-update")
                .version(crate_version!())
                .author(crate_authors!("\n"))
                .about("A cargo subcommand for checking and applying updates to installed executables")
                .args(&[Arg::from_usage("-c --cargo-dir=[CARGO_DIR] 'The cargo home directory. Default: $CARGO_HOME or $HOME/.cargo'")
                            .visible_alias("root")
                            .validator(|s| existing_dir_validator("Cargo", &s)),
                        Arg::from_usage("-t --temp-dir=[TEMP_DIR] 'The temporary directory. Default: $TEMP/cargo-update'")
                            .validator(|s| existing_dir_validator("Temporary", &s)),
                        Arg::from_usage("-a --all 'Update all packages'"),
                        Arg::from_usage("-l --list 'Don't update packages, only list and check if they need an update'"),
                        Arg::from_usage("-f --force 'Update all packages regardless if they need updating'"),
                        Arg::from_usage("-i --allow-no-update 'Allow for fresh-installing packages'"),
                        Arg::from_usage("-g --git 'Also update git packages'"),
                        Arg::from_usage("-s --filter=[PACKAGE_FILTER]... 'Specify a filter a package must match to be considered'")
                            .validator(|s| PackageFilterElement::parse(&s).map(|_| ())),
                        Arg::from_usage("<PACKAGE>... 'Packages to update'")
                            .empty_values(false)
                            .min_values(1)
                            .validator(|s| package_parse(s).map(|_| ()))]))
            .get_matches();
        let matches = matches.subcommand_matches("install-update").unwrap();

        let all = matches.is_present("all");
        let cdir = cargo_dir();
        Options {
            to_update: match (all, matches.values_of("PACKAGE")) {
                (_, Some(pkgs)) => {
                    let packages: Vec<_> = pkgs.map(String::from).map(package_parse).map(Result::unwrap).collect();
                    packages.unique_via(|l, r| l.0 == r.0)
                }
                (true, None) => vec![],
                (false, None) => {
                    clap::Error {
                            message: format!("Need at least one PACKAGE without --all"),
                            kind: clap::ErrorKind::MissingRequiredArgument,
                            info: None,
                        }
                        .exit()
                }
            },
            all: all,
            update: !matches.is_present("list"),
            install: matches.is_present("allow-no-update"),
            force: matches.is_present("force"),
            update_git: matches.is_present("git"),
            filter: matches.values_of("filter").map(|pfs| pfs.flat_map(PackageFilterElement::parse).collect()).unwrap_or_else(|| vec![]),
            crates_file: match matches.value_of("cargo-dir") {
                Some(dir) => (format!("{}/.crates.toml", dir), fs::canonicalize(dir).unwrap().join(".crates.toml")),
                None => {
                    match env::var("CARGO_INSTALL_ROOT").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
                        Ok(ch) => ("$CARGO_INSTALL_ROOT/.crates.toml".to_string(), ch.join(".crates.toml")),
                        Err(()) => (format!("{}/.crates.toml", cdir.0), cdir.1.join(".crates.toml")),
                    }
                }
            },
            cargo_dir: cdir,
            temp_dir: {
                let (temp_s, temp_pb) = if let Some(tmpdir) = matches.value_of("temp-dir") {
                    (tmpdir.to_string(), fs::canonicalize(tmpdir).unwrap())
                } else {
                    ("$TEMP".to_string(), env::temp_dir())
                };

                (format!("{}{}cargo-update",
                         temp_s,
                         if temp_s.ends_with('/') || temp_s.ends_with('\\') {
                             ""
                         } else {
                             "/"
                         }),
                 temp_pb.join("cargo-update"))
            },
        }
    }
}

impl ConfigOptions {
    /// Parse `env`-wide command-line arguments into a `ConfigOptions` instance
    pub fn parse() -> ConfigOptions {
        let matches = App::new("cargo-install-update-config")
            .bin_name("cargo")
            .settings(&[AppSettings::ColoredHelp, AppSettings::ArgRequiredElseHelp, AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
            .subcommand(SubCommand::with_name("install-update-config")
                .version(crate_version!())
                .author(crate_authors!("\n"))
                .about("A cargo subcommand for checking and applying updates to installed executables -- configuration")
                .args(&[Arg::from_usage("-c --cargo-dir=[CARGO_DIR] 'The cargo home directory. Default: $CARGO_HOME or $HOME/.cargo'")
                            .validator(|s| existing_dir_validator("Cargo", &s)),
                        Arg::from_usage("-t --toolchain=[TOOLCHAIN] 'Toolchain to use or empty for default'"),
                        Arg::from_usage("-f --feature=[FEATURE]... 'Feature to enable'"),
                        Arg::from_usage("-n --no-feature=[DISABLED_FEATURE]... 'Feature to disable'"),
                        Arg::from_usage("-d --default-features=[DEFAULT_FEATURES] 'Whether to allow default features'")
                            .possible_values(&["1", "yes", "true", "0", "no", "false"])
                            .hide_possible_values(true),
                        Arg::from_usage("--debug 'Compile the package in debug mode'").conflicts_with("release"),
                        Arg::from_usage("--release 'Compile the package in release mode'").conflicts_with("debug"),
                        Arg::from_usage("-v --version=[VERSION_REQ] 'Require a cargo-compatible version range'")
                            .validator(|s| SemverReq::from_str(&s).map(|_| ()).map_err(|e| e.to_string()))
                            .conflicts_with("any-version"),
                        Arg::from_usage("-a --any-version 'Allow any version'").conflicts_with("version"),
                        Arg::from_usage("<PACKAGE> 'Package to configure'").empty_values(false)]))
            .get_matches();
        let matches = matches.subcommand_matches("install-update-config").unwrap();

        let cdir = cargo_dir();
        ConfigOptions {
            crates_file: match matches.value_of("cargo-dir") {
                Some(dir) => (format!("{}/.crates.toml", dir), fs::canonicalize(dir).unwrap().join(".crates.toml")),
                None => {
                    match env::var("CARGO_INSTALL_ROOT").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
                        Ok(ch) => ("$CARGO_INSTALL_ROOT/.crates.toml".to_string(), ch.join(".crates.toml")),
                        Err(()) => (format!("{}/.crates.toml", cdir.0), cdir.1.join(".crates.toml")),
                    }
                }
            },
            package: matches.value_of("PACKAGE").unwrap().to_string(),
            ops: matches.value_of("toolchain")
                .map(|t| if t.is_empty() {
                    ConfigOperation::RemoveToolchain
                } else {
                    ConfigOperation::SetToolchain(t.to_string())
                })
                .into_iter()
                .chain(matches.values_of("feature").into_iter().flat_map(|f| f).map(str::to_string).map(ConfigOperation::AddFeature))
                .chain(matches.values_of("no-feature").into_iter().flat_map(|f| f).map(str::to_string).map(ConfigOperation::RemoveFeature))
                .chain(matches.value_of("default-features").map(|d| ["1", "yes", "true"].contains(&d)).map(ConfigOperation::DefaultFeatures).into_iter())
                .chain(match (matches.is_present("debug"), matches.is_present("release")) {
                        (true, _) => Some(ConfigOperation::SetDebugMode(true)),
                        (_, true) => Some(ConfigOperation::SetDebugMode(false)),
                        _ => None,
                    }
                    .into_iter())
                .chain(match (matches.is_present("any-version"), matches.value_of("version")) {
                        (true, _) => Some(ConfigOperation::RemoveTargetVersion),
                        (false, Some(vr)) => Some(ConfigOperation::SetTargetVersion(SemverReq::from_str(vr).unwrap())),
                        _ => None,
                    }
                    .into_iter())
                .collect(),
        }
    }
}

fn cargo_dir() -> (String, PathBuf) {
    match env::var("CARGO_HOME").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
        Ok(ch) => ("$CARGO_HOME".to_string(), ch),
        Err(()) =>
                match home_dir().and_then(|hd| hd.canonicalize().ok()) {
                    Some(mut hd) => {
                        hd.push(".cargo");

                        fs::create_dir_all(&hd).unwrap();
                        ("$HOME/.cargo".to_string(), hd)
                    }
                    None => {
                        clap::Error {
                                message: "$CARGO_HOME and home directory invalid, please specify the cargo home directory with the -c option".to_string(),
                                kind: clap::ErrorKind::MissingRequiredArgument,
                                info: None,
                            }
                            .exit()
                    }
                },
    }
}

fn existing_dir_validator(label: &str, s: &str) -> Result<(), String> {
    fs::canonicalize(s).map(|_| ()).map_err(|_| format!("{} directory \"{}\" not found", label, s))
}

fn package_parse(s: String) -> Result<(String, Option<Semver>), String> {
    if let Some(idx) = s.find(':') {
        Ok((s[0..idx].to_string(),
            Some(try!(Semver::parse(&s[idx + 1..]).map_err(|e| format!("Version {} provided for package {} invalid: {}", &s[idx + 1..], &s[0..idx], e))))))
    } else {
        Ok((s, None))
    }
}
<commit_msg>Don't require [PACKAGE] arg<commit_after>//! Option parsing and management.
//!
//! Use the `Options::parse()` function to get the program's configuration,
//! as parsed from the commandline.
//!
//! # Examples
//!
//! ```no_run
//! # use cargo_update::Options;
//! let opts = Options::parse();
//! println!("{:#?}", opts);
//! ```

use self::super::ops::{PackageFilterElement, ConfigOperation};
use semver::{VersionReq as SemverReq, Version as Semver};
use clap::{self, AppSettings, SubCommand, App, Arg};
use array_tool::vec::Uniq;
use std::path::PathBuf;
use std::str::FromStr;
use dirs::home_dir;
use std::env;
use std::fs;

/// Representation of the application's all configurable values.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Options {
    /// (Additional) packages to update. Default: `[]`
    pub to_update: Vec<(String, Option<Semver>)>,
    /// Whether to update all packages. Default: `false`
    pub all: bool,
    /// Whether to update packages or just list them. Default: `true`
    pub update: bool,
    /// Whether to allow for just installing packages. Default: `false`
    pub install: bool,
    /// Update all packages. Default: `false`
    pub force: bool,
    /// Update git packages too (it's expensive). Default: `false`
    pub update_git: bool,
    /// Update all packages. Default: empty
    pub filter: Vec<PackageFilterElement>,
    /// The `.crates.toml` file in the `cargo` home directory.
    /// Default: in `"$CARGO_INSTALL_ROOT"`, then `"$CARGO_HOME"`, then `"$HOME/.cargo"`
    pub crates_file: (String, PathBuf),
    /// The `cargo` home directory. Default: `"$CARGO_HOME"`, then `"$HOME/.cargo"`
    pub cargo_dir: (String, PathBuf),
    /// The temporary directory to clone git repositories to. Default: `"$TEMP/cargo-update"`
    pub temp_dir: (String, PathBuf),
}

/// Representation of the config application's all configurable values.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ConfigOptions {
    /// The `config` file in the `cargo` home directory.
    /// Default: in `"$CARGO_INSTALL_ROOT"`, then `"$CARGO_HOME"`, then `"$HOME/.cargo"`
    pub crates_file: (String, PathBuf),
    /// Crate to modify config for
    pub package: String,
    /// What to do to the config, or display with empty
    pub ops: Vec<ConfigOperation>,
}

impl Options {
    /// Parse `env`-wide command-line arguments into an `Options` instance
    pub fn parse() -> Options {
        let matches = App::new("cargo-install-update")
            .bin_name("cargo")
            .settings(&[AppSettings::ColoredHelp, AppSettings::ArgRequiredElseHelp, AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
            .subcommand(SubCommand::with_name("install-update")
                .version(crate_version!())
                .author(crate_authors!("\n"))
                .about("A cargo subcommand for checking and applying updates to installed executables")
                .args(&[Arg::from_usage("-c --cargo-dir=[CARGO_DIR] 'The cargo home directory. Default: $CARGO_HOME or $HOME/.cargo'")
                            .visible_alias("root")
                            .validator(|s| existing_dir_validator("Cargo", &s)),
                        Arg::from_usage("-t --temp-dir=[TEMP_DIR] 'The temporary directory. Default: $TEMP/cargo-update'")
                            .validator(|s| existing_dir_validator("Temporary", &s)),
                        Arg::from_usage("-a --all 'Update all packages'"),
                        Arg::from_usage("-l --list 'Don't update packages, only list and check if they need an update'"),
                        Arg::from_usage("-f --force 'Update all packages regardless if they need updating'"),
                        Arg::from_usage("-i --allow-no-update 'Allow for fresh-installing packages'"),
                        Arg::from_usage("-g --git 'Also update git packages'"),
                        Arg::from_usage("-s --filter=[PACKAGE_FILTER]... 'Specify a filter a package must match to be considered'")
                            .validator(|s| PackageFilterElement::parse(&s).map(|_| ())),
                        Arg::from_usage("[PACKAGE]... 'Packages to update'")
                            .empty_values(false)
                            .min_values(1)
                            .validator(|s| package_parse(s).map(|_| ()))]))
            .get_matches();
        let matches = matches.subcommand_matches("install-update").unwrap();

        let all = matches.is_present("all");
        let cdir = cargo_dir();
        Options {
            to_update: match (all, matches.values_of("PACKAGE")) {
                (_, Some(pkgs)) => {
                    let packages: Vec<_> = pkgs.map(String::from).map(package_parse).map(Result::unwrap).collect();
                    packages.unique_via(|l, r| l.0 == r.0)
                }
                (true, None) => vec![],
                (false, None) => {
                    clap::Error {
                            message: format!("Need at least one PACKAGE without --all"),
                            kind: clap::ErrorKind::MissingRequiredArgument,
                            info: None,
                        }
                        .exit()
                }
            },
            all: all,
            update: !matches.is_present("list"),
            install: matches.is_present("allow-no-update"),
            force: matches.is_present("force"),
            update_git: matches.is_present("git"),
            filter: matches.values_of("filter").map(|pfs| pfs.flat_map(PackageFilterElement::parse).collect()).unwrap_or_else(|| vec![]),
            crates_file: match matches.value_of("cargo-dir") {
                Some(dir) => (format!("{}/.crates.toml", dir), fs::canonicalize(dir).unwrap().join(".crates.toml")),
                None => {
                    match env::var("CARGO_INSTALL_ROOT").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
                        Ok(ch) => ("$CARGO_INSTALL_ROOT/.crates.toml".to_string(), ch.join(".crates.toml")),
                        Err(()) => (format!("{}/.crates.toml", cdir.0), cdir.1.join(".crates.toml")),
                    }
                }
            },
            cargo_dir: cdir,
            temp_dir: {
                let (temp_s, temp_pb) = if let Some(tmpdir) = matches.value_of("temp-dir") {
                    (tmpdir.to_string(), fs::canonicalize(tmpdir).unwrap())
                } else {
                    ("$TEMP".to_string(), env::temp_dir())
                };

                (format!("{}{}cargo-update",
                         temp_s,
                         if temp_s.ends_with('/') || temp_s.ends_with('\\') {
                             ""
                         } else {
                             "/"
                         }),
                 temp_pb.join("cargo-update"))
            },
        }
    }
}

impl ConfigOptions {
    /// Parse `env`-wide command-line arguments into a `ConfigOptions` instance
    pub fn parse() -> ConfigOptions {
        let matches = App::new("cargo-install-update-config")
            .bin_name("cargo")
            .settings(&[AppSettings::ColoredHelp, AppSettings::ArgRequiredElseHelp, AppSettings::GlobalVersion, AppSettings::SubcommandRequired])
            .subcommand(SubCommand::with_name("install-update-config")
                .version(crate_version!())
                .author(crate_authors!("\n"))
                .about("A cargo subcommand for checking and applying updates to installed executables -- configuration")
                .args(&[Arg::from_usage("-c --cargo-dir=[CARGO_DIR] 'The cargo home directory. Default: $CARGO_HOME or $HOME/.cargo'")
                            .validator(|s| existing_dir_validator("Cargo", &s)),
                        Arg::from_usage("-t --toolchain=[TOOLCHAIN] 'Toolchain to use or empty for default'"),
                        Arg::from_usage("-f --feature=[FEATURE]... 'Feature to enable'"),
                        Arg::from_usage("-n --no-feature=[DISABLED_FEATURE]... 'Feature to disable'"),
                        Arg::from_usage("-d --default-features=[DEFAULT_FEATURES] 'Whether to allow default features'")
                            .possible_values(&["1", "yes", "true", "0", "no", "false"])
                            .hide_possible_values(true),
                        Arg::from_usage("--debug 'Compile the package in debug mode'").conflicts_with("release"),
                        Arg::from_usage("--release 'Compile the package in release mode'").conflicts_with("debug"),
                        Arg::from_usage("-v --version=[VERSION_REQ] 'Require a cargo-compatible version range'")
                            .validator(|s| SemverReq::from_str(&s).map(|_| ()).map_err(|e| e.to_string()))
                            .conflicts_with("any-version"),
                        Arg::from_usage("-a --any-version 'Allow any version'").conflicts_with("version"),
                        Arg::from_usage("<PACKAGE> 'Package to configure'").empty_values(false)]))
            .get_matches();
        let matches = matches.subcommand_matches("install-update-config").unwrap();

        let cdir = cargo_dir();
        ConfigOptions {
            crates_file: match matches.value_of("cargo-dir") {
                Some(dir) => (format!("{}/.crates.toml", dir), fs::canonicalize(dir).unwrap().join(".crates.toml")),
                None => {
                    match env::var("CARGO_INSTALL_ROOT").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
                        Ok(ch) => ("$CARGO_INSTALL_ROOT/.crates.toml".to_string(), ch.join(".crates.toml")),
                        Err(()) => (format!("{}/.crates.toml", cdir.0), cdir.1.join(".crates.toml")),
                    }
                }
            },
            package: matches.value_of("PACKAGE").unwrap().to_string(),
            ops: matches.value_of("toolchain")
                .map(|t| if t.is_empty() {
                    ConfigOperation::RemoveToolchain
                } else {
                    ConfigOperation::SetToolchain(t.to_string())
                })
                .into_iter()
                .chain(matches.values_of("feature").into_iter().flat_map(|f| f).map(str::to_string).map(ConfigOperation::AddFeature))
                .chain(matches.values_of("no-feature").into_iter().flat_map(|f| f).map(str::to_string).map(ConfigOperation::RemoveFeature))
                .chain(matches.value_of("default-features").map(|d| ["1", "yes", "true"].contains(&d)).map(ConfigOperation::DefaultFeatures).into_iter())
                .chain(match (matches.is_present("debug"), matches.is_present("release")) {
                        (true, _) => Some(ConfigOperation::SetDebugMode(true)),
                        (_, true) => Some(ConfigOperation::SetDebugMode(false)),
                        _ => None,
                    }
                    .into_iter())
                .chain(match (matches.is_present("any-version"), matches.value_of("version")) {
                        (true, _) => Some(ConfigOperation::RemoveTargetVersion),
                        (false, Some(vr)) => Some(ConfigOperation::SetTargetVersion(SemverReq::from_str(vr).unwrap())),
                        _ => None,
                    }
                    .into_iter())
                .collect(),
        }
    }
}

fn cargo_dir() -> (String, PathBuf) {
    match env::var("CARGO_HOME").map_err(|_| ()).and_then(|ch| fs::canonicalize(ch).map_err(|_| ())) {
        Ok(ch) => ("$CARGO_HOME".to_string(), ch),
        Err(()) =>
                match home_dir().and_then(|hd| hd.canonicalize().ok()) {
                    Some(mut hd) => {
                        hd.push(".cargo");

                        fs::create_dir_all(&hd).unwrap();
                        ("$HOME/.cargo".to_string(), hd)
                    }
                    None => {
                        clap::Error {
                                message: "$CARGO_HOME and home directory invalid, please specify the cargo home directory with the -c option".to_string(),
                                kind: clap::ErrorKind::MissingRequiredArgument,
                                info: None,
                            }
                            .exit()
                    }
                },
    }
}

fn existing_dir_validator(label: &str, s: &str) -> Result<(), String> {
    fs::canonicalize(s).map(|_| ()).map_err(|_| format!("{} directory \"{}\" not found", label, s))
}

fn package_parse(s: String) -> Result<(String, Option<Semver>), String> {
    if let Some(idx) = s.find(':') {
        Ok((s[0..idx].to_string(),
            Some(try!(Semver::parse(&s[idx + 1..]).map_err(|e| format!("Version {} provided for package {} invalid: {}", &s[idx + 1..], &s[0..idx], e))))))
    } else {
        Ok((s, None))
    }
}
<|endoftext|>


<commit_before>use msg::Msg;
use options::Options;
use result::ZmqResult;
use socket_base::{OnConnected, SocketMessage};

use std::collections::HashMap;
use std::comm::Select;
use std::sync::{RWLock, Arc};

struct Peer {
    sender: Sender<Box<Msg>>,
    receiver: Receiver<Box<Msg>>,
}

impl Peer {
    fn new(sender: Sender<Box<Msg>>, receiver: Receiver<Box<Msg>>) -> Peer {
        Peer {
            sender: sender,
            receiver: receiver,
        }
    }
}

pub struct PeerManager {
    pub options: Arc<RWLock<Options>>,
    pub tx: Sender<ZmqResult<SocketMessage>>,
    rx: Receiver<ZmqResult<SocketMessage>>,
    pub peers: HashMap<uint, Peer>,
    ids: Vec<uint>,
}

impl PeerManager {
    pub fn new() -> PeerManager {
        let (tx, rx) = channel();
        PeerManager {
            options: Arc::new(RWLock::new(Options::new())),
            tx: tx,
            rx: rx,
            peers: HashMap::new(),
            ids: Vec::new(),
        }
    }

    pub fn recv_first(&mut self) -> (uint, Box<Msg>) {
        loop {
            self.sync_until(|pm| { pm.peers.len() > 0 });
            let to_remove = {
                let selector = Select::new();
                let mut mapping = HashMap::new();
                let mut index = 0;
                for id in self.ids.iter() {
                    let peer = self.peers.get(id);
                    let handle = box selector.handle(&peer.receiver);
                    let hid = handle.id();
                    mapping.insert(hid, (Some(handle), index));
                    let handle = mapping.get_mut(&hid).mut0().get_mut_ref();
                    unsafe {
                        handle.add();
                    }
                    index += 1;
                }
                let handle = box selector.handle(&self.rx);
                let hid = handle.id();
                mapping.insert(hid, (None, 0));
                let hid = selector.wait();
                match mapping.pop(&hid) {
                    Some((None, _)) => continue,
                    Some((Some(mut handle), index)) => {
                        match handle.recv_opt() {
                            Ok(msg) => return (*self.ids.get(index), msg),
                            _ => index,
                        }
                    },
                    None => fail!(),
                }
            };
            match self.ids.remove(to_remove) {
                Some(id) => {
                    self.peers.remove(&id);
                },
                None => (),
            }
        }
    }

    pub fn recv_from(&mut self, id: uint) -> Box<Msg> {
        self.sync_until(|pm| { pm.peers.contains_key(&id) });
        self.peers.get(&id).receiver.recv()
    }

    pub fn send_to(&mut self, id: uint, msg: Box<Msg>) {
        debug!("Sending {} to {}", msg, id);
        self.sync_until(|pm| { pm.peers.contains_key(&id) });
        self.peers.get(&id).sender.send(msg);
    }

    pub fn round_robin(&mut self, mut index: uint) -> (uint, uint) {
        self.sync_until(|pm| { pm.ids.len() > 0 });
        index = (index + 1) % self.ids.len();
        (index, *self.ids.get(index))
    }

    fn sync_until(&mut self, cond: |&PeerManager| -> bool) {
        loop {
            if !cond(self) {
                debug!("Condition not met, wait... peers: {}", self.peers.len());
                match self.rx.recv_opt() {
                    Ok(msg) => self.handle_msg(msg),
                    Err(_) => fail!(),
                }
                continue;
            }
            match self.rx.try_recv() {
                Ok(msg) => self.handle_msg(msg),
                Err(_) => break,
            }
        }
    }

    fn handle_msg(&mut self, msg: ZmqResult<SocketMessage>) {
        match msg {
            Ok(OnConnected(tx, rx)) => {
                let id = self.ids.last().unwrap_or(&0) + 1;
                debug!("New peer: {}", id);
                self.ids.push(id);
                self.peers.insert(id, Peer::new(tx, rx));
            }
            _ => (),
        }
    }
}
<commit_msg>Fix test - no wonder why the popped handle was not removed on destroy before destroying the Select object.<commit_after>use msg::Msg;
use options::Options;
use result::ZmqResult;
use socket_base::{OnConnected, SocketMessage};

use std::collections::HashMap;
use std::comm::Select;
use std::sync::{RWLock, Arc};

struct Peer {
    sender: Sender<Box<Msg>>,
    receiver: Receiver<Box<Msg>>,
}

impl Peer {
    fn new(sender: Sender<Box<Msg>>, receiver: Receiver<Box<Msg>>) -> Peer {
        Peer {
            sender: sender,
            receiver: receiver,
        }
    }
}

pub struct PeerManager {
    pub options: Arc<RWLock<Options>>,
    pub tx: Sender<ZmqResult<SocketMessage>>,
    rx: Receiver<ZmqResult<SocketMessage>>,
    pub peers: HashMap<uint, Peer>,
    ids: Vec<uint>,
}

impl PeerManager {
    pub fn new() -> PeerManager {
        let (tx, rx) = channel();
        PeerManager {
            options: Arc::new(RWLock::new(Options::new())),
            tx: tx,
            rx: rx,
            peers: HashMap::new(),
            ids: Vec::new(),
        }
    }

    pub fn recv_first(&mut self) -> (uint, Box<Msg>) {
        loop {
            self.sync_until(|pm| { pm.peers.len() > 0 });
            let to_remove = {
                let selector = Select::new();
                let mut mapping = HashMap::new();
                let mut index = 0;
                for id in self.ids.iter() {
                    let peer = self.peers.get(id);
                    let handle = box selector.handle(&peer.receiver);
                    let hid = handle.id();
                    mapping.insert(hid, (Some(handle), index));
                    let handle = mapping.get_mut(&hid).mut0().get_mut_ref();
                    unsafe {
                        handle.add();
                    }
                    index += 1;
                }
                let handle = box selector.handle(&self.rx);
                let hid = handle.id();
                mapping.insert(hid, (None, 0));
                let hid = selector.wait();
                match mapping.pop(&hid) {
                    Some((None, _)) => continue,
                    Some((Some(mut handle), index)) => {
                        match handle.recv_opt() {
                            Ok(msg) => {
                                unsafe {
                                    handle.remove();
                                }
                                return (*self.ids.get(index), msg);
                            }
                            _ => {
                                unsafe {
                                    handle.remove();
                                }
                                index
                            }
                        }
                    },
                    None => fail!(),
                }
            };
            match self.ids.remove(to_remove) {
                Some(id) => {
                    self.peers.remove(&id);
                },
                None => (),
            }
        }
    }

    pub fn recv_from(&mut self, id: uint) -> Box<Msg> {
        self.sync_until(|pm| { pm.peers.contains_key(&id) });
        self.peers.get(&id).receiver.recv()
    }

    pub fn send_to(&mut self, id: uint, msg: Box<Msg>) {
        debug!("Sending {} to {}", msg, id);
        self.sync_until(|pm| { pm.peers.contains_key(&id) });
        self.peers.get(&id).sender.send(msg);
    }

    pub fn round_robin(&mut self, mut index: uint) -> (uint, uint) {
        self.sync_until(|pm| { pm.ids.len() > 0 });
        index = (index + 1) % self.ids.len();
        (index, *self.ids.get(index))
    }

    fn sync_until(&mut self, cond: |&PeerManager| -> bool) {
        loop {
            if !cond(self) {
                debug!("Condition not met, wait... peers: {}", self.peers.len());
                match self.rx.recv_opt() {
                    Ok(msg) => self.handle_msg(msg),
                    Err(_) => fail!(),
                }
                continue;
            }
            match self.rx.try_recv() {
                Ok(msg) => self.handle_msg(msg),
                Err(_) => break,
            }
        }
    }

    fn handle_msg(&mut self, msg: ZmqResult<SocketMessage>) {
        match msg {
            Ok(OnConnected(tx, rx)) => {
                let id = self.ids.last().unwrap_or(&0) + 1;
                debug!("New peer: {}", id);
                self.ids.push(id);
                self.peers.insert(id, Peer::new(tx, rx));
            }
            _ => (),
        }
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2018 The predicates-rs Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fmt;
use std::fs;
use std::io::{self, Read};
use std::path;

use crate::reflection;
use crate::Predicate;

fn read_file(path: &path::Path) -> io::Result<Vec<u8>> {
    let mut buffer = Vec::new();
    fs::File::open(path)?.read_to_end(&mut buffer)?;
    Ok(buffer)
}

/// Predicate adapter that converts a `path` predicate to a byte predicate on its content.
///
/// This is created by `pred.from_path()`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileContentPredicate

where
    P: Predicate<[u8]>,
{
    p: P,
}

impl
 FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn eval(&self, path: &path::Path) -> io::Result<bool> {
        let buffer = read_file(path)?;
        Ok(self.p.eval(&buffer))
    }
}

impl
 reflection::PredicateReflection for FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
        let params = vec![reflection::Child::new("predicate", &self.p)];
        Box::new(params.into_iter())
    }
}

impl
 fmt::Display for FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.p.fmt(f)
    }
}

impl
 Predicate<path::Path> for FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn eval(&self, path: &path::Path) -> bool {
        self.eval(path).unwrap_or(false)
    }

    fn find_case<'a>(
        &'a self,
        expected: bool,
        variable: &path::Path,
    ) -> Option<reflection::Case<'a>> {
        let buffer = read_file(variable);
        match (expected, buffer) {
            (_, Ok(buffer)) => self
                .p
                .find_case(expected, &buffer)
                .map(|child| reflection::Case::new(Some(self), expected).add_child(child)),
            (true, Err(_)) => None,
            (false, Err(err)) => Some(
                reflection::Case::new(Some(self), false)
                    .add_product(reflection::Product::new("error", err)),
            ),
        }
    }
}

/// `Predicate` extension adapting a `slice` Predicate.
pub trait PredicateFileContentExt
where
    Self: Predicate<[u8]>,
    Self: Sized,
{
    /// Returns a `FileContentPredicate` that adapts `Self` to a file content `Predicate`.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    /// use std::path::Path;
    ///
    /// let predicate_fn = predicate::str::is_empty().not().from_utf8().from_file_path();
    /// assert_eq!(true, predicate_fn.eval(Path::new("./tests/hello_world")));
    /// assert_eq!(false, predicate_fn.eval(Path::new("./tests/empty_file")));
    /// ```
    #[allow(clippy::wrong_self_convention)]
    fn from_file_path(self) -> FileContentPredicate<Self> {
        FileContentPredicate { p: self }
    }
}

impl
 PredicateFileContentExt for P where P: Predicate<[u8]> {}
<commit_msg>fix: Remove redundant result<commit_after>// Copyright (c) 2018 The predicates-rs Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::fmt;
use std::fs;
use std::io::{self, Read};
use std::path;

use crate::reflection;
use crate::Predicate;

fn read_file(path: &path::Path) -> io::Result<Vec<u8>> {
    let mut buffer = Vec::new();
    fs::File::open(path)?.read_to_end(&mut buffer)?;
    Ok(buffer)
}

/// Predicate adapter that converts a `path` predicate to a byte predicate on its content.
///
/// This is created by `pred.from_path()`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileContentPredicate

where
    P: Predicate<[u8]>,
{
    p: P,
}

impl
 FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn eval(&self, path: &path::Path) -> io::Result<bool> {
        let buffer = read_file(path)?;
        Ok(self.p.eval(&buffer))
    }
}

impl
 reflection::PredicateReflection for FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn children<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Child<'a>> + 'a> {
        let params = vec![reflection::Child::new("predicate", &self.p)];
        Box::new(params.into_iter())
    }
}

impl
 fmt::Display for FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.p.fmt(f)
    }
}

impl
 Predicate<path::Path> for FileContentPredicate

where
    P: Predicate<[u8]>,
{
    fn eval(&self, path: &path::Path) -> bool {
        self.eval(path).unwrap_or(false)
    }

    fn find_case<'a>(
        &'a self,
        expected: bool,
        variable: &path::Path,
    ) -> Option<reflection::Case<'a>> {
        let buffer = read_file(variable);
        match (expected, buffer) {
            (_, Ok(buffer)) => self.p.find_case(expected, &buffer),
            (true, Err(_)) => None,
            (false, Err(err)) => Some(
                reflection::Case::new(Some(self), false)
                    .add_product(reflection::Product::new("error", err)),
            ),
        }
    }
}

/// `Predicate` extension adapting a `slice` Predicate.
pub trait PredicateFileContentExt
where
    Self: Predicate<[u8]>,
    Self: Sized,
{
    /// Returns a `FileContentPredicate` that adapts `Self` to a file content `Predicate`.
    ///
    /// # Examples
    ///
    /// ```
    /// use predicates::prelude::*;
    /// use std::path::Path;
    ///
    /// let predicate_fn = predicate::str::is_empty().not().from_utf8().from_file_path();
    /// assert_eq!(true, predicate_fn.eval(Path::new("./tests/hello_world")));
    /// assert_eq!(false, predicate_fn.eval(Path::new("./tests/empty_file")));
    /// ```
    #[allow(clippy::wrong_self_convention)]
    fn from_file_path(self) -> FileContentPredicate<Self> {
        FileContentPredicate { p: self }
    }
}

impl
 PredicateFileContentExt for P where P: Predicate<[u8]> {}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::os::unix::prelude::*;
#[cfg(unix)]
use std::ffi::OsString;
#[cfg(windows)]
use std::str;

pub enum RawPath {
    Path(PathBuf),
    Bytes(Vec<u8>),
}

impl RawPath {
    pub fn new(bytes: Vec<u8>) -> Self {
        Self::from_bytes(bytes)
    }

    pub fn as_path(&self) -> Option<&Path> {
        match *self {
            RawPath::Path(ref p) => Some(&p),
            RawPath::Bytes(ref b) => None,
        }
    }

    pub fn as_bytes(&self) -> &[u8] {
        match *self {
            RawPath::Path(ref p) => path2bytes(p),
            RawPath::Bytes(ref b) => b,
        }
    }

    #[cfg(windows)]
    fn from_bytes(bytes: Vec<u8>) -> Self {
        if let Ok(s) = str::from_utf8(&bytes) {
            RawPath::Path(PathBuf::from(s))
        } else {
            RawPath::Bytes(bytes)
        }
    }

    #[cfg(unix)]
    fn from_bytes(bytes: Vec<u8>) -> Self {
        RawPath::Path(PathBuf::from(OsString::from_vec(bytes)))
    }
}

#[cfg(windows)]
fn path2bytes(p: &Path) -> &[u8] {
    p.as_os_str().to_str().unwrap().as_bytes()
}

#[cfg(unix)]
fn path2bytes(p: &Path) -> &[u8] {
    p.as_os_str().as_bytes()
}
<commit_msg>Fixed windows build<commit_after>use std::path::{Path, PathBuf};
#[cfg(unix)]
use std::os::unix::prelude::*;
#[cfg(unix)]
use std::ffi::OsString;
#[cfg(windows)]
use std::str;

pub enum RawPath {
    Path(PathBuf),
    Bytes(Vec<u8>),
}

impl RawPath {
    pub fn new(bytes: Vec<u8>) -> Self {
        Self::from_bytes(bytes)
    }

    pub fn as_path(&self) -> Option<&Path> {
        match *self {
            RawPath::Path(ref p) => Some(&p),
            RawPath::Bytes(ref b) => None,
        }
    }

    pub fn as_bytes(&self) -> &[u8] {
        match *self {
            RawPath::Path(ref p) => path2bytes(p),
            RawPath::Bytes(ref b) => b,
        }
    }

    #[cfg(windows)]
    fn from_bytes(bytes: Vec<u8>) -> Self {
        if let Ok(s) = str::from_utf8(&bytes) {
            return RawPath::Path(PathBuf::from(s));
        }
        RawPath::Bytes(bytes)
    }

    #[cfg(unix)]
    fn from_bytes(bytes: Vec<u8>) -> Self {
        RawPath::Path(PathBuf::from(OsString::from_vec(bytes)))
    }
}

#[cfg(windows)]
fn path2bytes(p: &Path) -> &[u8] {
    p.as_os_str().to_str().unwrap().as_bytes()
}

#[cfg(unix)]
fn path2bytes(p: &Path) -> &[u8] {
    p.as_os_str().as_bytes()
}
<|endoftext|>


<commit_before>use crate::git;
use log::{debug, error, info, trace};
use regex::Regex;
use reqwest;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use std::io::{stdin, stdout, Write};

pub trait Remote {
    /// Get the ID of the project associated with the repository
    fn get_project_id(&mut self) -> Result<&str, &str>;

    /// Get the branch associated with the merge request having the given ID
    fn get_req_branch(&mut self, mr_id: i64) -> Result<String, &str>;
}

/// Print a pretty remote
impl fmt::Display for Remote {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Remote")
    }
}

/// Debug a remote
impl fmt::Debug for Remote {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

#[derive(Debug)]
struct GitHub {
    id: String,
    name: String,
    origin: String,
    api_root: String,
}

impl Remote for GitHub {
    fn get_project_id(&mut self) -> Result<&str, &str> {
        Ok(&self.id)
    }

    fn get_req_branch(&mut self, mr_id: i64) -> Result<String, &str> {
        Ok(format!("pr/{}", mr_id))
    }
}

#[derive(Debug)]
struct GitLab {
    id: String,
    domain: String,
    name: String,
    namespace: String,
    origin: String,
    api_root: String,
    api_key: String,
}

impl Remote for GitLab {
    fn get_project_id(&mut self) -> Result<&str, &str> {
        if self.id.is_empty() {
            self.id = format!("{}", query_gitlab_project_id(self)?);
        }
        Ok(&self.id)
    }

    fn get_req_branch(&mut self, mr_id: i64) -> Result<String, &str> {
        query_gitlab_branch_name(self, mr_id)
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct GitLabProject {
    id: i64,
    description: String,
    name: String,
    path: String,
    path_with_namespace: String,
}

fn query_gitlab_api(url: reqwest::Url, token: String) -> reqwest::Response {
    let client = reqwest::Client::new();
    client
        .get(url)
        .header("PRIVATE-TOKEN", token)
        .send()
        .expect("failed to send request")
}

/// Query the GitLab API for remote's project
fn query_gitlab_project_id(remote: &GitLab) -> Result<i64, &'static str> {
    trace!("Querying GitLab API for {:?}", remote);
    let url = reqwest::Url::parse(&format!(
        "{}/projects/{}%2F{}",
        remote.api_root, remote.namespace, remote.name
    ))
    .unwrap();
    let mut resp = query_gitlab_api(url, remote.api_key.to_string());
    debug!("Project ID query response: {:?}", resp);
    if !resp.status().is_success() {
        match search_gitlab_project_id(remote) {
            Ok(id) => {
                return Ok(id);
            },
            Err(_) => {
                return Err("Unable to get the project ID from the GitLab API.\nFind and configure \
                   your project ID using the instructions at: \
                   https://github.com/arusahni/git-req/wiki/Finding-Project-IDs");
            }
        }
    }
    let buf: GitLabProject = resp.json().expect("failed to read response");
    debug!("{:?}", buf);
    Ok(buf.id)
}

#[derive(Serialize, Deserialize, Debug)]
struct GitLabNamespace {
    id: i64,
    name: String,
    path: String,
    kind: String,
    full_path: String,
}

/// Search GitLab for the project ID (if the direct lookup didn't work)
fn search_gitlab_project_id(remote: &GitLab) -> Result<i64, &'static str> {
    trace!("Searching GitLab API for namespace {:?} by project name", remote.namespace);
    let url = reqwest::Url::parse(&format!("{}/namespaces/{}", remote.api_root, remote.namespace)).unwrap();
    let mut resp = query_gitlab_api(url, remote.api_key.to_string());
    debug!("Namespace ID query response: {:?}", resp);
    if !resp.status().is_success() {
        return Err("Couldn't find namespace");
    }
    let ns_buf: GitLabNamespace = resp.json().expect("failed to read response");
    debug!("Querying namespace {:?}", ns_buf);
    let url = match ns_buf.kind.as_ref() {
        "user" => reqwest::Url::parse(&format!("{}/users/{}/projects", remote.api_root, ns_buf.id)).unwrap(),
        "group" => reqwest::Url::parse(&format!("{}/groups/{}/projects?search={}", remote.api_root, ns_buf.id, remote.name)).unwrap(),
        _ => {
            error!("Unknown namespace kind {:?}", ns_buf.kind);
            return Err("Unknown namespace");
        }
    };
    let mut resp = query_gitlab_api(url, remote.api_key.to_string());
    debug!("Project ID query response: {:?}", resp);
    let projects: Vec<GitLabProject> = resp.json().expect("failed to read projects response");
    match projects.iter().find(|&prj| prj.name == remote.name) {
        Some(project) => Ok(project.id),
        None => Err("Couldn't find project"),
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct GitLabMergeRequest {
    id: i64,
    iid: i64,
    title: String,
    target_branch: String,
    source_branch: String,
    sha: String,
    web_url: String,
}

/// Get the project ID from config
fn load_project_id() -> Option<String> {
    match git::get_config("projectid") {
        Some(project_id) => Some(project_id),
        None => {
            debug!("No project ID found");
            None
        }
    }
}

/// Query the GitLab API for the branch corresponding to the MR
fn query_gitlab_branch_name(remote: &GitLab, mr_id: i64) -> Result<String, &str> {
    let client = reqwest::Client::new();
    let url = reqwest::Url::parse(&format!(
        "{}/projects/{}/merge_requests/{}",
        remote.api_root, remote.id, mr_id
    ))
    .unwrap();
    let mut resp = client
        .get(url)
        .header("PRIVATE-TOKEN", remote.api_key.to_string())
        .send()
        .expect("failed to send request");
    debug!("Response: {:?}", resp);
    let buf: GitLabMergeRequest = match resp.json() {
        Ok(buf) => buf,
        Err(_) => {
            return Err("failed to read response");
        }
    };
    Ok(buf.source_branch)
}

/// Extract the project name from a Github origin URL
fn get_github_project_name(origin: &str) -> String {
    trace!("Getting project name for: {}", origin);
    let project_regex = Regex::new(r".*:(.*/\S+)\.git\w*$").unwrap();
    let captures = project_regex.captures(origin).unwrap();
    String::from(&captures[1])
}

/// Extract the project name from a GitLab origin URL
fn get_gitlab_project_name(origin: &str) -> String {
    trace!("Getting project name for: {}", origin);
    let project_regex = Regex::new(r".*/(\S+)\.git$").unwrap();
    let captures = project_regex.captures(origin).unwrap();
    String::from(&captures[1])
}

/// Extract the project namespace from a GitLab origin URL
fn get_gitlab_project_namespace(origin: &str) -> Option<String> {
    trace!("Getting project namespace for: {}", origin);
    let project_regex = Regex::new(r".*[/:](\S+)/\S+\.git$").unwrap();
    match project_regex.captures(origin) {
        Some(captures) => Some(String::from(&captures[1])),
        None => None
    }
}

/// Get the domain from an origin URL
pub fn get_domain(origin: &str) -> Result<&str, String> {
    let domain_regex = Regex::new(r"((http[s]?|ssh)://)?(\S+@)?(?P<domain>([^:/])+)").unwrap();
    let captures = domain_regex.captures(origin);
    if captures.is_none() {
        return Err(String::from("invalid remote set"));
    }
    Ok(captures.unwrap().name("domain").map_or("", |x| x.as_str()))
}

/// Get a remote struct from an origin URL
pub fn get_remote(origin: &str) -> Result<Box<Remote>, String> {
    let domain = get_domain(origin)?;
    Ok(match domain {
        "github.com" => Box::new(GitHub {
            id: get_github_project_name(origin),
            name: get_github_project_name(origin),
            origin: String::from(origin),
            api_root: String::from("https://api.github.com/repos"),
        }),
        // For now, if not GitHub, then GitLab
        gitlab_domain => {
            let namespace = match get_gitlab_project_namespace(origin) {
                Some(ns) => ns,
                None => {
                    return Err(String::from("Could not parse the GitLab project namespace from the origin."));
                }
            };
            let mut remote = GitLab {
                id: String::from(""),
                domain: String::from(gitlab_domain),
                name: get_gitlab_project_name(origin),
                namespace,
                origin: String::from(origin),
                api_root: format!("https://{}/api/v4", gitlab_domain),
                api_key: String::from(""),
            };
            let apikey = match git::get_req_config(&domain, "apikey") {
                Some(key) => key,
                None => {
                    let mut newkey = String::new();
                    print!("Please enter the read-only API key for {}: ", gitlab_domain);
                    let _ = stdout().flush();
                    stdin()
                        .read_line(&mut newkey)
                        .expect("Did not input a correct key");
                    trace!("New Key: {}", &newkey);
                    git::set_req_config(&domain, "apikey", &newkey.trim());
                    String::from(newkey.trim())
                }
            };
            info!("API Key: {}", &apikey);
            remote.api_key = apikey;
            let project_id = match load_project_id() {
                Some(x) => x,
                None => {
                    let project_id_str = match remote.get_project_id() {
                        Ok(id_str) => Ok(id_str),
                        Err(e) => {
                            info!("Error getting project ID: {:?}", e);
                            Err(e)
                        }
                    }?;
                    git::set_config("projectid", project_id_str);
                    String::from(project_id_str)
                }
            };
            info!("Got project ID: {}", project_id);
            remote.id = project_id;
            Box::new(remote)
        }
    })
}

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

    #[test]
    fn test_get_gitlab_project_namespace_http() {
        let ns = get_gitlab_project_namespace("https://gitlab.com/my_namespace/my_project.git");
        assert!(ns.is_some());
        assert_eq!("my_namespace", ns.unwrap());
    }

    #[test]
    fn test_get_gitlab_project_namespace_git() {
        let ns = get_gitlab_project_namespace("git@gitlab.com:my_namespace/my_project.git");
        assert!(ns.is_some());
        assert_eq!("my_namespace", ns.unwrap());
    }

    #[test]
    fn test_get_gitlab_project_name_http() {
        let ns = get_gitlab_project_name("https://gitlab.com/my_namespace/my_project.git");
        assert_eq!("my_project", ns);
    }

    #[test]
    fn test_get_gitlab_project_name_git() {
        let ns = get_gitlab_project_name("git@gitlab.com:my_namespace/my_project.git");
        assert_eq!("my_project", ns);
    }
}
<commit_msg>Make project model's description field optional.<commit_after>use crate::git;
use log::{debug, error, info, trace};
use regex::Regex;
use reqwest;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use std::io::{stdin, stdout, Write};

pub trait Remote {
    /// Get the ID of the project associated with the repository
    fn get_project_id(&mut self) -> Result<&str, &str>;

    /// Get the branch associated with the merge request having the given ID
    fn get_req_branch(&mut self, mr_id: i64) -> Result<String, &str>;
}

/// Print a pretty remote
impl fmt::Display for Remote {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Remote")
    }
}

/// Debug a remote
impl fmt::Debug for Remote {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

#[derive(Debug)]
struct GitHub {
    id: String,
    name: String,
    origin: String,
    api_root: String,
}

impl Remote for GitHub {
    fn get_project_id(&mut self) -> Result<&str, &str> {
        Ok(&self.id)
    }

    fn get_req_branch(&mut self, mr_id: i64) -> Result<String, &str> {
        Ok(format!("pr/{}", mr_id))
    }
}

#[derive(Debug)]
struct GitLab {
    id: String,
    domain: String,
    name: String,
    namespace: String,
    origin: String,
    api_root: String,
    api_key: String,
}

impl Remote for GitLab {
    fn get_project_id(&mut self) -> Result<&str, &str> {
        if self.id.is_empty() {
            self.id = format!("{}", query_gitlab_project_id(self)?);
        }
        Ok(&self.id)
    }

    fn get_req_branch(&mut self, mr_id: i64) -> Result<String, &str> {
        query_gitlab_branch_name(self, mr_id)
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct GitLabProject {
    id: i64,
    description: Option<String>,
    name: String,
    path: String,
    path_with_namespace: String,
}

fn query_gitlab_api(url: reqwest::Url, token: String) -> reqwest::Response {
    let client = reqwest::Client::new();
    client
        .get(url)
        .header("PRIVATE-TOKEN", token)
        .send()
        .expect("failed to send request")
}

/// Query the GitLab API for remote's project
fn query_gitlab_project_id(remote: &GitLab) -> Result<i64, &'static str> {
    trace!("Querying GitLab API for {:?}", remote);
    let url = reqwest::Url::parse(&format!(
        "{}/projects/{}%2F{}",
        remote.api_root, remote.namespace, remote.name
    ))
    .unwrap();
    let mut resp = query_gitlab_api(url, remote.api_key.to_string());
    debug!("Project ID query response: {:?}", resp);
    if !resp.status().is_success() {
        match search_gitlab_project_id(remote) {
            Ok(id) => {
                return Ok(id);
            },
            Err(_) => {
                return Err("Unable to get the project ID from the GitLab API.\nFind and configure \
                   your project ID using the instructions at: \
                   https://github.com/arusahni/git-req/wiki/Finding-Project-IDs");
            }
        }
    }
    let buf: GitLabProject = resp.json().expect("failed to read response");
    debug!("{:?}", buf);
    Ok(buf.id)
}

#[derive(Serialize, Deserialize, Debug)]
struct GitLabNamespace {
    id: i64,
    name: String,
    path: String,
    kind: String,
    full_path: String,
}

/// Search GitLab for the project ID (if the direct lookup didn't work)
fn search_gitlab_project_id(remote: &GitLab) -> Result<i64, &'static str> {
    trace!("Searching GitLab API for namespace {:?} by project name", remote.namespace);
    let url = reqwest::Url::parse(&format!("{}/namespaces/{}", remote.api_root, remote.namespace)).unwrap();
    let mut resp = query_gitlab_api(url, remote.api_key.to_string());
    debug!("Namespace ID query response: {:?}", resp);
    if !resp.status().is_success() {
        return Err("Couldn't find namespace");
    }
    let ns_buf: GitLabNamespace = resp.json().expect("failed to read response");
    debug!("Querying namespace {:?}", ns_buf);
    let url = match ns_buf.kind.as_ref() {
        "user" => reqwest::Url::parse(&format!("{}/users/{}/projects", remote.api_root, ns_buf.id)).unwrap(),
        "group" => reqwest::Url::parse(&format!("{}/groups/{}/projects?search={}", remote.api_root, ns_buf.id, remote.name)).unwrap(),
        _ => {
            error!("Unknown namespace kind {:?}", ns_buf.kind);
            return Err("Unknown namespace");
        }
    };
    let mut resp = query_gitlab_api(url, remote.api_key.to_string());
    debug!("Project ID query response: {:?}", resp);
    let projects: Vec<GitLabProject> = resp.json().expect("failed to read projects response");
    match projects.iter().find(|&prj| prj.name == remote.name) {
        Some(project) => Ok(project.id),
        None => Err("Couldn't find project"),
    }
}

#[derive(Serialize, Deserialize, Debug)]
struct GitLabMergeRequest {
    id: i64,
    iid: i64,
    title: String,
    target_branch: String,
    source_branch: String,
    sha: String,
    web_url: String,
}

/// Get the project ID from config
fn load_project_id() -> Option<String> {
    match git::get_config("projectid") {
        Some(project_id) => Some(project_id),
        None => {
            debug!("No project ID found");
            None
        }
    }
}

/// Query the GitLab API for the branch corresponding to the MR
fn query_gitlab_branch_name(remote: &GitLab, mr_id: i64) -> Result<String, &str> {
    let client = reqwest::Client::new();
    let url = reqwest::Url::parse(&format!(
        "{}/projects/{}/merge_requests/{}",
        remote.api_root, remote.id, mr_id
    ))
    .unwrap();
    let mut resp = client
        .get(url)
        .header("PRIVATE-TOKEN", remote.api_key.to_string())
        .send()
        .expect("failed to send request");
    debug!("Response: {:?}", resp);
    let buf: GitLabMergeRequest = match resp.json() {
        Ok(buf) => buf,
        Err(_) => {
            return Err("failed to read response");
        }
    };
    Ok(buf.source_branch)
}

/// Extract the project name from a Github origin URL
fn get_github_project_name(origin: &str) -> String {
    trace!("Getting project name for: {}", origin);
    let project_regex = Regex::new(r".*:(.*/\S+)\.git\w*$").unwrap();
    let captures = project_regex.captures(origin).unwrap();
    String::from(&captures[1])
}

/// Extract the project name from a GitLab origin URL
fn get_gitlab_project_name(origin: &str) -> String {
    trace!("Getting project name for: {}", origin);
    let project_regex = Regex::new(r".*/(\S+)\.git$").unwrap();
    let captures = project_regex.captures(origin).unwrap();
    String::from(&captures[1])
}

/// Extract the project namespace from a GitLab origin URL
fn get_gitlab_project_namespace(origin: &str) -> Option<String> {
    trace!("Getting project namespace for: {}", origin);
    let project_regex = Regex::new(r".*[/:](\S+)/\S+\.git$").unwrap();
    match project_regex.captures(origin) {
        Some(captures) => Some(String::from(&captures[1])),
        None => None
    }
}

/// Get the domain from an origin URL
pub fn get_domain(origin: &str) -> Result<&str, String> {
    let domain_regex = Regex::new(r"((http[s]?|ssh)://)?(\S+@)?(?P<domain>([^:/])+)").unwrap();
    let captures = domain_regex.captures(origin);
    if captures.is_none() {
        return Err(String::from("invalid remote set"));
    }
    Ok(captures.unwrap().name("domain").map_or("", |x| x.as_str()))
}

/// Get a remote struct from an origin URL
pub fn get_remote(origin: &str) -> Result<Box<Remote>, String> {
    let domain = get_domain(origin)?;
    Ok(match domain {
        "github.com" => Box::new(GitHub {
            id: get_github_project_name(origin),
            name: get_github_project_name(origin),
            origin: String::from(origin),
            api_root: String::from("https://api.github.com/repos"),
        }),
        // For now, if not GitHub, then GitLab
        gitlab_domain => {
            let namespace = match get_gitlab_project_namespace(origin) {
                Some(ns) => ns,
                None => {
                    return Err(String::from("Could not parse the GitLab project namespace from the origin."));
                }
            };
            let mut remote = GitLab {
                id: String::from(""),
                domain: String::from(gitlab_domain),
                name: get_gitlab_project_name(origin),
                namespace,
                origin: String::from(origin),
                api_root: format!("https://{}/api/v4", gitlab_domain),
                api_key: String::from(""),
            };
            let apikey = match git::get_req_config(&domain, "apikey") {
                Some(key) => key,
                None => {
                    let mut newkey = String::new();
                    print!("Please enter the read-only API key for {}: ", gitlab_domain);
                    let _ = stdout().flush();
                    stdin()
                        .read_line(&mut newkey)
                        .expect("Did not input a correct key");
                    trace!("New Key: {}", &newkey);
                    git::set_req_config(&domain, "apikey", &newkey.trim());
                    String::from(newkey.trim())
                }
            };
            info!("API Key: {}", &apikey);
            remote.api_key = apikey;
            let project_id = match load_project_id() {
                Some(x) => x,
                None => {
                    let project_id_str = match remote.get_project_id() {
                        Ok(id_str) => Ok(id_str),
                        Err(e) => {
                            info!("Error getting project ID: {:?}", e);
                            Err(e)
                        }
                    }?;
                    git::set_config("projectid", project_id_str);
                    String::from(project_id_str)
                }
            };
            info!("Got project ID: {}", project_id);
            remote.id = project_id;
            Box::new(remote)
        }
    })
}

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

    #[test]
    fn test_get_gitlab_project_namespace_http() {
        let ns = get_gitlab_project_namespace("https://gitlab.com/my_namespace/my_project.git");
        assert!(ns.is_some());
        assert_eq!("my_namespace", ns.unwrap());
    }

    #[test]
    fn test_get_gitlab_project_namespace_git() {
        let ns = get_gitlab_project_namespace("git@gitlab.com:my_namespace/my_project.git");
        assert!(ns.is_some());
        assert_eq!("my_namespace", ns.unwrap());
    }

    #[test]
    fn test_get_gitlab_project_name_http() {
        let ns = get_gitlab_project_name("https://gitlab.com/my_namespace/my_project.git");
        assert_eq!("my_project", ns);
    }

    #[test]
    fn test_get_gitlab_project_name_git() {
        let ns = get_gitlab_project_name("git@gitlab.com:my_namespace/my_project.git");
        assert_eq!("my_project", ns);
    }
}
<|endoftext|>