<commit_before>use std::collections::HashSet;
use std::env;
use std::ffi::OsStr;
use std::io;
use std::path::PathBuf;

use anyhow::Result;
use clap::{Arg, ArgMatches, Command};
use console::style;
use if_chain::if_chain;
use proguard::ProguardMapping;
use serde::Serialize;
use symbolic::common::{ByteView, DebugId};
use uuid::{Uuid, Version as UuidVersion};
use walkdir::{DirEntry, WalkDir};

use crate::utils::args::validate_id;
use crate::utils::dif::{DifFile, DifType};
use crate::utils::progress::{ProgressBar, ProgressStyle};
use crate::utils::system::QuietExit;

// text files larger than 32 megabytes are not considered to be
// valid mapping files when scanning
const MAX_MAPPING_FILE: u64 = 32 * 1024 * 1024;

#[derive(Serialize, Debug)]
struct DifMatch {
    #[serde(rename = "type")]
    pub ty: DifType,
    pub id: DebugId,
    pub path: PathBuf,
}

pub fn make_command(command: Command) -> Command {
    command
        .about("Locate debug information files for given debug identifiers.")
        .arg(
            Arg::new("ids")
                .value_name("ID")
                .help("The debug identifiers of the files to search for.")
                .validator(validate_id)
                .multiple_occurrences(true),
        )
        .arg(
            Arg::new("types")
                .long("type")
                .short('t')
                .value_name("TYPE")
                .multiple_occurrences(true)
                .possible_values(&[
                    "dsym",
                    "elf",
                    "pe",
                    "pdb",
                    "proguard",
                    "breakpad",
                    "sourcebundle",
                ])
                .help(
                    "Only consider debug information files of the given \
                     type.  By default all types are considered.",
                ),
        )
        .arg(
            Arg::new("no_well_known")
                .long("no-well-known")
                .help("Do not look for debug symbols in well known locations."),
        )
        .arg(
            Arg::new("no_cwd")
                .long("no-cwd")
                .help("Do not look for debug symbols in the current working directory."),
        )
        .arg(
            Arg::new("paths")
                .long("path")
                .short('p')
                .multiple_occurrences(true)
                .help("Add a path to search recursively for debug info files."),
        )
        .arg(
            Arg::new("json")
                .long("json")
                .help("Format outputs as JSON."),
        )
}

fn id_hint(id: &DebugId) -> &'static str {
    if id.appendix() > 0 {
        return "likely PDB";
    }

    match id.uuid().get_version() {
        Some(UuidVersion::Sha1) => "likely Proguard",
        Some(UuidVersion::Md5) => "likely dSYM",
        None => "likely ELF Debug",
        _ => "unknown",
    }
}

fn find_ids(
    paths: &HashSet<PathBuf>,
    types: &HashSet<DifType>,
    ids: &HashSet<DebugId>,
    as_json: bool,
) -> Result<bool> {
    let mut remaining = ids.clone();
    let mut breakpad_found = HashSet::new();
    let mut proguard_uuids: HashSet<_> = ids
        .iter()
        .map(DebugId::uuid)
        .filter(|&x| x.get_version() == Some(UuidVersion::Sha1))
        .collect();

    let iter = paths
        .iter()
        .flat_map(WalkDir::new)
        .filter_map(Result::ok)
        .filter(|e| e.file_type().is_file());

    let mut found_files = vec![];
    let pb = ProgressBar::new_spinner();
    pb.set_style(
        ProgressStyle::default_spinner()
            .tick_chars("/|\\- ")
            .template(
                "{spinner} Looking for debug info files... {msg:.dim}\
                 \n  debug info files found: {prefix:.yellow}",
            ),
    );

    for dirent in iter {
        if remaining.is_empty() {
            break;
        }

        if let Some(p) = dirent.file_name().to_str() {
            pb.set_message(p);
        }
        pb.tick();
        pb.set_prefix(&format!("{}", found_files.len()));

        let found: Vec<_> = types
            .iter()
            .filter_map(|t| match t {
                DifType::Dsym => find_ids_for_dsym(&dirent, &remaining),
                DifType::Elf => find_ids_for_elf(&dirent, &remaining),
                DifType::Pe => find_ids_for_pe(&dirent, &remaining),
                DifType::Pdb => find_ids_for_pdb(&dirent, &remaining),
                DifType::SourceBundle => find_ids_for_sourcebundle(&dirent, &remaining),
                DifType::Breakpad => find_ids_for_breakpad(&dirent, &remaining),
                DifType::Proguard => find_ids_for_proguard(&dirent, &proguard_uuids),
                DifType::Wasm => None,
            })
            .flatten()
            .collect();

        for (id, ty) in found {
            let path = dirent.path().to_path_buf();
            found_files.push(DifMatch { ty, id, path });
            if ty == DifType::Breakpad {
                breakpad_found.insert(id);
            } else {
                remaining.remove(&id);
            }
            proguard_uuids.remove(&id.uuid());
        }
    }

    pb.finish_and_clear();

    if as_json {
        serde_json::to_writer_pretty(&mut io::stdout(), &found_files)?;
        println!();
    } else {
        for m in found_files {
            println!(
                "{} {} [{}]",
                style(m.id).dim(),
                m.path.display(),
                style(m.ty).yellow()
            );
        }
        remaining.extend(breakpad_found);
        if !remaining.is_empty() {
            eprintln!();
            eprintln!("missing debug information files:");
            for id in &remaining {
                eprintln!("  {} ({})", id, id_hint(id),);
            }
        }
    }

    Ok(remaining.is_empty())
}

fn find_ids_for_proguard(
    dirent: &DirEntry,
    proguard_uuids: &HashSet<Uuid>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if !proguard_uuids.is_empty();
        if dirent.path().extension() == Some(OsStr::new("txt"));
        if let Ok(md) = dirent.metadata();
        if md.len() < MAX_MAPPING_FILE;
        if let Ok(byteview) = ByteView::open(dirent.path());
        let mapping = ProguardMapping::new(&byteview);
        if mapping.is_valid();
        if proguard_uuids.contains(&mapping.uuid());
        then {
            return Some(vec![(mapping.uuid().into(), DifType::Proguard)]);
        }
    }
    None
}

fn find_ids_for_dsym(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        // we regularly match on .class files but the will never be
        // dsyms, so we can quickly skip them here
        if dirent.path().extension() != Some(OsStr::new("class"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Dsym));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Dsym))
        }
    }
    None
}

fn find_ids_for_elf(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Elf));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Elf))
        }
    }
    None
}

fn find_ids_for_pe(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("exe")) ||
        dirent.path().extension() == Some(OsStr::new("dll"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Pe));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Pe))
        }
    }
    None
}

fn find_ids_for_pdb(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("pdb"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Pdb));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Pdb))
        }
    }
    None
}

fn find_ids_for_sourcebundle(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("zip"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::SourceBundle));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::SourceBundle))
        }
    }
    None
}

fn find_ids_for_breakpad(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("sym"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Breakpad));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Breakpad))
        }
    }
    None
}

fn extract_remaining_ids(
    ids: &[DebugId],
    remaining: &HashSet<DebugId>,
    t: DifType,
) -> Vec<(DebugId, DifType)> {
    ids.iter()
        .filter_map(|id| {
            if remaining.contains(id) {
                return Some((id.to_owned(), t));
            }
            None
        })
        .collect()
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
    let mut paths = HashSet::new();
    let mut types = HashSet::new();
    let mut ids = HashSet::new();

    // which types should we consider?
    if let Some(t) = matches.values_of("types") {
        for ty in t {
            types.insert(ty.parse().unwrap());
        }
    } else {
        types.insert(DifType::Dsym);
        types.insert(DifType::Pdb);
        types.insert(DifType::Pe);
        types.insert(DifType::Proguard);
        types.insert(DifType::SourceBundle);
        types.insert(DifType::Breakpad);
    }

    let with_well_known = !matches.is_present("no_well_known");
    let with_cwd = !matches.is_present("no_cwd");

    // start adding well known locations
    if_chain! {
        if with_well_known;
        if types.contains(&DifType::Dsym);
        if let Some(path) = dirs::home_dir().map(|x| x.join("Library/Developer/Xcode/DerivedData"));
        if path.is_dir();
        then {
            paths.insert(path);
        }
    }

    // current folder if wanted
    if_chain! {
        if with_cwd;
        if let Ok(path) = env::current_dir();
        then {
            paths.insert(path);
        }
    }

    // extra paths
    if let Some(p) = matches.values_of("paths") {
        for path in p {
            paths.insert(PathBuf::from(path));
        }
    }

    // which ids are we looking for?
    if let Some(i) = matches.values_of("ids") {
        for id in i {
            ids.insert(id.parse().unwrap());
        }
    } else {
        return Ok(());
    }

    if !find_ids(&paths, &types, &ids, matches.is_present("json"))? {
        return Err(QuietExit(1).into());
    }

    Ok(())
}
<commit_msg>fix: paths argument for debug-files find should take a value (#1346)<commit_after>use std::collections::HashSet;
use std::env;
use std::ffi::OsStr;
use std::io;
use std::path::PathBuf;

use anyhow::Result;
use clap::{Arg, ArgMatches, Command};
use console::style;
use if_chain::if_chain;
use proguard::ProguardMapping;
use serde::Serialize;
use symbolic::common::{ByteView, DebugId};
use uuid::{Uuid, Version as UuidVersion};
use walkdir::{DirEntry, WalkDir};

use crate::utils::args::validate_id;
use crate::utils::dif::{DifFile, DifType};
use crate::utils::progress::{ProgressBar, ProgressStyle};
use crate::utils::system::QuietExit;

// text files larger than 32 megabytes are not considered to be
// valid mapping files when scanning
const MAX_MAPPING_FILE: u64 = 32 * 1024 * 1024;

#[derive(Serialize, Debug)]
struct DifMatch {
    #[serde(rename = "type")]
    pub ty: DifType,
    pub id: DebugId,
    pub path: PathBuf,
}

pub fn make_command(command: Command) -> Command {
    command
        .about("Locate debug information files for given debug identifiers.")
        .arg(
            Arg::new("ids")
                .value_name("ID")
                .help("The debug identifiers of the files to search for.")
                .validator(validate_id)
                .multiple_occurrences(true),
        )
        .arg(
            Arg::new("types")
                .long("type")
                .short('t')
                .value_name("TYPE")
                .multiple_occurrences(true)
                .possible_values(&[
                    "dsym",
                    "elf",
                    "pe",
                    "pdb",
                    "proguard",
                    "breakpad",
                    "sourcebundle",
                ])
                .help(
                    "Only consider debug information files of the given \
                     type.  By default all types are considered.",
                ),
        )
        .arg(
            Arg::new("no_well_known")
                .long("no-well-known")
                .help("Do not look for debug symbols in well known locations."),
        )
        .arg(
            Arg::new("no_cwd")
                .long("no-cwd")
                .help("Do not look for debug symbols in the current working directory."),
        )
        .arg(
            Arg::new("paths")
                .long("path")
                .short('p')
                .value_name("PATH")
                .multiple_occurrences(true)
                .help("Add a path to search recursively for debug info files."),
        )
        .arg(
            Arg::new("json")
                .long("json")
                .help("Format outputs as JSON."),
        )
}

fn id_hint(id: &DebugId) -> &'static str {
    if id.appendix() > 0 {
        return "likely PDB";
    }

    match id.uuid().get_version() {
        Some(UuidVersion::Sha1) => "likely Proguard",
        Some(UuidVersion::Md5) => "likely dSYM",
        None => "likely ELF Debug",
        _ => "unknown",
    }
}

fn find_ids(
    paths: &HashSet<PathBuf>,
    types: &HashSet<DifType>,
    ids: &HashSet<DebugId>,
    as_json: bool,
) -> Result<bool> {
    let mut remaining = ids.clone();
    let mut breakpad_found = HashSet::new();
    let mut proguard_uuids: HashSet<_> = ids
        .iter()
        .map(DebugId::uuid)
        .filter(|&x| x.get_version() == Some(UuidVersion::Sha1))
        .collect();

    let iter = paths
        .iter()
        .flat_map(WalkDir::new)
        .filter_map(Result::ok)
        .filter(|e| e.file_type().is_file());

    let mut found_files = vec![];
    let pb = ProgressBar::new_spinner();
    pb.set_style(
        ProgressStyle::default_spinner()
            .tick_chars("/|\\- ")
            .template(
                "{spinner} Looking for debug info files... {msg:.dim}\
                 \n  debug info files found: {prefix:.yellow}",
            ),
    );

    for dirent in iter {
        if remaining.is_empty() {
            break;
        }

        if let Some(p) = dirent.file_name().to_str() {
            pb.set_message(p);
        }
        pb.tick();
        pb.set_prefix(&format!("{}", found_files.len()));

        let found: Vec<_> = types
            .iter()
            .filter_map(|t| match t {
                DifType::Dsym => find_ids_for_dsym(&dirent, &remaining),
                DifType::Elf => find_ids_for_elf(&dirent, &remaining),
                DifType::Pe => find_ids_for_pe(&dirent, &remaining),
                DifType::Pdb => find_ids_for_pdb(&dirent, &remaining),
                DifType::SourceBundle => find_ids_for_sourcebundle(&dirent, &remaining),
                DifType::Breakpad => find_ids_for_breakpad(&dirent, &remaining),
                DifType::Proguard => find_ids_for_proguard(&dirent, &proguard_uuids),
                DifType::Wasm => None,
            })
            .flatten()
            .collect();

        for (id, ty) in found {
            let path = dirent.path().to_path_buf();
            found_files.push(DifMatch { ty, id, path });
            if ty == DifType::Breakpad {
                breakpad_found.insert(id);
            } else {
                remaining.remove(&id);
            }
            proguard_uuids.remove(&id.uuid());
        }
    }

    pb.finish_and_clear();

    if as_json {
        serde_json::to_writer_pretty(&mut io::stdout(), &found_files)?;
        println!();
    } else {
        for m in found_files {
            println!(
                "{} {} [{}]",
                style(m.id).dim(),
                m.path.display(),
                style(m.ty).yellow()
            );
        }
        remaining.extend(breakpad_found);
        if !remaining.is_empty() {
            eprintln!();
            eprintln!("missing debug information files:");
            for id in &remaining {
                eprintln!("  {} ({})", id, id_hint(id),);
            }
        }
    }

    Ok(remaining.is_empty())
}

fn find_ids_for_proguard(
    dirent: &DirEntry,
    proguard_uuids: &HashSet<Uuid>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if !proguard_uuids.is_empty();
        if dirent.path().extension() == Some(OsStr::new("txt"));
        if let Ok(md) = dirent.metadata();
        if md.len() < MAX_MAPPING_FILE;
        if let Ok(byteview) = ByteView::open(dirent.path());
        let mapping = ProguardMapping::new(&byteview);
        if mapping.is_valid();
        if proguard_uuids.contains(&mapping.uuid());
        then {
            return Some(vec![(mapping.uuid().into(), DifType::Proguard)]);
        }
    }
    None
}

fn find_ids_for_dsym(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        // we regularly match on .class files but the will never be
        // dsyms, so we can quickly skip them here
        if dirent.path().extension() != Some(OsStr::new("class"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Dsym));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Dsym))
        }
    }
    None
}

fn find_ids_for_elf(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Elf));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Elf))
        }
    }
    None
}

fn find_ids_for_pe(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("exe")) ||
        dirent.path().extension() == Some(OsStr::new("dll"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Pe));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Pe))
        }
    }
    None
}

fn find_ids_for_pdb(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("pdb"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Pdb));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Pdb))
        }
    }
    None
}

fn find_ids_for_sourcebundle(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("zip"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::SourceBundle));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::SourceBundle))
        }
    }
    None
}

fn find_ids_for_breakpad(
    dirent: &DirEntry,
    remaining: &HashSet<DebugId>,
) -> Option<Vec<(DebugId, DifType)>> {
    if_chain! {
        if dirent.path().extension() == Some(OsStr::new("sym"));
        if let Ok(dif) = DifFile::open_path(dirent.path(), Some(DifType::Breakpad));
        then {
            return Some(extract_remaining_ids(&dif.ids(), remaining, DifType::Breakpad))
        }
    }
    None
}

fn extract_remaining_ids(
    ids: &[DebugId],
    remaining: &HashSet<DebugId>,
    t: DifType,
) -> Vec<(DebugId, DifType)> {
    ids.iter()
        .filter_map(|id| {
            if remaining.contains(id) {
                return Some((id.to_owned(), t));
            }
            None
        })
        .collect()
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
    let mut paths = HashSet::new();
    let mut types = HashSet::new();
    let mut ids = HashSet::new();

    // which types should we consider?
    if let Some(t) = matches.values_of("types") {
        for ty in t {
            types.insert(ty.parse().unwrap());
        }
    } else {
        types.insert(DifType::Dsym);
        types.insert(DifType::Pdb);
        types.insert(DifType::Pe);
        types.insert(DifType::Proguard);
        types.insert(DifType::SourceBundle);
        types.insert(DifType::Breakpad);
    }

    let with_well_known = !matches.is_present("no_well_known");
    let with_cwd = !matches.is_present("no_cwd");

    // start adding well known locations
    if_chain! {
        if with_well_known;
        if types.contains(&DifType::Dsym);
        if let Some(path) = dirs::home_dir().map(|x| x.join("Library/Developer/Xcode/DerivedData"));
        if path.is_dir();
        then {
            paths.insert(path);
        }
    }

    // current folder if wanted
    if_chain! {
        if with_cwd;
        if let Ok(path) = env::current_dir();
        then {
            paths.insert(path);
        }
    }

    // extra paths
    if let Some(p) = matches.values_of("paths") {
        for path in p {
            paths.insert(PathBuf::from(path));
        }
    }

    // which ids are we looking for?
    if let Some(i) = matches.values_of("ids") {
        for id in i {
            ids.insert(id.parse().unwrap());
        }
    } else {
        return Ok(());
    }

    if !find_ids(&paths, &types, &ids, matches.is_present("json"))? {
        return Err(QuietExit(1).into());
    }

    Ok(())
}
<|endoftext|>


<commit_before>//! The integration suite helpers.

use regex::Regex;
use std::process::Command;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

/// List windows will give details about the active sessions in testing.
/// target: A string represented by the {named_session}:{named_window}
/// tmux list-windows -t TARGET -F '#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'
pub fn list_windows(target: &String) -> String {
    let output = Command::new("tmux")
                     .arg("list-windows")
                     .arg("-t")
                     .arg(target)
                     .arg("-F")
                     .arg("'#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'")
                     .output()
                     .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn open_muxed(project: &String, project_root: &Path) -> () {
    println!("root: {}", project_root.display());
    let output = Command::new("./target/debug/muxed")
        .arg("-d")
        .arg("-p")
        .arg(format!("{}", project_root.display()))
        .arg(project)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

pub fn kill_session(target: &String) -> () {
    Command::new("tmux")
        .arg("kill-session")
        .arg("-t")
        .arg(target)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn send_keys(target: &String, exec: &String) -> () {
    Command::new("tmux")
        .arg("send-keys")
        .arg("-t")
        .arg(target)
        .arg(exec)
        .arg("KPEnter")
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn wait_on(file: &PathBuf) -> () {
    while !file.exists() {
        // Wait increased from 10 to 750 due to the pre_window tests.
        sleep(Duration::from_millis(750));
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SessionValue {
    Usize(usize),
    String(String),
    Empty,
}

impl SessionValue {
    pub fn as_str(&self) -> Option<&str> {
        match self {
            &SessionValue::String(ref s) => Some(s),
            _ => None
        }
    }

    pub fn as_usize(&self) -> Option<usize> {
        match self {
            &SessionValue::Usize(s) => Some(s),
            _ => None
        }
    }
}

#[derive(Debug)]
pub struct TmuxSession {
    pub num_of_windows: usize,
    pub windows: HashMap<String, WindowValues>,
    pub window_active: SessionValue,
    pub name: SessionValue,
}

#[derive(Debug, Clone)]
pub struct WindowValues {
    pub panes: SessionValue,
    pub pane_current_path: SessionValue,
}

static NAME_REGEX:              &'static str = r"\(Session: (.*)\)";
static WINDOW_NAME_REGEX:       &'static str = r":\s(\w*)[$\*-]?\s+\(";
static WINDOW_ACTIVE_REGEX:     &'static str = r"\s(.*)\*";
static PANE_CURRENT_PATH_REGEX: &'static str = r"\(Dir: (.*)\) ";
static PANES_COUNT_REGEX:       &'static str = r"\((\d*) panes\)";

impl TmuxSession {
    pub fn from_string(results: &String) -> TmuxSession {
        let window_name = Regex::new(WINDOW_NAME_REGEX).unwrap();

        let lines: Vec<&str> = results.split("\n").collect();
        let (_, window_lines) = lines.split_last().unwrap();

        let mut windows: HashMap<String, WindowValues> = HashMap::new();

        for line in window_lines {
            let cap = window_name.captures(line).unwrap();
            let name = cap.at(1).unwrap();

            let win_val = WindowValues{
                panes: TmuxSession::count_panes(line),
                pane_current_path: TmuxSession::retrieve_capture(&line, PANE_CURRENT_PATH_REGEX).unwrap_or(SessionValue::Empty)
            };

            windows.insert(name.to_string(), win_val.clone());
        }

        TmuxSession {
          num_of_windows: window_lines.len(),
          windows: windows,
          window_active: TmuxSession::retrieve_capture(&window_lines[0], WINDOW_ACTIVE_REGEX).unwrap_or(SessionValue::Empty),
          name: TmuxSession::retrieve_capture(&window_lines[0], NAME_REGEX).unwrap_or(SessionValue::Empty)
        }
    }

    pub fn retrieve_capture(line: &str, pattern: &str) -> Result<SessionValue, String> {
        let reg = Regex::new(pattern).unwrap();

        if let Some(caps) = reg.captures(line) {
            return match caps.at(1) {
               Some(x) => Ok(SessionValue::String(x.to_string())),
               None    => Err("No capture".to_string())
            };
        };

        Err("No capture".to_string())
    }

    pub fn count_panes(line: &str) -> SessionValue {
        let panes = Regex::new(PANES_COUNT_REGEX).unwrap();
        let mut num: &str = "";

        for cap in panes.captures_iter(line) {
            num = cap.at(1).unwrap_or("0");
        }

        SessionValue::Usize(usize::from_str(num).unwrap())
    }
}

#[test]
fn count_panes_returns_two() {
    let num = TmuxSession::count_panes("1: ssh  (2 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 2)
}

#[test]
fn count_panes_returns_one() {
    let num = TmuxSession::count_panes("1: ssh  (1 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 1)
}

#[test]
fn parses_with_trailing_whitespace_after_window_name() {
    let config = "1: ssh  (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_previous_flag() {
    let config = "1: ssh- (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_dollar_sign_flag() {
    let config = "1: ssh$ (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_window_flag() {
    let config = "1: ssh* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn count_three_windows() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let num = TmuxSession::from_string(&config.to_string()).num_of_windows;
    assert_eq!(num, 3)
}

#[test]
fn count_four_total_panes() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let num = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    let num1 = session.windows.get("vim").unwrap().panes.as_usize().unwrap();
    let num2 = session.windows.get("bash").unwrap().panes.as_usize().unwrap();
    assert_eq!(num, 1);
    assert_eq!(num1, 1);
    assert_eq!(num2, 2)
}

#[test]
fn expect_ok_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session:)";
    let result = TmuxSession::retrieve_capture(&line, NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_muxed_to_be_captured_as_session() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "muxed")
}

#[test]
fn expect_ok_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_window_name_capture() {
    let line = "2: (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim1_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim1")
}

#[test]
fn expect_ok_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_active_window_capture() {
    let line = "2: vim (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_ACTIVE_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim")
}
<commit_msg>test(regex): Update regex usage for newer version<commit_after>//! The integration suite helpers.

use regex::Regex;
use std::process::Command;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

/// List windows will give details about the active sessions in testing.
/// target: A string represented by the {named_session}:{named_window}
/// tmux list-windows -t TARGET -F '#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'
pub fn list_windows(target: &String) -> String {
    let output = Command::new("tmux")
                     .arg("list-windows")
                     .arg("-t")
                     .arg(target)
                     .arg("-F")
                     .arg("'#{window_index}: #{window_name}#{?window_active,*, } (#{window_panes} panes) (Dir: #{pane_current_path}) (Session: #{session_name})'")
                     .output()
                     .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    String::from_utf8_lossy(&output.stdout).into_owned()
}

pub fn open_muxed(project: &String, project_root: &Path) -> () {
    println!("root: {}", project_root.display());
    let output = Command::new("./target/debug/muxed")
        .arg("-d")
        .arg("-p")
        .arg(format!("{}", project_root.display()))
        .arg(project)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    println!("status: {}", output.status);
    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}

pub fn kill_session(target: &String) -> () {
    Command::new("tmux")
        .arg("kill-session")
        .arg("-t")
        .arg(target)
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn send_keys(target: &String, exec: &String) -> () {
    Command::new("tmux")
        .arg("send-keys")
        .arg("-t")
        .arg(target)
        .arg(exec)
        .arg("KPEnter")
        .output()
        .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
}

pub fn wait_on(file: &PathBuf) -> () {
    while !file.exists() {
        // Wait increased from 10 to 750 due to the pre_window tests.
        sleep(Duration::from_millis(750));
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SessionValue {
    Usize(usize),
    String(String),
    Empty,
}

impl SessionValue {
    pub fn as_str(&self) -> Option<&str> {
        match self {
            &SessionValue::String(ref s) => Some(s),
            _ => None
        }
    }

    pub fn as_usize(&self) -> Option<usize> {
        match self {
            &SessionValue::Usize(s) => Some(s),
            _ => None
        }
    }
}

#[derive(Debug)]
pub struct TmuxSession {
    pub num_of_windows: usize,
    pub windows: HashMap<String, WindowValues>,
    pub window_active: SessionValue,
    pub name: SessionValue,
}

#[derive(Debug, Clone)]
pub struct WindowValues {
    pub panes: SessionValue,
    pub pane_current_path: SessionValue,
}

static NAME_REGEX:              &'static str = r"\(Session: (.*)\)";
static WINDOW_NAME_REGEX:       &'static str = r":\s(\w*)[$\*-]?\s+\(";
static WINDOW_ACTIVE_REGEX:     &'static str = r"\s(.*)\*";
static PANE_CURRENT_PATH_REGEX: &'static str = r"\(Dir: (.*)\) ";
static PANES_COUNT_REGEX:       &'static str = r"\((\d*) panes\)";

impl TmuxSession {
    pub fn from_string(results: &String) -> TmuxSession {
        let window_name = Regex::new(WINDOW_NAME_REGEX).unwrap();

        let lines: Vec<&str> = results.split("\n").collect();
        let (_, window_lines) = lines.split_last().unwrap();

        let mut windows: HashMap<String, WindowValues> = HashMap::new();

        for line in window_lines {
            let cap = window_name.captures(line).unwrap();
            let name = cap.get(1).unwrap().as_str();

            let win_val = WindowValues{
                panes: TmuxSession::count_panes(line),
                pane_current_path: TmuxSession::retrieve_capture(&line, PANE_CURRENT_PATH_REGEX).unwrap_or(SessionValue::Empty)
            };

            windows.insert(name.to_string(), win_val.clone());
        }

        TmuxSession {
          num_of_windows: window_lines.len(),
          windows: windows,
          window_active: TmuxSession::retrieve_capture(&window_lines[0], WINDOW_ACTIVE_REGEX).unwrap_or(SessionValue::Empty),
          name: TmuxSession::retrieve_capture(&window_lines[0], NAME_REGEX).unwrap_or(SessionValue::Empty)
        }
    }

    pub fn retrieve_capture(line: &str, pattern: &str) -> Result<SessionValue, String> {
        let reg = Regex::new(pattern).unwrap();

        if let Some(caps) = reg.captures(line) {
            return match caps.get(1) {
               Some(x) => Ok(SessionValue::String(x.as_str().to_string())),
               None    => Err("No capture".to_string())
            };
        };

        Err("No capture".to_string())
    }

    pub fn count_panes(line: &str) -> SessionValue {
        let panes = Regex::new(PANES_COUNT_REGEX).unwrap();
        let mut num: &str = "";

        for cap in panes.captures_iter(line) {
            num = match cap.get(1) {
                Some(x) => x.as_str(),
                None    => "0"
            }
        }

        SessionValue::Usize(usize::from_str(num).unwrap())
    }
}

#[test]
fn count_panes_returns_two() {
    let num = TmuxSession::count_panes("1: ssh  (2 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 2)
}

#[test]
fn count_panes_returns_one() {
    let num = TmuxSession::count_panes("1: ssh  (1 panes) (Dir: /Projects/muxed) (session: muxed)");
    assert_eq!(num.as_usize().unwrap(), 1)
}

#[test]
fn parses_with_trailing_whitespace_after_window_name() {
    let config = "1: ssh  (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_previous_flag() {
    let config = "1: ssh- (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_dollar_sign_flag() {
    let config = "1: ssh$ (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn parses_with_window_flag() {
    let config = "1: ssh* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let panes = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    assert_eq!(session.num_of_windows, 1);
    assert_eq!(panes, 2)
}

#[test]
fn count_three_windows() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let num = TmuxSession::from_string(&config.to_string()).num_of_windows;
    assert_eq!(num, 3)
}

#[test]
fn count_four_total_panes() {
    let config = "1: ssh  (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  2: vim- (1 panes) (Dir: /Projects/muxed) (Session: muxed)
                  3: bash* (2 panes) (Dir: /Projects/muxed) (Session: muxed)\n";
    let session = TmuxSession::from_string(&config.to_string());
    let num = session.windows.get("ssh").unwrap().panes.as_usize().unwrap();
    let num1 = session.windows.get("vim").unwrap().panes.as_usize().unwrap();
    let num2 = session.windows.get("bash").unwrap().panes.as_usize().unwrap();
    assert_eq!(num, 1);
    assert_eq!(num1, 1);
    assert_eq!(num2, 2)
}

#[test]
fn expect_ok_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_session_name_capture() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session:)";
    let result = TmuxSession::retrieve_capture(&line, NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_muxed_to_be_captured_as_session() {
    let line = "2: vim1* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "muxed")
}

#[test]
fn expect_ok_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_NAME_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_window_name_capture() {
    let line = "2: (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_NAME_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim1_window_name_capture() {
    let line = "2: vim1* (1 panes) vim2* (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_NAME_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim1")
}

#[test]
fn expect_ok_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_ok())
}

#[test]
fn expect_err_active_window_capture() {
    let line = "2: vim (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_ACTIVE_REGEX);
    assert!(result.is_err())
}

#[test]
fn expect_vim_active_window_capture() {
    let line = "2: vim* (1 panes) (Dir: /Projects/muxed) (Session: muxed)";
    let result = TmuxSession::retrieve_capture(&line, WINDOW_ACTIVE_REGEX).unwrap();
    assert_eq!(result.as_str().unwrap(), "vim")
}
<|endoftext|>


<commit_before>//! Seat global utilities
//!
//! This module provides you with utilities for handling the seat globals
//! and the associated input Wayland objects.
//!
//! ## How to use it
//!
//! ### Initialization
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::seat::Seat;
//!
//! # fn main(){
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! // insert the seat:
//! let (seat, seat_global) = Seat::new(
//!     &mut display,    // the display
//!     "seat-0".into(), // the name of the seat, will be advertized to clients
//!     None             // insert a logger here
//! );
//! # }
//! ```
//!
//! ### Run usage
//!
//! Once the seat is initialized, you can add capabilities to it.
//!
//! Currently, only pointer and keyboard capabilities are supported by
//! smithay.
//!
//! You can add these capabilities via methods of the `Seat` struct:
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! #
//! # use smithay::wayland::seat::Seat;
//! #
//! # fn main(){
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! # let (mut seat, seat_global) = Seat::new(
//! #     &mut display,
//! #     "seat-0".into(),
//! #     None
//! # );
//! let pointer_handle = seat.add_pointer();
//! # }
//! ```
//!
//! These handles can be cloned and sent across thread, so you can keep one around
//! in your event-handling code to forward inputs to your clients.

use std::sync::{Arc, Mutex};

mod keyboard;
mod pointer;

pub use self::{
    keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig},
    pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle},
};

use wayland_server::{protocol::wl_seat, Display, Global, NewResource, Resource};

struct Inner {
    log: ::slog::Logger,
    name: String,
    pointer: Option<PointerHandle>,
    keyboard: Option<KeyboardHandle>,
    known_seats: Vec<Resource<wl_seat::WlSeat>>,
}

impl Inner {
    fn compute_caps(&self) -> wl_seat::Capability {
        let mut caps = wl_seat::Capability::empty();
        if self.pointer.is_some() {
            caps |= wl_seat::Capability::Pointer;
        }
        if self.keyboard.is_some() {
            caps |= wl_seat::Capability::Keyboard;
        }
        caps
    }

    fn send_all_caps(&self) {
        let capabilities = self.compute_caps();
        for seat in &self.known_seats {
            seat.send(wl_seat::Event::Capabilities { capabilities });
        }
    }
}

/// A Seat handle
///
/// This struct gives you access to the control of the
/// capabilities of the associated seat.
///
/// It is directly inserted in the event loop by its `new` method.
///
/// See module-level documentation for details of use.
pub struct Seat {
    inner: Arc<Mutex<Inner>>,
}

impl Seat {
    /// Create a new seat global
    ///
    /// A new seat global is created with given name and inserted
    /// into this event loop.
    ///
    /// You are provided with the state token to retrieve it (allowing
    /// you to add or remove capabilities from it), and the global handle,
    /// in case you want to remove it.
    pub fn new<L>(display: &mut Display, name: String, logger: L) -> (Seat, Global<wl_seat::WlSeat>)
    where
        L: Into<Option<::slog::Logger>>,
    {
        let log = ::slog_or_stdlog(logger);
        let inner = Arc::new(Mutex::new(Inner {
            log: log.new(o!("smithay_module" => "seat_handler", "seat_name" => name.clone())),
            name,
            pointer: None,
            keyboard: None,
            known_seats: Vec::new(),
        }));
        let seat = Seat { inner: inner.clone() };
        let global = display.create_global(5, move |new_seat, _version| {
            let seat = implement_seat(new_seat, inner.clone());
            let mut inner = inner.lock().unwrap();
            if seat.version() >= 2 {
                seat.send(wl_seat::Event::Name {
                    name: inner.name.clone(),
                });
            }
            seat.send(wl_seat::Event::Capabilities {
                capabilities: inner.compute_caps(),
            });
            inner.known_seats.push(seat);
        });
        (seat, global)
    }

    /// Adds the pointer capability to this seat
    ///
    /// You are provided a `PointerHandle`, which allows you to send input events
    /// to this keyboard. This handle can be cloned and sent across threads.
    ///
    /// Calling this method on a seat that already has a pointer capability
    /// will overwrite it, and will be seen by the clients as if the
    /// mouse was unplugged and a new one was plugged.
    pub fn add_pointer(&mut self) -> PointerHandle {
        let mut inner = self.inner.lock().unwrap();
        let pointer = self::pointer::create_pointer_handler();
        if inner.pointer.is_some() {
            // there is already a pointer, remove it and notify the clients
            // of the change
            inner.pointer = None;
            inner.send_all_caps();
        }
        inner.pointer = Some(pointer.clone());
        inner.send_all_caps();
        pointer
    }

    /// Remove the pointer capability from this seat
    ///
    /// Clients will be appropriately notified.
    pub fn remove_pointer(&mut self) {
        let mut inner = self.inner.lock().unwrap();
        if inner.pointer.is_some() {
            inner.pointer = None;
            inner.send_all_caps();
        }
    }

    /// Adds the keyboard capability to this seat
    ///
    /// You are provided a `KbdHandle`, which allows you to send input events
    /// to this keyboard. This handle can be cloned and sent across threads.
    ///
    /// You also provide a Model/Layout/Variant/Options specification of the
    /// keymap to be used for this keyboard, as well as any repeat-info that
    /// will be forwarded to the clients.
    ///
    /// Calling this method on a seat that already has a keyboard capability
    /// will overwrite it, and will be seen by the clients as if the
    /// keyboard was unplugged and a new one was plugged.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # extern crate smithay;
    /// # use smithay::wayland::seat::{Seat, XkbConfig};
    /// # let mut seat: Seat = unimplemented!();
    /// let keyboard = seat
    ///     .add_keyboard(
    ///         XkbConfig {
    ///             layout: "de",
    ///             variant: "nodeadkeys",
    ///             ..XkbConfig::default()
    ///         },
    ///         1000,
    ///         500,
    ///     )
    ///     .expect("Failed to initialize the keyboard");
    /// ```
    pub fn add_keyboard(
        &mut self,
        xkb_config: keyboard::XkbConfig,
        repeat_delay: i32,
        repeat_rate: i32,
    ) -> Result<KeyboardHandle, KeyboardError> {
        let mut inner = self.inner.lock().unwrap();
        let keyboard =
            self::keyboard::create_keyboard_handler(xkb_config, repeat_delay, repeat_rate, &inner.log)?;
        if inner.keyboard.is_some() {
            // there is already a keyboard, remove it and notify the clients
            // of the change
            inner.keyboard = None;
            inner.send_all_caps();
        }
        inner.keyboard = Some(keyboard.clone());
        inner.send_all_caps();
        Ok(keyboard)
    }

    /// Remove the keyboard capability from this seat
    ///
    /// Clients will be appropriately notified.
    pub fn remove_keyboard(&mut self) {
        let mut inner = self.inner.lock().unwrap();
        if inner.keyboard.is_some() {
            inner.keyboard = None;
            inner.send_all_caps();
        }
    }

    /// Checks whether a given `WlSeat` is associated with this `Seat`
    pub fn owns(&self, seat: &Resource<wl_seat::WlSeat>) -> bool {
        let inner = self.inner.lock().unwrap();
        inner.known_seats.iter().any(|s| s.equals(seat))
    }
}

fn implement_seat(
    new_seat: NewResource<wl_seat::WlSeat>,
    inner: Arc<Mutex<Inner>>,
) -> Resource<wl_seat::WlSeat> {
    let dest_inner = inner.clone();
    new_seat.implement(
        move |request, _seat| {
            let inner = inner.lock().unwrap();
            match request {
                wl_seat::Request::GetPointer { id } => {
                    let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref());
                    if let Some(ref ptr_handle) = inner.pointer {
                        ptr_handle.new_pointer(pointer);
                    } else {
                        // we should send a protocol error... but the protocol does not allow
                        // us, so this pointer will just remain inactive ¯\_(ツ)_/¯
                    }
                }
                wl_seat::Request::GetKeyboard { id } => {
                    let keyboard = self::keyboard::implement_keyboard(id, inner.keyboard.as_ref());
                    if let Some(ref kbd_handle) = inner.keyboard {
                        kbd_handle.new_kbd(keyboard);
                    } else {
                        // same as pointer, should error but cannot
                    }
                }
                wl_seat::Request::GetTouch { id: _ } => {
                    // TODO
                }
                wl_seat::Request::Release => {
                    // Our destructors already handle it
                }
            }
        },
        Some(move |seat| {
            dest_inner
                .lock()
                .unwrap()
                .known_seats
                .retain(|s| !s.equals(&seat));
        }),
        (),
    )
}
<commit_msg>seat: add utilities to Seat<commit_after>//! Seat global utilities
//!
//! This module provides you with utilities for handling the seat globals
//! and the associated input Wayland objects.
//!
//! ## How to use it
//!
//! ### Initialization
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::wayland::seat::Seat;
//!
//! # fn main(){
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! // insert the seat:
//! let (seat, seat_global) = Seat::new(
//!     &mut display,    // the display
//!     "seat-0".into(), // the name of the seat, will be advertized to clients
//!     None             // insert a logger here
//! );
//! # }
//! ```
//!
//! ### Run usage
//!
//! Once the seat is initialized, you can add capabilities to it.
//!
//! Currently, only pointer and keyboard capabilities are supported by
//! smithay.
//!
//! You can add these capabilities via methods of the `Seat` struct:
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! #
//! # use smithay::wayland::seat::Seat;
//! #
//! # fn main(){
//! # let mut event_loop = wayland_server::calloop::EventLoop::<()>::new().unwrap();
//! # let mut display = wayland_server::Display::new(event_loop.handle());
//! # let (mut seat, seat_global) = Seat::new(
//! #     &mut display,
//! #     "seat-0".into(),
//! #     None
//! # );
//! let pointer_handle = seat.add_pointer();
//! # }
//! ```
//!
//! These handles can be cloned and sent across thread, so you can keep one around
//! in your event-handling code to forward inputs to your clients.

use std::sync::{Arc, Mutex};

mod keyboard;
mod pointer;

pub use self::{
    keyboard::{keysyms, Error as KeyboardError, KeyboardHandle, Keysym, ModifiersState, XkbConfig},
    pointer::{AxisFrame, PointerGrab, PointerHandle, PointerInnerHandle},
};

use wayland_server::{protocol::wl_seat, Display, Global, NewResource, Resource};

struct Inner {
    log: ::slog::Logger,
    name: String,
    pointer: Option<PointerHandle>,
    keyboard: Option<KeyboardHandle>,
    known_seats: Vec<Resource<wl_seat::WlSeat>>,
}

impl Inner {
    fn compute_caps(&self) -> wl_seat::Capability {
        let mut caps = wl_seat::Capability::empty();
        if self.pointer.is_some() {
            caps |= wl_seat::Capability::Pointer;
        }
        if self.keyboard.is_some() {
            caps |= wl_seat::Capability::Keyboard;
        }
        caps
    }

    fn send_all_caps(&self) {
        let capabilities = self.compute_caps();
        for seat in &self.known_seats {
            seat.send(wl_seat::Event::Capabilities { capabilities });
        }
    }
}

/// A Seat handle
///
/// This struct gives you access to the control of the
/// capabilities of the associated seat.
///
/// It is directly inserted in the event loop by its `new` method.
///
/// See module-level documentation for details of use.
pub struct Seat {
    inner: Arc<Mutex<Inner>>,
}

impl Seat {
    /// Create a new seat global
    ///
    /// A new seat global is created with given name and inserted
    /// into this event loop.
    ///
    /// You are provided with the state token to retrieve it (allowing
    /// you to add or remove capabilities from it), and the global handle,
    /// in case you want to remove it.
    pub fn new<L>(display: &mut Display, name: String, logger: L) -> (Seat, Global<wl_seat::WlSeat>)
    where
        L: Into<Option<::slog::Logger>>,
    {
        let log = ::slog_or_stdlog(logger);
        let inner = Arc::new(Mutex::new(Inner {
            log: log.new(o!("smithay_module" => "seat_handler", "seat_name" => name.clone())),
            name,
            pointer: None,
            keyboard: None,
            known_seats: Vec::new(),
        }));
        let seat = Seat { inner: inner.clone() };
        let global = display.create_global(5, move |new_seat, _version| {
            let seat = implement_seat(new_seat, inner.clone());
            let mut inner = inner.lock().unwrap();
            if seat.version() >= 2 {
                seat.send(wl_seat::Event::Name {
                    name: inner.name.clone(),
                });
            }
            seat.send(wl_seat::Event::Capabilities {
                capabilities: inner.compute_caps(),
            });
            inner.known_seats.push(seat);
        });
        (seat, global)
    }

    /// Attempt to retrieve a `Seat` from an existing resource
    pub fn from_resource(seat: &Resource<wl_seat::WlSeat>) -> Option<Seat> {
        seat.user_data::<Arc<Mutex<Inner>>>()
            .cloned()
            .map(|inner| Seat { inner })
    }

    /// Adds the pointer capability to this seat
    ///
    /// You are provided a `PointerHandle`, which allows you to send input events
    /// to this keyboard. This handle can be cloned and sent across threads.
    ///
    /// Calling this method on a seat that already has a pointer capability
    /// will overwrite it, and will be seen by the clients as if the
    /// mouse was unplugged and a new one was plugged.
    pub fn add_pointer(&mut self) -> PointerHandle {
        let mut inner = self.inner.lock().unwrap();
        let pointer = self::pointer::create_pointer_handler();
        if inner.pointer.is_some() {
            // there is already a pointer, remove it and notify the clients
            // of the change
            inner.pointer = None;
            inner.send_all_caps();
        }
        inner.pointer = Some(pointer.clone());
        inner.send_all_caps();
        pointer
    }

    /// Access the pointer of this seat if any
    pub fn get_pointer(&self) -> Option<PointerHandle> {
        self.inner.lock().unwrap().pointer.clone()
    }

    /// Remove the pointer capability from this seat
    ///
    /// Clients will be appropriately notified.
    pub fn remove_pointer(&mut self) {
        let mut inner = self.inner.lock().unwrap();
        if inner.pointer.is_some() {
            inner.pointer = None;
            inner.send_all_caps();
        }
    }

    /// Adds the keyboard capability to this seat
    ///
    /// You are provided a `KbdHandle`, which allows you to send input events
    /// to this keyboard. This handle can be cloned and sent across threads.
    ///
    /// You also provide a Model/Layout/Variant/Options specification of the
    /// keymap to be used for this keyboard, as well as any repeat-info that
    /// will be forwarded to the clients.
    ///
    /// Calling this method on a seat that already has a keyboard capability
    /// will overwrite it, and will be seen by the clients as if the
    /// keyboard was unplugged and a new one was plugged.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # extern crate smithay;
    /// # use smithay::wayland::seat::{Seat, XkbConfig};
    /// # let mut seat: Seat = unimplemented!();
    /// let keyboard = seat
    ///     .add_keyboard(
    ///         XkbConfig {
    ///             layout: "de",
    ///             variant: "nodeadkeys",
    ///             ..XkbConfig::default()
    ///         },
    ///         1000,
    ///         500,
    ///     )
    ///     .expect("Failed to initialize the keyboard");
    /// ```
    pub fn add_keyboard(
        &mut self,
        xkb_config: keyboard::XkbConfig,
        repeat_delay: i32,
        repeat_rate: i32,
    ) -> Result<KeyboardHandle, KeyboardError> {
        let mut inner = self.inner.lock().unwrap();
        let keyboard =
            self::keyboard::create_keyboard_handler(xkb_config, repeat_delay, repeat_rate, &inner.log)?;
        if inner.keyboard.is_some() {
            // there is already a keyboard, remove it and notify the clients
            // of the change
            inner.keyboard = None;
            inner.send_all_caps();
        }
        inner.keyboard = Some(keyboard.clone());
        inner.send_all_caps();
        Ok(keyboard)
    }

    /// Access the keyboard of this seat if any
    pub fn get_keyboard(&self) -> Option<KeyboardHandle> {
        self.inner.lock().unwrap().keyboard.clone()
    }

    /// Remove the keyboard capability from this seat
    ///
    /// Clients will be appropriately notified.
    pub fn remove_keyboard(&mut self) {
        let mut inner = self.inner.lock().unwrap();
        if inner.keyboard.is_some() {
            inner.keyboard = None;
            inner.send_all_caps();
        }
    }

    /// Checks whether a given `WlSeat` is associated with this `Seat`
    pub fn owns(&self, seat: &Resource<wl_seat::WlSeat>) -> bool {
        let inner = self.inner.lock().unwrap();
        inner.known_seats.iter().any(|s| s.equals(seat))
    }
}

fn implement_seat(
    new_seat: NewResource<wl_seat::WlSeat>,
    inner: Arc<Mutex<Inner>>,
) -> Resource<wl_seat::WlSeat> {
    let dest_inner = inner.clone();
    new_seat.implement(
        move |request, seat| {
            let inner = seat.user_data::<Arc<Mutex<Inner>>>().unwrap().lock().unwrap();
            match request {
                wl_seat::Request::GetPointer { id } => {
                    let pointer = self::pointer::implement_pointer(id, inner.pointer.as_ref());
                    if let Some(ref ptr_handle) = inner.pointer {
                        ptr_handle.new_pointer(pointer);
                    } else {
                        // we should send a protocol error... but the protocol does not allow
                        // us, so this pointer will just remain inactive ¯\_(ツ)_/¯
                    }
                }
                wl_seat::Request::GetKeyboard { id } => {
                    let keyboard = self::keyboard::implement_keyboard(id, inner.keyboard.as_ref());
                    if let Some(ref kbd_handle) = inner.keyboard {
                        kbd_handle.new_kbd(keyboard);
                    } else {
                        // same as pointer, should error but cannot
                    }
                }
                wl_seat::Request::GetTouch { id: _ } => {
                    // TODO
                }
                wl_seat::Request::Release => {
                    // Our destructors already handle it
                }
            }
        },
        Some(move |seat| {
            dest_inner
                .lock()
                .unwrap()
                .known_seats
                .retain(|s| !s.equals(&seat));
        }),
        inner,
    )
}
<|endoftext|>


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

use std::cmp::Ordering;

use super::symbol_range::*;

///
/// A symbol map maps from one set of symbol ranges to another
///
pub struct SymbolMap<Symbol: PartialOrd+Clone> {
    // Ranges in this symbol map
    ranges: Vec<SymbolRange<Symbol>>,
}

impl<Symbol: PartialOrd+Clone> SymbolMap<Symbol> {
    ///
    /// Creates a new symbol map
    ///
    pub fn new() -> SymbolMap<Symbol> {
        SymbolMap { ranges: vec![] }
    }

    ///
    /// Orders two symbols (makes unordered symbols equal)
    ///
    #[inline]
    fn order_symbols(a: &Symbol, b: &Symbol) -> Ordering {
        if let Some(order) = a.partial_cmp(b) {
            order
        } else {
            Ordering::Equal
        }
    }

    ///
    /// Adds a range to those that are known about by this object
    ///
    pub fn add_range(&mut self, range: &SymbolRange<Symbol>) {
        let existing = self.ranges.binary_search_by(|test_range| { SymbolMap::order_symbols(&range.lowest, &test_range.lowest) });

        // Insert the range if it is not already in the map
        if let Err(insertion_pos) = existing {
            self.ranges.insert(insertion_pos, range.clone());
        }
    }

    ///
    /// Finds the ranges in this map that overlap the target ranges
    ///
    pub fn find_overlapping_ranges(&self, range: &SymbolRange<Symbol>) -> Vec<&SymbolRange<Symbol>> {
        let mut result = vec![];

        // Find the first range that matches (or the insertion position, which should be the first range wit a lowest value higher than the target range)
        let existing = self.ranges.binary_search_by(|test_range| { SymbolMap::order_symbols(&range.lowest, &test_range.lowest) });

        // Start returning values from here
        let mut pos = match existing {
            Ok(found_position) => found_position,
            Err(insert_position) => insert_position
        };

        // TODO: can we construct a set of ranges such that one is missed here? Think maybe we can
        while pos < self.ranges.len() && self.ranges[pos].lowest <= range.highest {
            result.push(&self.ranges[pos]);
            pos += 1;
        }

        result
    }

    ///
    /// Creates a non-overlapping range from an overlapping one
    ///
    pub fn to_non_overlapping_map(&self) -> SymbolMap<Symbol> {
        // Create a vector of all the range symbols
        let mut range_symbols: Vec<Symbol> = self.ranges.iter()
            .map(|r| r.lowest.clone())
            .chain(self.ranges.iter().map(|r| r.highest.clone()))
            .collect();

        // Sort and dedupe (we'll get pairs of ranges)
        range_symbols.sort_by(SymbolMap::order_symbols);
        range_symbols.dedup();

        // Generate a new symbol map with these ranges
        // They'll overlap with the last value, which should be OK if following the rules for adjacent ranges in symbol_range.rs
        let mut result = vec![];
        for index in 0..range_symbols.len()-1 {
            result.push(SymbolRange::new(range_symbols[index].clone(), range_symbols[index+1].clone()));
        }

        // We already sorted everything, so bypass the usual 'add' method (which sorts as it goes)
        SymbolMap { ranges: result }
    }
}

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

    #[test]
    fn can_lookup_overlapping_ranges() {
        let mut map = SymbolMap::new();

        map.add_range(&SymbolRange::new(0, 4));
        map.add_range(&SymbolRange::new(2, 5));
        map.add_range(&SymbolRange::new(3, 6));

        let bottom = map.find_overlapping_ranges(&SymbolRange::new(0, 1));
        let all    = map.find_overlapping_ranges(&SymbolRange::new(1, 3));
        let top    = map.find_overlapping_ranges(&SymbolRange::new(6, 6));

        assert!(bottom == vec![&SymbolRange::new(0, 4)]);
        assert!(all == vec![&SymbolRange::new(0, 4), &SymbolRange::new(2, 5), &SymbolRange::new(3, 6)]);
        assert!(top == vec![&SymbolRange::new(6, 6)]);
    }

    #[test]
    fn obeys_adjacency_rule() {
        let mut map = SymbolMap::new();

        // By the adjacency rule for symbol ranges (in symbol_range.rs), '4' here is only in the upper range
        map.add_range(&SymbolRange::new(0, 4));
        map.add_range(&SymbolRange::new(4, 8));

        let top = map.find_overlapping_ranges(&SymbolRange::new(4, 4));

        assert!(top == vec![&SymbolRange::new(4, 8)]);
    }

    #[test]
    fn can_get_non_overlapping_map() {
        let mut map = SymbolMap::new();

        map.add_range(&SymbolRange::new(0, 4));
        map.add_range(&SymbolRange::new(2, 5));
        map.add_range(&SymbolRange::new(3, 6));

        let non_overlapping = map.to_non_overlapping_map();

        let all = non_overlapping.find_overlapping_ranges(&SymbolRange::new(0, 6));

        assert!(all == vec![&SymbolRange::new(0, 2), &SymbolRange::new(2, 3), &SymbolRange::new(3, 6)]);
    }
}
<commit_msg>Make add_range work even if we find a value with a duplicate lower value<commit_after>//
//   Copyright 2016 Andrew Hunter
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//

use std::cmp::Ordering;

use super::symbol_range::*;

///
/// A symbol map maps from one set of symbol ranges to another
///
pub struct SymbolMap<Symbol: PartialOrd+Clone> {
    // Ranges in this symbol map
    ranges: Vec<SymbolRange<Symbol>>,
}

impl<Symbol: PartialOrd+Clone> SymbolMap<Symbol> {
    ///
    /// Creates a new symbol map
    ///
    pub fn new() -> SymbolMap<Symbol> {
        SymbolMap { ranges: vec![] }
    }

    ///
    /// Orders two symbols (makes unordered symbols equal)
    ///
    #[inline]
    fn order_symbols(a: &Symbol, b: &Symbol) -> Ordering {
        if let Some(order) = a.partial_cmp(b) {
            order
        } else {
            Ordering::Equal
        }
    }

    ///
    /// Adds a range to those that are known about by this object
    ///
    pub fn add_range(&mut self, range: &SymbolRange<Symbol>) {
        let existing = self.ranges.binary_search_by(|test_range| { SymbolMap::order_symbols(&range.lowest, &test_range.lowest) });

        // Insert the range if it is not already in the map
        if let Err(insertion_pos) = existing {
            self.ranges.insert(insertion_pos, range.clone());
        } else if let Ok(insertion_pos) = existing {
            self.ranges.insert(insertion_pos, range.clone());
        }
    }

    ///
    /// Finds the ranges in this map that overlap the target ranges
    ///
    pub fn find_overlapping_ranges(&self, range: &SymbolRange<Symbol>) -> Vec<&SymbolRange<Symbol>> {
        let mut result = vec![];

        // Find the first range that matches (or the insertion position, which should be the first range wit a lowest value higher than the target range)
        let existing = self.ranges.binary_search_by(|test_range| { SymbolMap::order_symbols(&range.lowest, &test_range.lowest) });

        // Start returning values from here
        let mut pos = match existing {
            Ok(found_position) => found_position,
            Err(insert_position) => insert_position
        };

        // TODO: can we construct a set of ranges such that one is missed here? Think maybe we can
        while pos < self.ranges.len() && self.ranges[pos].lowest <= range.highest {
            result.push(&self.ranges[pos]);
            pos += 1;
        }

        result
    }

    ///
    /// Creates a non-overlapping range from an overlapping one
    ///
    pub fn to_non_overlapping_map(&self) -> SymbolMap<Symbol> {
        // Create a vector of all the range symbols
        let mut range_symbols: Vec<Symbol> = self.ranges.iter()
            .map(|r| r.lowest.clone())
            .chain(self.ranges.iter().map(|r| r.highest.clone()))
            .collect();

        // Sort and dedupe (we'll get pairs of ranges)
        range_symbols.sort_by(SymbolMap::order_symbols);
        range_symbols.dedup();

        // Generate a new symbol map with these ranges
        // They'll overlap with the last value, which should be OK if following the rules for adjacent ranges in symbol_range.rs
        let mut result = vec![];
        for index in 0..range_symbols.len()-1 {
            result.push(SymbolRange::new(range_symbols[index].clone(), range_symbols[index+1].clone()));
        }

        // We already sorted everything, so bypass the usual 'add' method (which sorts as it goes)
        SymbolMap { ranges: result }
    }
}

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

    #[test]
    fn can_lookup_overlapping_ranges() {
        let mut map = SymbolMap::new();

        map.add_range(&SymbolRange::new(0, 4));
        map.add_range(&SymbolRange::new(2, 5));
        map.add_range(&SymbolRange::new(3, 6));

        let bottom = map.find_overlapping_ranges(&SymbolRange::new(0, 1));
        let all    = map.find_overlapping_ranges(&SymbolRange::new(1, 3));
        let top    = map.find_overlapping_ranges(&SymbolRange::new(6, 6));

        println!("All is: {:?}", all);

        assert!(bottom == vec![&SymbolRange::new(0, 4)]);
        assert!(all == vec![&SymbolRange::new(0, 4), &SymbolRange::new(2, 5), &SymbolRange::new(3, 6)]);
        assert!(top == vec![&SymbolRange::new(6, 6)]);
    }

    #[test]
    fn obeys_adjacency_rule() {
        let mut map = SymbolMap::new();

        // By the adjacency rule for symbol ranges (in symbol_range.rs), '4' here is only in the upper range
        map.add_range(&SymbolRange::new(0, 4));
        map.add_range(&SymbolRange::new(4, 8));

        let top = map.find_overlapping_ranges(&SymbolRange::new(4, 4));

        assert!(top == vec![&SymbolRange::new(4, 8)]);
    }

    #[test]
    fn can_get_non_overlapping_map() {
        let mut map = SymbolMap::new();

        map.add_range(&SymbolRange::new(0, 4));
        map.add_range(&SymbolRange::new(2, 5));
        map.add_range(&SymbolRange::new(3, 6));

        let non_overlapping = map.to_non_overlapping_map();

        let all = non_overlapping.find_overlapping_ranges(&SymbolRange::new(0, 6));

        assert!(all == vec![&SymbolRange::new(0, 2), &SymbolRange::new(2, 3), &SymbolRange::new(3, 6)]);
    }
}
<|endoftext|>


<commit_before>// Copyright © 2017, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

extern crate bragi;
extern crate iron_test;
extern crate serde_json;
use super::BragiHandler;
use super::get_value;

pub fn bragi_stops_test(es_wrapper: ::ElasticSearchWrapper) {
    let bragi = BragiHandler::new(format!("{}/munin", es_wrapper.host()));

    // ******************************************
    // we the OSM dataset, three-cities bano dataset and a stop file
    // the current dataset are thus (load order matters):
    // - osm_fixture.osm.pbf
    // - bano-three_cities
    // - stops.txt
    // ******************************************
    let osm2mimir = concat!(env!("OUT_DIR"), "/../../../osm2mimir");
    ::launch_and_assert(osm2mimir,
                        vec!["--input=./tests/fixtures/osm_fixture.osm.pbf".into(),
                             "--level=8".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    let bano2mimir = concat!(env!("OUT_DIR"), "/../../../bano2mimir");
    ::launch_and_assert(bano2mimir,
                        vec!["--input=./tests/fixtures/bano-three_cities.csv".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    let stops2mimir = concat!(env!("OUT_DIR"), "/../../../stops2mimir");
    ::launch_and_assert(stops2mimir,
                        vec!["--input=./tests/fixtures/stops.txt".into(),
                             "--dataset=dataset1".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    stop_attached_to_admin_test(&bragi);
    stop_no_admin_test(&bragi);

    let stops2mimir = concat!(env!("OUT_DIR"), "/../../../stops2mimir");
    ::launch_and_assert(stops2mimir,
                        vec!["--input=./tests/fixtures/stops_dataset2.txt".into(),
                             "--dataset=dataset2".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    stop_filtered_by_dataset_test(&bragi);
    stop_all_data_test(&bragi);
    stop_order_by_weight_test(&bragi);
}

fn stop_attached_to_admin_test(bragi: &BragiHandler) {
    // with this query we should find only one response, a stop
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=true");
    assert_eq!(response.len(), 1);
    let stop = response.first().unwrap();

    assert_eq!(get_value(stop, "type"), "public_transport:stop_area");
    assert_eq!(get_value(stop, "label"), "14 Juillet (Vaux-le-Pénil)");
    assert_eq!(get_value(stop, "name"), "14 Juillet");
    assert_eq!(get_value(stop, "id"), "stop_area:SA:second_station");
    assert_eq!(get_value(stop, "citycode"), "77487");
    assert_eq!(get_value(stop, "postcode"), "77000");

    // this stop area is in the boundary of the admin 'Vaux-le-Pénil',
    // it should have been associated to it
    assert_eq!(get_value(stop, "city"), "Vaux-le-Pénil");
    let admins = stop.get("administrative_regions").and_then(|a| a.as_array());
    assert_eq!(admins.map(|a| a.len()).unwrap_or(0), 1);
}

fn stop_no_admin_test(bragi: &BragiHandler) {
    // we query another stop, but this one is outside the range of an admin,
    // we should get the stop, but with no admin attached to it
    let response = bragi.get("/autocomplete?q=Far west station&_all_data=true");
    assert_eq!(response.len(), 1);
    let stop = response.first().unwrap();

    assert_eq!(get_value(stop, "type"), "public_transport:stop_area");
    assert_eq!(get_value(stop, "label"), "Far west station");
    assert_eq!(get_value(stop, "name"), "Far west station");
    assert_eq!(get_value(stop, "id"), "stop_area:SA:station_no_city");
    assert_eq!(get_value(stop, "city"), "");
    let admins = stop.get("administrative_regions").and_then(|a| a.as_array());
    assert_eq!(admins.map(|a| a.len()).unwrap_or(0), 0);
}

fn stop_filtered_by_dataset_test(bragi: &BragiHandler) {
    // Search stops on all aliases
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=true");
    assert_eq!(response.len(), 2);

    let stop = response.first().unwrap();
    assert_eq!(get_value(stop, "id"), "stop_area:SA:second_station");

    let stop = response.last().unwrap();
    assert_eq!(get_value(stop, "id"),
               "stop_area:SA:second_station:dataset2");

    // filter by dataset1
    let response = bragi.get("/autocomplete?q=14 juillet&pt_dataset=dataset1");

    assert_eq!(response.len(), 1);

    let stop = response.first().unwrap();
    assert_eq!(get_value(stop, "id"), "stop_area:SA:second_station");
    // filter by dataset2
    let response = bragi.get("/autocomplete?q=14 juillet&pt_dataset=dataset2");

    assert_eq!(response.len(), 1);

    let stop = response.first().unwrap();
    assert_eq!(get_value(stop, "id"),
               "stop_area:SA:second_station:dataset2");
}

fn stop_all_data_test(bragi: &BragiHandler) {
    // search without _all_data, default value : _all_data = false
    let response = bragi.get("/autocomplete?q=14 juillet");
    assert_eq!(response.len(), 0);

    // search wiht _all_data = false
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=false");
    assert_eq!(response.len(), 0);

    // search wiht _all_data = true
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=true");
    assert_eq!(response.len(), 2);
}

fn stop_order_by_weight_test(bragi: &BragiHandler) {
    // The StopAreas are sorted by weight. stop_area:SA:weight_3_station having weight 3
    // will be the first element in the result where as stop_area:SA:weight_1_station will always be second.
    let response = bragi.get("/autocomplete?q=weight&_all_data=true");
    assert_eq!(response.len(), 2);

    let stop = response.first().unwrap();

    assert_eq!(get_value(stop, "type"), "public_transport:stop_area");
    assert_eq!(get_value(stop, "label"), "weight three");
    assert_eq!(get_value(stop, "name"), "weight three");
    assert_eq!(get_value(stop, "id"), "stop_area:SA:weight_3_station");
}
<commit_msg>mimir: formatting by fmt<commit_after>// Copyright © 2017, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

extern crate bragi;
extern crate iron_test;
extern crate serde_json;
use super::BragiHandler;
use super::get_value;

pub fn bragi_stops_test(es_wrapper: ::ElasticSearchWrapper) {
    let bragi = BragiHandler::new(format!("{}/munin", es_wrapper.host()));

    // ******************************************
    // we the OSM dataset, three-cities bano dataset and a stop file
    // the current dataset are thus (load order matters):
    // - osm_fixture.osm.pbf
    // - bano-three_cities
    // - stops.txt
    // ******************************************
    let osm2mimir = concat!(env!("OUT_DIR"), "/../../../osm2mimir");
    ::launch_and_assert(osm2mimir,
                        vec!["--input=./tests/fixtures/osm_fixture.osm.pbf".into(),
                             "--level=8".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    let bano2mimir = concat!(env!("OUT_DIR"), "/../../../bano2mimir");
    ::launch_and_assert(bano2mimir,
                        vec!["--input=./tests/fixtures/bano-three_cities.csv".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    let stops2mimir = concat!(env!("OUT_DIR"), "/../../../stops2mimir");
    ::launch_and_assert(stops2mimir,
                        vec!["--input=./tests/fixtures/stops.txt".into(),
                             "--dataset=dataset1".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    stop_attached_to_admin_test(&bragi);
    stop_no_admin_test(&bragi);

    let stops2mimir = concat!(env!("OUT_DIR"), "/../../../stops2mimir");
    ::launch_and_assert(stops2mimir,
                        vec!["--input=./tests/fixtures/stops_dataset2.txt".into(),
                             "--dataset=dataset2".into(),
                             format!("--connection-string={}", es_wrapper.host())],
                        &es_wrapper);

    stop_filtered_by_dataset_test(&bragi);
    stop_all_data_test(&bragi);
    stop_order_by_weight_test(&bragi);
}

fn stop_attached_to_admin_test(bragi: &BragiHandler) {
    // with this query we should find only one response, a stop
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=true");
    assert_eq!(response.len(), 1);
    let stop = response.first().unwrap();

    assert_eq!(get_value(stop, "type"), "public_transport:stop_area");
    assert_eq!(get_value(stop, "label"), "14 Juillet (Vaux-le-Pénil)");
    assert_eq!(get_value(stop, "name"), "14 Juillet");
    assert_eq!(get_value(stop, "id"), "stop_area:SA:second_station");
    assert_eq!(get_value(stop, "citycode"), "77487");
    assert_eq!(get_value(stop, "postcode"), "77000");

    // this stop area is in the boundary of the admin 'Vaux-le-Pénil',
    // it should have been associated to it
    assert_eq!(get_value(stop, "city"), "Vaux-le-Pénil");
    let admins = stop.get("administrative_regions").and_then(|a| a.as_array());
    assert_eq!(admins.map(|a| a.len()).unwrap_or(0), 1);
}

fn stop_no_admin_test(bragi: &BragiHandler) {
    // we query another stop, but this one is outside the range of an admin,
    // we should get the stop, but with no admin attached to it
    let response = bragi.get("/autocomplete?q=Far west station&_all_data=true");
    assert_eq!(response.len(), 1);
    let stop = response.first().unwrap();

    assert_eq!(get_value(stop, "type"), "public_transport:stop_area");
    assert_eq!(get_value(stop, "label"), "Far west station");
    assert_eq!(get_value(stop, "name"), "Far west station");
    assert_eq!(get_value(stop, "id"), "stop_area:SA:station_no_city");
    assert_eq!(get_value(stop, "city"), "");
    let admins = stop.get("administrative_regions").and_then(|a| a.as_array());
    assert_eq!(admins.map(|a| a.len()).unwrap_or(0), 0);
}

fn stop_filtered_by_dataset_test(bragi: &BragiHandler) {
    // Search stops on all aliases
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=true");
    assert_eq!(response.len(), 2);

    let stop = response.first().unwrap();
    assert_eq!(get_value(stop, "id"), "stop_area:SA:second_station");

    let stop = response.last().unwrap();
    assert_eq!(get_value(stop, "id"),
               "stop_area:SA:second_station:dataset2");

    // filter by dataset1
    let response = bragi.get("/autocomplete?q=14 juillet&pt_dataset=dataset1");

    assert_eq!(response.len(), 1);

    let stop = response.first().unwrap();
    assert_eq!(get_value(stop, "id"), "stop_area:SA:second_station");
    // filter by dataset2
    let response = bragi.get("/autocomplete?q=14 juillet&pt_dataset=dataset2");

    assert_eq!(response.len(), 1);

    let stop = response.first().unwrap();
    assert_eq!(get_value(stop, "id"),
               "stop_area:SA:second_station:dataset2");
}

fn stop_all_data_test(bragi: &BragiHandler) {
    // search without _all_data, default value : _all_data = false
    let response = bragi.get("/autocomplete?q=14 juillet");
    assert_eq!(response.len(), 0);

    // search wiht _all_data = false
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=false");
    assert_eq!(response.len(), 0);

    // search wiht _all_data = true
    let response = bragi.get("/autocomplete?q=14 juillet&_all_data=true");
    assert_eq!(response.len(), 2);
}

fn stop_order_by_weight_test(bragi: &BragiHandler) {
    // The StopAreas are sorted by weight. stop_area:SA:weight_3_station having weight 3
    // will be the first element in the result where as stop_area:SA:weight_1_station will
    //always be second.
    let response = bragi.get("/autocomplete?q=weight&_all_data=true");
    assert_eq!(response.len(), 2);

    let stop = response.first().unwrap();

    assert_eq!(get_value(stop, "type"), "public_transport:stop_area");
    assert_eq!(get_value(stop, "label"), "weight three");
    assert_eq!(get_value(stop, "name"), "weight three");
    assert_eq!(get_value(stop, "id"), "stop_area:SA:weight_3_station");
}
<|endoftext|>