<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Ben Eills <ben@beneills.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (ToDO) rwxr sourcepath targetpath

extern crate clap;
extern crate libc;

mod mode;

#[macro_use]
extern crate uucore;

use clap::{App, Arg, ArgMatches};
use std::fs;
use std::path::{Path, PathBuf};
use std::result::Result;

const DEFAULT_MODE: u32 = 0o755;

#[allow(dead_code)]
pub struct Behavior {
    main_function: MainFunction,
    specified_mode: Option<u32>,
    suffix: String,
    verbose: bool,
}

#[derive(Clone, Eq, PartialEq)]
pub enum MainFunction {
    /// Create directories
    Directory,
    /// Install files to locations (primary functionality)
    Standard,
}

impl Behavior {
    /// Determine the mode for chmod after copy.
    pub fn mode(&self) -> u32 {
        match self.specified_mode {
            Some(x) => x,
            None => DEFAULT_MODE,
        }
    }
}

static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
 DIRECTORY, while setting permission modes and owner/group";
static VERSION: &str = env!("CARGO_PKG_VERSION");

static OPT_COMPARE: &str = "compare";
static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2";
static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored";
static OPT_CREATED: &str = "created";
static OPT_GROUP: &str = "group";
static OPT_MODE: &str = "mode";
static OPT_OWNER: &str = "owner";
static OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps";
static OPT_STRIP: &str = "strip";
static OPT_STRIP_PROGRAM: &str = "strip-program";
static OPT_SUFFIX: &str = "suffix";
static OPT_TARGET_DIRECTORY: &str = "target-directory";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_VERBOSE: &str = "verbose";
static OPT_PRESERVE_CONTEXT: &str = "preserve-context";
static OPT_CONTEXT: &str = "context";

static ARG_FILES: &str = "files";

fn get_usage() -> String {
    format!("{0} [OPTION]... [FILE]...", executable!())
}

/// Main install utility function, called from main.rs.
///
/// Returns a program return code.
///
pub fn uumain(args: impl uucore::Args) -> i32 {
    let usage = get_usage();

    let matches = App::new(executable!())
        .version(VERSION)
        .about(ABOUT)
        .usage(&usage[..])
        .arg(
                Arg::with_name(OPT_BACKUP)
                .long(OPT_BACKUP)
                .help("(unimplemented) make a backup of each existing destination file")
                .value_name("CONTROL")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_BACKUP_2)
            .short("b")
            .help("(unimplemented) like --backup but does not accept an argument")
        )
        .arg(
            Arg::with_name(OPT_IGNORED)
            .short("c")
            .help("ignored")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_COMPARE)
            .short("C")
            .long(OPT_COMPARE)
            .help("(unimplemented) compare each pair of source and destination files, and in some cases, do not modify the destination at all")
        )
        .arg(
            Arg::with_name(OPT_DIRECTORY)
                .short("d")
                .long(OPT_DIRECTORY)
                .help("treat all arguments as directory names. create all components of the specified directories")
        )

        .arg(
            // TODO implement flag
            Arg::with_name(OPT_CREATED)
                .short("D")
                .help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_GROUP)
                .short("g")
                .long(OPT_GROUP)
                .help("(unimplemented) set group ownership, instead of process's current group")
                .value_name("GROUP")
        )
        .arg(
            Arg::with_name(OPT_MODE)
                .short("m")
                .long(OPT_MODE)
                .help("set permission mode (as in chmod), instead of rwxr-xr-x")
                .value_name("MODE")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_OWNER)
                .short("o")
                .long(OPT_OWNER)
                .help("(unimplemented) set ownership (super-user only)")
                .value_name("OWNER")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_PRESERVE_TIMESTAMPS)
                .short("p")
                .long(OPT_PRESERVE_TIMESTAMPS)
                .help("(unimplemented) apply access/modification times of SOURCE files to corresponding destination files")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_STRIP)
            .short("s")
            .long(OPT_STRIP)
            .help("(unimplemented) strip symbol tables")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_STRIP_PROGRAM)
                .long(OPT_STRIP_PROGRAM)
                .help("(unimplemented) program used to strip binaries")
                .value_name("PROGRAM")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_SUFFIX)
                .short("S")
                .long(OPT_SUFFIX)
                .help("(unimplemented) override the usual backup suffix")
                .value_name("SUFFIX")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_TARGET_DIRECTORY)
                .short("t")
                .long(OPT_TARGET_DIRECTORY)
                .help("(unimplemented) move all SOURCE arguments into DIRECTORY")
                .value_name("DIRECTORY")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_NO_TARGET_DIRECTORY)
                .short("T")
                .long(OPT_NO_TARGET_DIRECTORY)
                .help("(unimplemented) treat DEST as a normal file")

        )
        .arg(
            Arg::with_name(OPT_VERBOSE)
            .short("v")
            .long(OPT_VERBOSE)
            .help("explain what is being done")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_PRESERVE_CONTEXT)
                .short("P")
                .long(OPT_PRESERVE_CONTEXT)
                .help("(unimplemented) preserve security context")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_CONTEXT)
                .short("Z")
                .long(OPT_CONTEXT)
                .help("(unimplemented) set security context of files and directories")
                .value_name("CONTEXT")
        )
        .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
        .get_matches_from(args);

    let paths: Vec<String> = matches
        .values_of(ARG_FILES)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    if let Err(s) = check_unimplemented(&matches) {
        show_error!("Unimplemented feature: {}", s);
        return 2;
    }

    let behavior = match behavior(&matches) {
        Ok(x) => x,
        Err(ret) => {
            return ret;
        }
    };

    match behavior.main_function {
        MainFunction::Directory => directory(paths, behavior),
        MainFunction::Standard => standard(paths, behavior),
    }
}

/// Check for unimplemented command line arguments.
///
/// Either return the degenerate Ok value, or an Err with string.
///
/// # Errors
///
/// Error datum is a string of the unimplemented argument.
///
///
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
    if matches.is_present(OPT_BACKUP) {
        Err("--backup")
    } else if matches.is_present(OPT_BACKUP_2) {
        Err("-b")
    } else if matches.is_present(OPT_COMPARE) {
        Err("--compare, -C")
    } else if matches.is_present(OPT_CREATED) {
        Err("-D")
    } else if matches.is_present(OPT_GROUP) {
        Err("--group, -g")
    } else if matches.is_present(OPT_OWNER) {
        Err("--owner, -o")
    } else if matches.is_present(OPT_PRESERVE_TIMESTAMPS) {
        Err("--preserve-timestamps, -p")
    } else if matches.is_present(OPT_STRIP) {
        Err("--strip, -s")
    } else if matches.is_present(OPT_STRIP_PROGRAM) {
        Err("--strip-program")
    } else if matches.is_present(OPT_SUFFIX) {
        Err("--suffix, -S")
    } else if matches.is_present(OPT_TARGET_DIRECTORY) {
        Err("--target-directory, -t")
    } else if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
        Err("--no-target-directory, -T")
    } else if matches.is_present(OPT_PRESERVE_CONTEXT) {
        Err("--preserve-context, -P")
    } else if matches.is_present(OPT_CONTEXT) {
        Err("--context, -Z")
    } else {
        Ok(())
    }
}

/// Determine behavior, given command line arguments.
///
/// If successful, returns a filled-out Behavior struct.
///
/// # Errors
///
/// In event of failure, returns an integer intended as a program return code.
///
fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
    let main_function = if matches.is_present("directory") {
        MainFunction::Directory
    } else {
        MainFunction::Standard
    };

    let considering_dir: bool = MainFunction::Directory == main_function;

    let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
        match matches.value_of(OPT_MODE) {
            Some(x) => match mode::parse(&x[..], considering_dir) {
                Ok(y) => Some(y),
                Err(err) => {
                    show_error!("Invalid mode string: {}", err);
                    return Err(1);
                }
            },
            None => {
                show_error!(
                    "option '--mode' requires an argument\n \
                     Try '{} --help' for more information.",
                    executable!()
                );
                return Err(1);
            }
        }
    } else {
        None
    };

    let backup_suffix = if matches.is_present(OPT_SUFFIX) {
        match matches.value_of(OPT_SUFFIX) {
            Some(x) => x,
            None => {
                show_error!(
                    "option '--suffix' requires an argument\n\
                     Try '{} --help' for more information.",
                    executable!()
                );
                return Err(1);
            }
        }
    } else {
        "~"
    };

    Ok(Behavior {
        main_function,
        specified_mode,
        suffix: backup_suffix.to_string(),
        verbose: matches.is_present(OPT_VERBOSE),
    })
}

/// Creates directories.
///
/// GNU man pages describe this functionality as creating 'all components of
/// the specified directories'.
///
/// Returns an integer intended as a program return code.
///
fn directory(paths: Vec<String>, b: Behavior) -> i32 {
    if paths.is_empty() {
        println!("{} with -d requires at least one argument.", executable!());
        1
    } else {
        let mut all_successful = true;

        for directory in paths.iter() {
            let path = Path::new(directory);

            if path.exists() {
                show_info!("cannot create directory '{}': File exists", path.display());
                all_successful = false;
            }

            if let Err(e) = fs::create_dir(directory) {
                show_info!("{}: {}", path.display(), e.to_string());
                all_successful = false;
            }

            if mode::chmod(&path, b.mode()).is_err() {
                all_successful = false;
            }

            if b.verbose {
                show_info!("created directory '{}'", path.display());
            }
        }
        if all_successful {
            0
        } else {
            1
        }
    }
}

/// Test if the path is a a new file path that can be
/// created immediately
fn is_new_file_path(path: &Path) -> bool {
    path.is_file() || !path.exists() && path.parent().map(Path::is_dir).unwrap_or(true)
}

/// Perform an install, given a list of paths and behavior.
///
/// Returns an integer intended as a program return code.
///
fn standard(paths: Vec<String>, b: Behavior) -> i32 {
    if paths.len() < 2 {
        println!("{} requires at least 2 arguments.", executable!());
        1
    } else {
        let sources = &paths[0..paths.len() - 1]
            .iter()
            .map(PathBuf::from)
            .collect::<Vec<_>>();
        let target = Path::new(paths.last().unwrap());

        if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 {
            copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
        } else {
            copy_files_into_dir(sources, &target.to_path_buf(), &b)
        }
    }
}

/// Copy some files into a directory.
///
/// Prints verbose information and error messages.
/// Returns an integer intended as a program return code.
///
/// # Parameters
///
/// _files_ must all exist as non-directories.
/// _target_dir_ must be a directory.
///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 {
    if !target_dir.is_dir() {
        show_error!("target ‘{}’ is not a directory", target_dir.display());
        return 1;
    }

    let mut all_successful = true;
    for sourcepath in files.iter() {
        let targetpath = match sourcepath.as_os_str().to_str() {
            Some(name) => target_dir.join(name),
            None => {
                show_error!(
                    "cannot stat ‘{}’: No such file or directory",
                    sourcepath.display()
                );

                all_successful = false;
                continue;
            }
        };

        if copy(sourcepath, &targetpath, b).is_err() {
            all_successful = false;
        }
    }
    if all_successful {
        0
    } else {
        1
    }
}

/// Copy a file to another file.
///
/// Prints verbose information and error messages.
/// Returns an integer intended as a program return code.
///
/// # Parameters
///
/// _file_ must exist as a non-directory.
/// _target_ must be a non-directory
///
fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 {
    if copy(file, &target, b).is_err() {
        1
    } else {
        0
    }
}

/// Copy one file to a new location, changing metadata.
///
/// # Parameters
///
/// _from_ must exist as a non-directory.
/// _to_ must be a non-existent file, whose parent directory exists.
///
/// # Errors
///
/// If the copy system call fails, we print a verbose error and return an empty error value.
///
fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
    let io_result = fs::copy(from, to);

    if let Err(err) = io_result {
        show_error!(
            "install: cannot install ‘{}’ to ‘{}’: {}",
            from.display(),
            to.display(),
            err
        );
        return Err(());
    }

    if mode::chmod(&to, b.mode()).is_err() {
        return Err(());
    }

    if b.verbose {
        show_info!("'{}' -> '{}'", from.display(), to.display());
    }

    Ok(())
}
<commit_msg>install: fix a typo<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Ben Eills <ben@beneills.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (ToDO) rwxr sourcepath targetpath

extern crate clap;
extern crate libc;

mod mode;

#[macro_use]
extern crate uucore;

use clap::{App, Arg, ArgMatches};
use std::fs;
use std::path::{Path, PathBuf};
use std::result::Result;

const DEFAULT_MODE: u32 = 0o755;

#[allow(dead_code)]
pub struct Behavior {
    main_function: MainFunction,
    specified_mode: Option<u32>,
    suffix: String,
    verbose: bool,
}

#[derive(Clone, Eq, PartialEq)]
pub enum MainFunction {
    /// Create directories
    Directory,
    /// Install files to locations (primary functionality)
    Standard,
}

impl Behavior {
    /// Determine the mode for chmod after copy.
    pub fn mode(&self) -> u32 {
        match self.specified_mode {
            Some(x) => x,
            None => DEFAULT_MODE,
        }
    }
}

static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
 DIRECTORY, while setting permission modes and owner/group";
static VERSION: &str = env!("CARGO_PKG_VERSION");

static OPT_COMPARE: &str = "compare";
static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2";
static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored";
static OPT_CREATED: &str = "created";
static OPT_GROUP: &str = "group";
static OPT_MODE: &str = "mode";
static OPT_OWNER: &str = "owner";
static OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps";
static OPT_STRIP: &str = "strip";
static OPT_STRIP_PROGRAM: &str = "strip-program";
static OPT_SUFFIX: &str = "suffix";
static OPT_TARGET_DIRECTORY: &str = "target-directory";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_VERBOSE: &str = "verbose";
static OPT_PRESERVE_CONTEXT: &str = "preserve-context";
static OPT_CONTEXT: &str = "context";

static ARG_FILES: &str = "files";

fn get_usage() -> String {
    format!("{0} [OPTION]... [FILE]...", executable!())
}

/// Main install utility function, called from main.rs.
///
/// Returns a program return code.
///
pub fn uumain(args: impl uucore::Args) -> i32 {
    let usage = get_usage();

    let matches = App::new(executable!())
        .version(VERSION)
        .about(ABOUT)
        .usage(&usage[..])
        .arg(
                Arg::with_name(OPT_BACKUP)
                .long(OPT_BACKUP)
                .help("(unimplemented) make a backup of each existing destination file")
                .value_name("CONTROL")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_BACKUP_2)
            .short("b")
            .help("(unimplemented) like --backup but does not accept an argument")
        )
        .arg(
            Arg::with_name(OPT_IGNORED)
            .short("c")
            .help("ignored")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_COMPARE)
            .short("C")
            .long(OPT_COMPARE)
            .help("(unimplemented) compare each pair of source and destination files, and in some cases, do not modify the destination at all")
        )
        .arg(
            Arg::with_name(OPT_DIRECTORY)
                .short("d")
                .long(OPT_DIRECTORY)
                .help("treat all arguments as directory names. create all components of the specified directories")
        )

        .arg(
            // TODO implement flag
            Arg::with_name(OPT_CREATED)
                .short("D")
                .help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_GROUP)
                .short("g")
                .long(OPT_GROUP)
                .help("(unimplemented) set group ownership, instead of process's current group")
                .value_name("GROUP")
        )
        .arg(
            Arg::with_name(OPT_MODE)
                .short("m")
                .long(OPT_MODE)
                .help("set permission mode (as in chmod), instead of rwxr-xr-x")
                .value_name("MODE")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_OWNER)
                .short("o")
                .long(OPT_OWNER)
                .help("(unimplemented) set ownership (super-user only)")
                .value_name("OWNER")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_PRESERVE_TIMESTAMPS)
                .short("p")
                .long(OPT_PRESERVE_TIMESTAMPS)
                .help("(unimplemented) apply access/modification times of SOURCE files to corresponding destination files")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_STRIP)
            .short("s")
            .long(OPT_STRIP)
            .help("(unimplemented) strip symbol tables")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_STRIP_PROGRAM)
                .long(OPT_STRIP_PROGRAM)
                .help("(unimplemented) program used to strip binaries")
                .value_name("PROGRAM")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_SUFFIX)
                .short("S")
                .long(OPT_SUFFIX)
                .help("(unimplemented) override the usual backup suffix")
                .value_name("SUFFIX")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_TARGET_DIRECTORY)
                .short("t")
                .long(OPT_TARGET_DIRECTORY)
                .help("(unimplemented) move all SOURCE arguments into DIRECTORY")
                .value_name("DIRECTORY")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_NO_TARGET_DIRECTORY)
                .short("T")
                .long(OPT_NO_TARGET_DIRECTORY)
                .help("(unimplemented) treat DEST as a normal file")

        )
        .arg(
            Arg::with_name(OPT_VERBOSE)
            .short("v")
            .long(OPT_VERBOSE)
            .help("explain what is being done")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_PRESERVE_CONTEXT)
                .short("P")
                .long(OPT_PRESERVE_CONTEXT)
                .help("(unimplemented) preserve security context")
        )
        .arg(
            // TODO implement flag
            Arg::with_name(OPT_CONTEXT)
                .short("Z")
                .long(OPT_CONTEXT)
                .help("(unimplemented) set security context of files and directories")
                .value_name("CONTEXT")
        )
        .arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
        .get_matches_from(args);

    let paths: Vec<String> = matches
        .values_of(ARG_FILES)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    if let Err(s) = check_unimplemented(&matches) {
        show_error!("Unimplemented feature: {}", s);
        return 2;
    }

    let behavior = match behavior(&matches) {
        Ok(x) => x,
        Err(ret) => {
            return ret;
        }
    };

    match behavior.main_function {
        MainFunction::Directory => directory(paths, behavior),
        MainFunction::Standard => standard(paths, behavior),
    }
}

/// Check for unimplemented command line arguments.
///
/// Either return the degenerate Ok value, or an Err with string.
///
/// # Errors
///
/// Error datum is a string of the unimplemented argument.
///
///
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
    if matches.is_present(OPT_BACKUP) {
        Err("--backup")
    } else if matches.is_present(OPT_BACKUP_2) {
        Err("-b")
    } else if matches.is_present(OPT_COMPARE) {
        Err("--compare, -C")
    } else if matches.is_present(OPT_CREATED) {
        Err("-D")
    } else if matches.is_present(OPT_GROUP) {
        Err("--group, -g")
    } else if matches.is_present(OPT_OWNER) {
        Err("--owner, -o")
    } else if matches.is_present(OPT_PRESERVE_TIMESTAMPS) {
        Err("--preserve-timestamps, -p")
    } else if matches.is_present(OPT_STRIP) {
        Err("--strip, -s")
    } else if matches.is_present(OPT_STRIP_PROGRAM) {
        Err("--strip-program")
    } else if matches.is_present(OPT_SUFFIX) {
        Err("--suffix, -S")
    } else if matches.is_present(OPT_TARGET_DIRECTORY) {
        Err("--target-directory, -t")
    } else if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
        Err("--no-target-directory, -T")
    } else if matches.is_present(OPT_PRESERVE_CONTEXT) {
        Err("--preserve-context, -P")
    } else if matches.is_present(OPT_CONTEXT) {
        Err("--context, -Z")
    } else {
        Ok(())
    }
}

/// Determine behavior, given command line arguments.
///
/// If successful, returns a filled-out Behavior struct.
///
/// # Errors
///
/// In event of failure, returns an integer intended as a program return code.
///
fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
    let main_function = if matches.is_present("directory") {
        MainFunction::Directory
    } else {
        MainFunction::Standard
    };

    let considering_dir: bool = MainFunction::Directory == main_function;

    let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
        match matches.value_of(OPT_MODE) {
            Some(x) => match mode::parse(&x[..], considering_dir) {
                Ok(y) => Some(y),
                Err(err) => {
                    show_error!("Invalid mode string: {}", err);
                    return Err(1);
                }
            },
            None => {
                show_error!(
                    "option '--mode' requires an argument\n \
                     Try '{} --help' for more information.",
                    executable!()
                );
                return Err(1);
            }
        }
    } else {
        None
    };

    let backup_suffix = if matches.is_present(OPT_SUFFIX) {
        match matches.value_of(OPT_SUFFIX) {
            Some(x) => x,
            None => {
                show_error!(
                    "option '--suffix' requires an argument\n\
                     Try '{} --help' for more information.",
                    executable!()
                );
                return Err(1);
            }
        }
    } else {
        "~"
    };

    Ok(Behavior {
        main_function,
        specified_mode,
        suffix: backup_suffix.to_string(),
        verbose: matches.is_present(OPT_VERBOSE),
    })
}

/// Creates directories.
///
/// GNU man pages describe this functionality as creating 'all components of
/// the specified directories'.
///
/// Returns an integer intended as a program return code.
///
fn directory(paths: Vec<String>, b: Behavior) -> i32 {
    if paths.is_empty() {
        println!("{} with -d requires at least one argument.", executable!());
        1
    } else {
        let mut all_successful = true;

        for directory in paths.iter() {
            let path = Path::new(directory);

            if path.exists() {
                show_info!("cannot create directory '{}': File exists", path.display());
                all_successful = false;
            }

            if let Err(e) = fs::create_dir(directory) {
                show_info!("{}: {}", path.display(), e.to_string());
                all_successful = false;
            }

            if mode::chmod(&path, b.mode()).is_err() {
                all_successful = false;
            }

            if b.verbose {
                show_info!("created directory '{}'", path.display());
            }
        }
        if all_successful {
            0
        } else {
            1
        }
    }
}

/// Test if the path is a new file path that can be
/// created immediately
fn is_new_file_path(path: &Path) -> bool {
    path.is_file() || !path.exists() && path.parent().map(Path::is_dir).unwrap_or(true)
}

/// Perform an install, given a list of paths and behavior.
///
/// Returns an integer intended as a program return code.
///
fn standard(paths: Vec<String>, b: Behavior) -> i32 {
    if paths.len() < 2 {
        println!("{} requires at least 2 arguments.", executable!());
        1
    } else {
        let sources = &paths[0..paths.len() - 1]
            .iter()
            .map(PathBuf::from)
            .collect::<Vec<_>>();
        let target = Path::new(paths.last().unwrap());

        if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 {
            copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
        } else {
            copy_files_into_dir(sources, &target.to_path_buf(), &b)
        }
    }
}

/// Copy some files into a directory.
///
/// Prints verbose information and error messages.
/// Returns an integer intended as a program return code.
///
/// # Parameters
///
/// _files_ must all exist as non-directories.
/// _target_dir_ must be a directory.
///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 {
    if !target_dir.is_dir() {
        show_error!("target ‘{}’ is not a directory", target_dir.display());
        return 1;
    }

    let mut all_successful = true;
    for sourcepath in files.iter() {
        let targetpath = match sourcepath.as_os_str().to_str() {
            Some(name) => target_dir.join(name),
            None => {
                show_error!(
                    "cannot stat ‘{}’: No such file or directory",
                    sourcepath.display()
                );

                all_successful = false;
                continue;
            }
        };

        if copy(sourcepath, &targetpath, b).is_err() {
            all_successful = false;
        }
    }
    if all_successful {
        0
    } else {
        1
    }
}

/// Copy a file to another file.
///
/// Prints verbose information and error messages.
/// Returns an integer intended as a program return code.
///
/// # Parameters
///
/// _file_ must exist as a non-directory.
/// _target_ must be a non-directory
///
fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 {
    if copy(file, &target, b).is_err() {
        1
    } else {
        0
    }
}

/// Copy one file to a new location, changing metadata.
///
/// # Parameters
///
/// _from_ must exist as a non-directory.
/// _to_ must be a non-existent file, whose parent directory exists.
///
/// # Errors
///
/// If the copy system call fails, we print a verbose error and return an empty error value.
///
fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
    let io_result = fs::copy(from, to);

    if let Err(err) = io_result {
        show_error!(
            "install: cannot install ‘{}’ to ‘{}’: {}",
            from.display(),
            to.display(),
            err
        );
        return Err(());
    }

    if mode::chmod(&to, b.mode()).is_err() {
        return Err(());
    }

    if b.verbose {
        show_info!("'{}' -> '{}'", from.display(), to.display());
    }

    Ok(())
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

extern crate libc;

use crate::config::Config;
use crate::layout::{Layout, LayoutMsg};
use crate::xlib_window_system::XlibWindowSystem;
use crate::state::WmState;
use crate::workspace::MoveOp;
use self::libc::execvpe;
use std::{env, iter};
use std::ptr::null;
use std::ffi::CString;
use std::process::Command;
use std::path::Path;
use std::fs::OpenOptions;
use x11::xlib::Window;
use anyhow::{bail, Context, Result};

pub enum Cmd {
    Exec(String, Vec<String>),
    SwitchWorkspace(usize),
    SwitchScreen(usize),
    MoveToWorkspace(usize),
    MoveToScreen(usize),
    SendLayoutMsg(LayoutMsg),
    NestLayout(Box<dyn Fn() -> Box<dyn Layout>>),
    Reload,
    Exit,
    KillClient,
    FocusParentUp,
    FocusParentDown,
    FocusParentMaster,
    FocusUp,
    FocusDown,
    FocusMaster,
    SwapUp,
    SwapDown,
    SwapMaster,
    SwapParentUp,
    SwapParentDown,
    SwapParentMaster,
}

impl Cmd {
    pub fn call(&self, xws: &XlibWindowSystem, state: &mut WmState, config: &Config) -> Result<()> {
        match self {
            Cmd::Exec(ref cmd, ref args) => {
                debug!("Cmd::Exec: {} {:?}", cmd, args);
                exec(cmd.clone(), args.clone());
            }
            Cmd::SwitchWorkspace(index) => {
                debug!("Cmd::SwitchWorkspace: {}", index);
                state.switch_to_ws(xws, config, index - 1, true);
            }
            Cmd::SwitchScreen(screen) => {
                debug!("Cmd::SwitchScreen: {}", screen);
                state.switch_to_screen(xws, config, screen - 1);
            }
            Cmd::MoveToWorkspace(index) => {
                debug!("Cmd::MoveToWorkspace: {}", index);
                state.move_window_to(xws, config, index - 1);
            }
            Cmd::MoveToScreen(screen) => {
                debug!("Cmd::MoveToScreen: {}", screen);
                state.move_window_to_screen(xws, config, screen - 1);
            }
            Cmd::SendLayoutMsg(ref msg) => {
                debug!("Cmd::SendLayoutMsg::{:?}", msg);
                state.current_ws_mut().send_layout_message(msg.clone());
                state.current_ws().redraw(xws, config, state.get_screens());
            }
            Cmd::NestLayout(layout_fn) => {
                let layout = layout_fn();
                debug!("Cmd::NestLayout: {}", layout.name());
                state.current_ws_mut().nest_layout(layout);
            }
            Cmd::Reload => {
                debug!("Cmd::Reload");
                reload(state)
                    .context("failed to reload xr3wm")?;
            }
            Cmd::Exit => {
                debug!("Cmd::Exit");
                xws.close();
            }
            Cmd::KillClient => {
                if let Some(window) = state.current_ws().focused_window() {
                    debug!("Cmd::KillClient: {:#x}", window);
                    xws.kill_window(window);
                }
            }
            Cmd::FocusUp | Cmd::FocusDown | Cmd::FocusMaster | Cmd::FocusParentUp | Cmd::FocusParentDown | Cmd::FocusParentMaster => {
                if let Some(window) = state.current_ws().focused_window() {
                    let workspace = state.current_ws_mut();
                    let new_focus = match self {
                        Cmd::FocusUp => {
                            debug!("Cmd::FocusUp: {:#x}", window);
                            workspace.move_focus(MoveOp::Up)
                        }
                        Cmd::FocusDown => {
                            debug!("Cmd::FocusDown: {:#x}", window);
                            workspace.move_focus(MoveOp::Down)
                        }
                        Cmd::FocusMaster => {
                            debug!("Cmd::FocusMaster: {:#x}", window);
                            workspace.move_focus(MoveOp::Swap)
                        }
                        Cmd::FocusParentUp => {
                            debug!("Cmd::FocusParentUp: {:#x}", window);
                            workspace.move_parent_focus(MoveOp::Up)
                        }
                        Cmd::FocusParentDown => {
                            debug!("Cmd::FocusParentDown: {:#x}", window);
                            workspace.move_parent_focus(MoveOp::Down)
                        }
                        Cmd::FocusParentMaster => {
                            debug!("Cmd::FocusParentMaster: {:#x}", window);
                            workspace.move_parent_focus(MoveOp::Swap)
                        }
                        _ => None
                    };

                    if let Some(window) = new_focus {
                        xws.focus_window(window);
                    }
                }
            },
            Cmd::SwapUp | Cmd::SwapDown | Cmd::SwapMaster | Cmd::SwapParentUp | Cmd::SwapParentDown | Cmd::SwapParentMaster => {
                if let Some(window) = state.current_ws().focused_window() {
                    let workspace = state.current_ws_mut();
                    let new_focus = match self {
                        Cmd::SwapUp => {
                            debug!("Cmd::SwapUp: {:#x}", window);
                            workspace.move_window(MoveOp::Up)
                        }
                        Cmd::SwapDown => {
                            debug!("Cmd::SwapDown: {:#x}", window);
                            workspace.move_window(MoveOp::Down)
                        }
                        Cmd::SwapMaster => {
                            debug!("Cmd::SwapMaster: {:#x}", window);
                            workspace.move_window(MoveOp::Swap)
                        }
                        Cmd::SwapParentUp => {
                            debug!("Cmd::SwapParentUp: {:#x}", window);
                            workspace.move_parent_window(MoveOp::Up)
                        }
                        Cmd::SwapParentDown => {
                            debug!("Cmd::SwapParentDown: {:#x}", window);
                            workspace.move_parent_window(MoveOp::Down)
                        }
                        Cmd::SwapParentMaster => {
                            debug!("Cmd::SwapParentMaster: {:#x}", window);
                            workspace.move_parent_window(MoveOp::Swap)
                        }
                        _ => false
                    };

                    if new_focus {
                        state.current_ws().redraw(xws, config, state.get_screens());
                    }
                }
            }
        }
        Ok(())
    }
}

fn reload(state: &WmState) -> Result<()> {
    info!("recompiling...");

    let config_build_dir = concat!(env!("HOME"), "/.xr3wm/.build");
    let mut cmd = Command::new("cargo");

    let output = cmd.current_dir(&config_build_dir)
        .arg("build")
        .env("RUST_LOG", "none")
        .output()
        .context("failed to run cargo")?;

    if !output.status.success() {
        let stderr_msg = String::from_utf8(output.stderr)
            .context("failed to convert cargo stderr to UTF-8")?;
        bail!("failed to recompile: {}", stderr_msg)
    }

    debug!("Cmd::Reload: restarting xr3wm...");

    let path = Path::new(concat!(env!("HOME"), "/.xr3wm/.tmp"));

    // save current workspace states to restore on restart
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(&path)
        .context("failed to open workspace state tmp file")?;

    serde_json::to_writer(file, state)
        .context("failed to serialize workspace states")?;

    let args: Vec<*const libc::c_char> = env::args()
        .filter_map(|x| CString::new(x).ok())
        .map(|x| x.into_raw() as *const libc::c_char)
        .chain(iter::once(null()))
        .collect();

    let envs: Vec<*const libc::c_char> = env::vars()
        .filter_map(|(k,v)| CString::new(format!("{}={}", k, v)).ok())
        .map(|x| x.into_raw() as *const libc::c_char)
        .chain(iter::once(null()))
        .collect();

    unsafe {
        execvpe(args[0] as *const libc::c_char, args.as_ptr(), envs.as_ptr());
        // execvp returns only if an error has occurred
        error!("failed to reload: {}", ::std::io::Error::last_os_error());
    }

    Ok(())
}

pub struct ManageHook {
    pub class_name: String,
    pub cmd: CmdManage,
}

pub enum CmdManage {
    Move(usize),
    Float,
    Fullscreen,
    Ignore,
}

impl CmdManage {
    pub fn call(&self,
                xws: &XlibWindowSystem,
                state: &mut WmState,
                config: &Config,
                window: Window) {
        match *self {
            CmdManage::Move(index) => {
                debug!("CmdManage::Move: {}, {}", window, index);
                state.add_window(Some(index - 1), xws, config, window);
            }
            CmdManage::Float => {
                debug!("CmdManage::Float");
                unimplemented!()
            }
            CmdManage::Fullscreen => {
                debug!("CmdManage::Fullscreen");
                unimplemented!()
            }
            CmdManage::Ignore => {
                debug!("CmdManage::Ignore");
                unimplemented!()
            }
        }
    }
}

fn exec(cmd: String, args: Vec<String>) {
    if !cmd.is_empty() {
        let mut cmd = Command::new(cmd);

        if !args.is_empty() {
            cmd.args(&args);
        }

        match cmd.envs(env::vars()).spawn() {
            Ok(_) => (),
            _ => panic!("failed to start \"{:?}\"", cmd),
        }
    }
}
<commit_msg>Print error msg instead of panicing on exec error<commit_after>#![allow(dead_code)]

extern crate libc;

use crate::config::Config;
use crate::layout::{Layout, LayoutMsg};
use crate::xlib_window_system::XlibWindowSystem;
use crate::state::WmState;
use crate::workspace::MoveOp;
use self::libc::execvpe;
use std::{env, iter};
use std::ptr::null;
use std::ffi::CString;
use std::process::Command;
use std::path::Path;
use std::fs::OpenOptions;
use x11::xlib::Window;
use anyhow::{bail, Context, Result};

pub enum Cmd {
    Exec(String, Vec<String>),
    SwitchWorkspace(usize),
    SwitchScreen(usize),
    MoveToWorkspace(usize),
    MoveToScreen(usize),
    SendLayoutMsg(LayoutMsg),
    NestLayout(Box<dyn Fn() -> Box<dyn Layout>>),
    Reload,
    Exit,
    KillClient,
    FocusParentUp,
    FocusParentDown,
    FocusParentMaster,
    FocusUp,
    FocusDown,
    FocusMaster,
    SwapUp,
    SwapDown,
    SwapMaster,
    SwapParentUp,
    SwapParentDown,
    SwapParentMaster,
}

impl Cmd {
    pub fn call(&self, xws: &XlibWindowSystem, state: &mut WmState, config: &Config) -> Result<()> {
        match self {
            Cmd::Exec(ref cmd, ref args) => {
                debug!("Cmd::Exec: {} {:?}", cmd, args);
                exec(cmd.clone(), args.clone());
            }
            Cmd::SwitchWorkspace(index) => {
                debug!("Cmd::SwitchWorkspace: {}", index);
                state.switch_to_ws(xws, config, index - 1, true);
            }
            Cmd::SwitchScreen(screen) => {
                debug!("Cmd::SwitchScreen: {}", screen);
                state.switch_to_screen(xws, config, screen - 1);
            }
            Cmd::MoveToWorkspace(index) => {
                debug!("Cmd::MoveToWorkspace: {}", index);
                state.move_window_to(xws, config, index - 1);
            }
            Cmd::MoveToScreen(screen) => {
                debug!("Cmd::MoveToScreen: {}", screen);
                state.move_window_to_screen(xws, config, screen - 1);
            }
            Cmd::SendLayoutMsg(ref msg) => {
                debug!("Cmd::SendLayoutMsg::{:?}", msg);
                state.current_ws_mut().send_layout_message(msg.clone());
                state.current_ws().redraw(xws, config, state.get_screens());
            }
            Cmd::NestLayout(layout_fn) => {
                let layout = layout_fn();
                debug!("Cmd::NestLayout: {}", layout.name());
                state.current_ws_mut().nest_layout(layout);
            }
            Cmd::Reload => {
                debug!("Cmd::Reload");
                reload(state)
                    .context("failed to reload xr3wm")?;
            }
            Cmd::Exit => {
                debug!("Cmd::Exit");
                xws.close();
            }
            Cmd::KillClient => {
                if let Some(window) = state.current_ws().focused_window() {
                    debug!("Cmd::KillClient: {:#x}", window);
                    xws.kill_window(window);
                }
            }
            Cmd::FocusUp | Cmd::FocusDown | Cmd::FocusMaster | Cmd::FocusParentUp | Cmd::FocusParentDown | Cmd::FocusParentMaster => {
                if let Some(window) = state.current_ws().focused_window() {
                    let workspace = state.current_ws_mut();
                    let new_focus = match self {
                        Cmd::FocusUp => {
                            debug!("Cmd::FocusUp: {:#x}", window);
                            workspace.move_focus(MoveOp::Up)
                        }
                        Cmd::FocusDown => {
                            debug!("Cmd::FocusDown: {:#x}", window);
                            workspace.move_focus(MoveOp::Down)
                        }
                        Cmd::FocusMaster => {
                            debug!("Cmd::FocusMaster: {:#x}", window);
                            workspace.move_focus(MoveOp::Swap)
                        }
                        Cmd::FocusParentUp => {
                            debug!("Cmd::FocusParentUp: {:#x}", window);
                            workspace.move_parent_focus(MoveOp::Up)
                        }
                        Cmd::FocusParentDown => {
                            debug!("Cmd::FocusParentDown: {:#x}", window);
                            workspace.move_parent_focus(MoveOp::Down)
                        }
                        Cmd::FocusParentMaster => {
                            debug!("Cmd::FocusParentMaster: {:#x}", window);
                            workspace.move_parent_focus(MoveOp::Swap)
                        }
                        _ => None
                    };

                    if let Some(window) = new_focus {
                        xws.focus_window(window);
                    }
                }
            },
            Cmd::SwapUp | Cmd::SwapDown | Cmd::SwapMaster | Cmd::SwapParentUp | Cmd::SwapParentDown | Cmd::SwapParentMaster => {
                if let Some(window) = state.current_ws().focused_window() {
                    let workspace = state.current_ws_mut();
                    let new_focus = match self {
                        Cmd::SwapUp => {
                            debug!("Cmd::SwapUp: {:#x}", window);
                            workspace.move_window(MoveOp::Up)
                        }
                        Cmd::SwapDown => {
                            debug!("Cmd::SwapDown: {:#x}", window);
                            workspace.move_window(MoveOp::Down)
                        }
                        Cmd::SwapMaster => {
                            debug!("Cmd::SwapMaster: {:#x}", window);
                            workspace.move_window(MoveOp::Swap)
                        }
                        Cmd::SwapParentUp => {
                            debug!("Cmd::SwapParentUp: {:#x}", window);
                            workspace.move_parent_window(MoveOp::Up)
                        }
                        Cmd::SwapParentDown => {
                            debug!("Cmd::SwapParentDown: {:#x}", window);
                            workspace.move_parent_window(MoveOp::Down)
                        }
                        Cmd::SwapParentMaster => {
                            debug!("Cmd::SwapParentMaster: {:#x}", window);
                            workspace.move_parent_window(MoveOp::Swap)
                        }
                        _ => false
                    };

                    if new_focus {
                        state.current_ws().redraw(xws, config, state.get_screens());
                    }
                }
            }
        }
        Ok(())
    }
}

fn reload(state: &WmState) -> Result<()> {
    info!("recompiling...");

    let config_build_dir = concat!(env!("HOME"), "/.xr3wm/.build");
    let mut cmd = Command::new("cargo");

    let output = cmd.current_dir(&config_build_dir)
        .arg("build")
        .env("RUST_LOG", "none")
        .output()
        .context("failed to run cargo")?;

    if !output.status.success() {
        let stderr_msg = String::from_utf8(output.stderr)
            .context("failed to convert cargo stderr to UTF-8")?;
        bail!("failed to recompile: {}", stderr_msg)
    }

    debug!("Cmd::Reload: restarting xr3wm...");

    let path = Path::new(concat!(env!("HOME"), "/.xr3wm/.tmp"));

    // save current workspace states to restore on restart
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(&path)
        .context("failed to open workspace state tmp file")?;

    serde_json::to_writer(file, state)
        .context("failed to serialize workspace states")?;

    let args: Vec<*const libc::c_char> = env::args()
        .filter_map(|x| CString::new(x).ok())
        .map(|x| x.into_raw() as *const libc::c_char)
        .chain(iter::once(null()))
        .collect();

    let envs: Vec<*const libc::c_char> = env::vars()
        .filter_map(|(k,v)| CString::new(format!("{}={}", k, v)).ok())
        .map(|x| x.into_raw() as *const libc::c_char)
        .chain(iter::once(null()))
        .collect();

    unsafe {
        execvpe(args[0] as *const libc::c_char, args.as_ptr(), envs.as_ptr());
        // execvp returns only if an error has occurred
        error!("failed to reload: {}", ::std::io::Error::last_os_error());
    }

    Ok(())
}

pub struct ManageHook {
    pub class_name: String,
    pub cmd: CmdManage,
}

pub enum CmdManage {
    Move(usize),
    Float,
    Fullscreen,
    Ignore,
}

impl CmdManage {
    pub fn call(&self,
                xws: &XlibWindowSystem,
                state: &mut WmState,
                config: &Config,
                window: Window) {
        match *self {
            CmdManage::Move(index) => {
                debug!("CmdManage::Move: {}, {}", window, index);
                state.add_window(Some(index - 1), xws, config, window);
            }
            CmdManage::Float => {
                debug!("CmdManage::Float");
                unimplemented!()
            }
            CmdManage::Fullscreen => {
                debug!("CmdManage::Fullscreen");
                unimplemented!()
            }
            CmdManage::Ignore => {
                debug!("CmdManage::Ignore");
                unimplemented!()
            }
        }
    }
}

fn exec(cmd: String, args: Vec<String>) {
    if !cmd.is_empty() {
        let mut cmd = Command::new(cmd);

        if !args.is_empty() {
            cmd.args(&args);
        }

        match cmd.envs(env::vars()).spawn() {
            Ok(_) => (),
            Err(e) => error!("failed to start \"{:?}\": {}", cmd, e),
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2015-2016, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::ffi::CString;
use std::ops::Deref;
use std::path::Path;

extern crate libc;
use libc::{c_char, c_double};

use ffi;
use ffi::enums::SurfaceType;

use surface::{Surface, SurfaceExt};

#[cfg(feature = "glib")]
use glib::translate::*;

pub struct PDFSurface(Surface);

extern "C" {
    pub fn cairo_pdf_surface_create (filename: *const c_char,
                                     width_in_points: c_double,
                                     height_in_points: c_double) -> *mut ffi::cairo_surface_t;
}

impl PDFSurface {
    pub fn from(surface: Surface) -> Result<PDFSurface, Surface> {
        if surface.get_type() == SurfaceType::Pdf {
            Ok(PDFSurface(surface))
        } else {
            Err(surface)
        }
    }

    #[doc(hidden)]
    pub unsafe fn from_raw_full(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from(Surface::from_raw_full(ptr)).unwrap()
    }

    pub fn create<T: AsRef<Path>>(filename: T, width: f64, height: f64) -> PDFSurface
    {
        // Convert: AsRef<Path> -> Cow<str> -> str
        let s = filename.as_ref().to_string_lossy().into_owned();
        let file = CString::new(s).unwrap();
        unsafe { Self::from_raw_full(cairo_pdf_surface_create(file.as_ptr(), width, height)) }
    }
}

impl AsRef<Surface> for PDFSurface {
    fn as_ref(&self) -> &Surface {
        &self.0
    }
}

impl Deref for PDFSurface {
    type Target = Surface;

    fn deref(&self) -> &Surface {
        &self.0
    }
}

impl Clone for PDFSurface {
    fn clone(&self) -> PDFSurface {
        PDFSurface(self.0.clone())
    }
}

unsafe impl Send for PDFSurface {}

#[cfg(feature = "glib")]
impl<'a> ToGlibPtr<'a, *mut ffi::cairo_surface_t> for PDFSurface {
    type Storage = &'a Surface;

    #[inline]
    fn to_glib_none(&'a self) -> Stash<'a, *mut ffi::cairo_surface_t, Self> {
        let stash = self.0.to_glib_none();
        Stash(stash.0, stash.1)
    }
}

#[cfg(feature = "glib")]
impl FromGlibPtrNone<*mut ffi::cairo_surface_t> for PDFSurface {
    #[inline]
    unsafe fn from_glib_none(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from(from_glib_none(ptr)).unwrap()
    }
}

#[cfg(feature = "glib")]
impl FromGlibPtrBorrow<*mut ffi::cairo_surface_t> for PDFSurface {
    #[inline]
    unsafe fn from_glib_borrow(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from(from_glib_borrow(ptr)).unwrap()
    }
}

#[cfg(feature = "glib")]
impl FromGlibPtrFull<*mut ffi::cairo_surface_t> for PDFSurface {
    #[inline]
    unsafe fn from_glib_full(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from_raw_full(ptr)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use context::*;
    use std::path::PathBuf;

    fn draw_x(cr: &Context) {
        cr.set_line_width(25.0);

        cr.set_source_rgba(1.0, 0.0, 0.0, 0.5);
        cr.line_to(0.,0.);
        cr.line_to(100.,100.);
        cr.stroke();

        cr.set_source_rgba(0.0, 0.0, 1.0, 0.5);
        cr.line_to(0.,100.);
        cr.line_to(100.,0.);
        cr.stroke();
    }

    #[test]
    fn pdf() {
        let output = PathBuf::from( env!("OUT_DIR") );

        { //Cow<str>
            let filename = output.join("test1.pdf");
            let surface = PDFSurface::create(filename, 100., 100.);
            let cr = Context::new(&surface);
            draw_x(&cr);
        }
        { // &str
            let filename = output.join("test2.pdf");
            let s = match filename.to_str() {
                None => panic!("Error converting Path to String"),
                Some(x) => x,
            };
            let surface = PDFSurface::create(s, 100., 100.);
            let cr = Context::new(&surface);
            draw_x(&cr);
        }
        { // String
            let filename = output.join("test3.pdf");
            let s = match filename.to_str() {
                None => panic!("Error converting Path to String"),
                Some(x) => String::from(x),
            };
            let surface = PDFSurface::create(s, 100., 100.);
            let cr = Context::new(&surface);
            draw_x(&cr);
        }
    }
}
<commit_msg>Fix feature config<commit_after>// Copyright 2015-2016, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::ffi::CString;
use std::ops::Deref;
use std::path::Path;

extern crate libc;
use libc::{c_char, c_double};

use ffi;
use ffi::enums::SurfaceType;

use surface::{Surface, SurfaceExt};

#[cfg(feature = "use_glib")]
use glib::translate::*;

pub struct PDFSurface(Surface);

extern "C" {
    pub fn cairo_pdf_surface_create (filename: *const c_char,
                                     width_in_points: c_double,
                                     height_in_points: c_double) -> *mut ffi::cairo_surface_t;
}

impl PDFSurface {
    pub fn from(surface: Surface) -> Result<PDFSurface, Surface> {
        if surface.get_type() == SurfaceType::Pdf {
            Ok(PDFSurface(surface))
        } else {
            Err(surface)
        }
    }

    #[doc(hidden)]
    pub unsafe fn from_raw_full(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from(Surface::from_raw_full(ptr)).unwrap()
    }

    pub fn create<T: AsRef<Path>>(filename: T, width: f64, height: f64) -> PDFSurface
    {
        // Convert: AsRef<Path> -> Cow<str> -> str
        let s = filename.as_ref().to_string_lossy().into_owned();
        let file = CString::new(s).unwrap();
        unsafe { Self::from_raw_full(cairo_pdf_surface_create(file.as_ptr(), width, height)) }
    }
}

impl AsRef<Surface> for PDFSurface {
    fn as_ref(&self) -> &Surface {
        &self.0
    }
}

impl Deref for PDFSurface {
    type Target = Surface;

    fn deref(&self) -> &Surface {
        &self.0
    }
}

impl Clone for PDFSurface {
    fn clone(&self) -> PDFSurface {
        PDFSurface(self.0.clone())
    }
}

unsafe impl Send for PDFSurface {}

#[cfg(feature = "use_glib")]
impl<'a> ToGlibPtr<'a, *mut ffi::cairo_surface_t> for PDFSurface {
    type Storage = &'a Surface;

    #[inline]
    fn to_glib_none(&'a self) -> Stash<'a, *mut ffi::cairo_surface_t, Self> {
        let stash = self.0.to_glib_none();
        Stash(stash.0, stash.1)
    }
}

#[cfg(feature = "use_glib")]
impl FromGlibPtrNone<*mut ffi::cairo_surface_t> for PDFSurface {
    #[inline]
    unsafe fn from_glib_none(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from(from_glib_none(ptr)).unwrap()
    }
}

#[cfg(feature = "use_glib")]
impl FromGlibPtrBorrow<*mut ffi::cairo_surface_t> for PDFSurface {
    #[inline]
    unsafe fn from_glib_borrow(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from(from_glib_borrow(ptr)).unwrap()
    }
}

#[cfg(feature = "use_glib")]
impl FromGlibPtrFull<*mut ffi::cairo_surface_t> for PDFSurface {
    #[inline]
    unsafe fn from_glib_full(ptr: *mut ffi::cairo_surface_t) -> PDFSurface {
        Self::from_raw_full(ptr)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use context::*;
    use std::path::PathBuf;

    fn draw_x(cr: &Context) {
        cr.set_line_width(25.0);

        cr.set_source_rgba(1.0, 0.0, 0.0, 0.5);
        cr.line_to(0.,0.);
        cr.line_to(100.,100.);
        cr.stroke();

        cr.set_source_rgba(0.0, 0.0, 1.0, 0.5);
        cr.line_to(0.,100.);
        cr.line_to(100.,0.);
        cr.stroke();
    }

    #[test]
    fn pdf() {
        let output = PathBuf::from( env!("OUT_DIR") );

        { //Cow<str>
            let filename = output.join("test1.pdf");
            let surface = PDFSurface::create(filename, 100., 100.);
            let cr = Context::new(&surface);
            draw_x(&cr);
        }
        { // &str
            let filename = output.join("test2.pdf");
            let s = match filename.to_str() {
                None => panic!("Error converting Path to String"),
                Some(x) => x,
            };
            let surface = PDFSurface::create(s, 100., 100.);
            let cr = Context::new(&surface);
            draw_x(&cr);
        }
        { // String
            let filename = output.join("test3.pdf");
            let s = match filename.to_str() {
                None => panic!("Error converting Path to String"),
                Some(x) => String::from(x),
            };
            let surface = PDFSurface::create(s, 100., 100.);
            let cr = Context::new(&surface);
            draw_x(&cr);
        }
    }
}
<|endoftext|>


<commit_before>use std::cmp;
use std::iter;
use std::time::{Duration, Instant};

use rand::{self, Rng};
use rand::distributions::IndependentSample;
use rand::distributions::gamma::Gamma;

use board::{Board, LegalMove, GameState};
use player::Player;

// Jeffrey's prior
const PRIOR: f64 = 0.5;

pub struct MCTSPlayer {
    dur: Duration,
}

impl MCTSPlayer {
    pub fn new(dur: Duration) -> Self {
        MCTSPlayer { dur }
    }
}

impl Player for MCTSPlayer {
    fn choose(&self, b: &Board) -> LegalMove {
        let now = Instant::now();
        let mut rng = rand::thread_rng();
        let mut node = Node::Unvisited;
        for i in 0.. {
            node.explore(&mut rng, b.clone());
            if node.is_certain() { break; }
            if now.elapsed() >= self.dur {
                println!("Choosing move after {} play-throughs", i);
                break;
            }
        }
        node.best_move(b)
    }
}

#[derive(Clone, Debug)]
enum Node {
    Unvisited,
    CertainLoss(Certain),
    CertainWin(Certain),
    CertainDraw(Certain),
    Probabilistic(Probabilistic),
}

impl Node {
    pub fn is_certain(&self) -> bool {
        match *self {
            Node::Unvisited => false,
            Node::CertainLoss(..) => true,
            Node::CertainWin(..) => true,
            Node::CertainDraw(..) => true,
            Node::Probabilistic(..) => false,
        }
    }

    pub fn best_move(&self, b: &Board) -> LegalMove {
        match *self {
            Node::Unvisited => panic!("node is unvisited"),
            Node::CertainLoss(ref c) => {
                println!("Certain loss in {} move(s)", c.depth);
                c.best_move(b)
            },
            Node::CertainWin(ref c) => {
                println!("Certain win in {} move(s)", c.depth);
                c.best_move(b)
            },
            Node::CertainDraw(ref c) => c.best_move(b),
            Node::Probabilistic(ref p) => p.best_move(b),
        }
    }

    pub fn explore<R: Rng>(&mut self, rng: &mut R, b: Board) -> f64 {
        let result = match *self {
            Node::Unvisited => self.explore_unvisted(rng, b),
            Node::Probabilistic(ref mut p) => p.explore(rng, b),
            _ => Ok(self.score()),
        };
        match result {
            Ok(score) => score,
            Err(node) => {
                *self = node;
                self.score()
            },
        }
    }

    fn score(&self) -> f64 {
        match *self {
            Node::Unvisited => panic!("node is unvisited"),
            Node::CertainLoss(..) => 1.0,
            Node::CertainWin(..) => 0.0,
            Node::CertainDraw(..) => 0.5,
            Node::Probabilistic(ref p) => p.score(),
        }
    }

    fn explore_unvisted<R: Rng>(&mut self, rng: &mut R, mut b: Board)
                                -> Result<f64, Node> {
        let (n, i, m) = Node::choose_unvisited_first(rng, &b);
        match b.make_legal_move(m) {
            GameState::Won => Err(Node::CertainWin(Certain::new(1, i))),
            GameState::Drawn => Err(Node::CertainDraw(Certain::new(1, i))),
            GameState::Ongoing => {
                let score = Node::choose_unvisited_rest(rng, b);
                Err(Node::Probabilistic(Probabilistic::new(n, score)))
            }
        }
    }

    fn choose_unvisited_first<R: Rng>(rng: &mut R, b: &Board) -> (usize, usize, LegalMove) {
        let mut m = None;
        let mut i = 0;
        let mut n = 0;
        for (i1, m1) in b.legal_moves_iter().enumerate() {
            n += 1;
            if m1.is_winning() { return (n, i1, m1); }
            if rng.gen_range(0, n) == 0 {
                i = i1;
                m = Some(m1);
            }
        }
        (n, i, m.unwrap())
    }

    fn choose_unvisited_rest<R: Rng>(rng: &mut R, mut b: Board) -> f64 {
        let mut score = 1.0;
        loop {
            let m = super::choose_winning_or_random(&b, rng);
            match b.make_legal_move(m) {
                GameState::Won => return score,
                GameState::Drawn => return 0.5,
                GameState::Ongoing => score = 1.0 - score,
            }
        }
    }

    fn p_parent_win(&self) -> f64 {
        match *self {
            Node::Unvisited => 0.5,
            Node::CertainLoss(..) => 1.0,
            Node::CertainWin(..) => 0.0,
            Node::CertainDraw(..) => 0.5,
            Node::Probabilistic(ref p) => p.p_parent_win(),
        }
    }

    fn p_parent_win_sample<R: Rng>(&self, r: &mut R) -> f64 {
        match *self {
            Node::Probabilistic(ref p) => p.p_parent_win_sample(r),
            _ => self.p_parent_win(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Certain {
    depth: usize,
    index: usize,
}

impl Certain {
    fn new(depth: usize, index: usize) -> Self {
        Certain { depth, index }
    }

    fn parent(&self, index: usize) -> Self {
        Certain::new(self.depth + 1, index)
    }

    fn best_move(&self, b: &Board) -> LegalMove {
        b.legal_moves_iter().nth(self.index).unwrap()
    }
}

#[derive(Clone, Debug)]
struct Probabilistic {
    score: f64,
    nplay: f64,
    children: Box<[Node]>,
}

impl Probabilistic {
    fn new(nchildren: usize, score: f64) -> Self {
        let score = PRIOR + score;
        let nplay = PRIOR + PRIOR + 1.0;
        let children = iter::repeat(Node::Unvisited).
            take(nchildren).collect::<Vec<Node>>().into_boxed_slice();
        Probabilistic { score, nplay, children }
    }

    fn best_move(&self, b: &Board) -> LegalMove {
        self.children.iter().zip(b.legal_moves_iter()).max_by(|&(n1, _), &(n2, _)| {
            n1.p_parent_win().partial_cmp(&n2.p_parent_win()).unwrap()
        }).map(|(_, m)| m).unwrap()
    }

    fn p_parent_win(&self) -> f64 {
        self.score / self.nplay as f64
    }

    fn p_parent_win_sample<R: Rng>(&self, r: &mut R) -> f64 {
        // Sample from Beta distribution via Gamma samples
        let a = Gamma::new(self.score, 1.0).ind_sample(r);
        let b = Gamma::new(self.nplay as f64, 1.0).ind_sample(r);
        a / (a + b)
    }

    fn explore<R: Rng>(&mut self, rng: &mut R, mut b: Board)
                       -> Result<f64, Node> {
        let mut closs = None;
        let mut nwin = 0;
        let mut cwin = None;
        let mut ndraw = 0;
        let mut cdraw = None;
        for (i, node) in self.children.iter().enumerate() {
            match *node {
                Node::CertainLoss(ref c1) => {
                    match closs {
                        None => closs = Some(c1.parent(i)),
                        Some(ref mut c) => *c = cmp::min(*c, c1.parent(i)),
                    }
                },
                Node::CertainWin(ref c1) => {
                    nwin += 1;
                    match cwin {
                        None => cwin = Some(c1.parent(i)),
                        Some(ref mut c) => *c = cmp::max(*c, c1.parent(i)),
                    }
                },
                Node::CertainDraw(ref c1) => {
                    ndraw += 1;
                    match cdraw {
                        None => cdraw = Some(c1.parent(i)),
                        Some(ref mut c) => *c = cmp::max(*c, c1.parent(i)),
                    }
                },
                _ => (),
            }
        }
        let n = self.children.len();
        if let Some(c) = closs { return Err(Node::CertainWin(c)); }
        if let Some(c) = cwin { if n == nwin { return Err(Node::CertainLoss(c)); } }
        if let Some(c) = cdraw { if n == nwin + ndraw { return Err(Node::CertainDraw(c)); } }
        let (_, node, m) = self.children.iter_mut().zip(b.legal_moves_iter()).
            map(|(node, m)| (node.p_parent_win_sample(rng), node, m)).
            max_by(|&(p1, _, _), &(p2, _, _)| p1.partial_cmp(&p2).unwrap()).
            unwrap();
        b.make_legal_move(m);
        let score = 1.0 - node.explore(rng, b);
        self.score += score;
        self.nplay += 1.0;
        Ok(score)
    }

    fn score(&self) -> f64 {
        self.score - PRIOR as f64
    }
}
<commit_msg>Proper Beta sampling; correct unvisited beliefs<commit_after>use std::cmp;
use std::iter;
use std::time::{Duration, Instant};

use rand::{self, Rng};
use rand::distributions::IndependentSample;
use rand::distributions::gamma::Gamma;

use board::{Board, LegalMove, GameState};
use player::Player;

// Jeffrey's prior
const PRIOR: f64 = 0.5;

pub struct MCTSPlayer {
    dur: Duration,
}

impl MCTSPlayer {
    pub fn new(dur: Duration) -> Self {
        MCTSPlayer { dur }
    }
}

impl Player for MCTSPlayer {
    fn choose(&self, b: &Board) -> LegalMove {
        let now = Instant::now();
        let mut rng = rand::thread_rng();
        let mut node = Node::Unvisited;
        for i in 0.. {
            node.explore(&mut rng, b.clone());
            if node.is_certain() { break; }
            if now.elapsed() >= self.dur {
                println!("Choosing move after {} play-throughs", i);
                break;
            }
        }
        node.best_move(b)
    }
}

#[derive(Clone, Debug, PartialEq)]
enum Node {
    Unvisited,
    CertainLoss(Certain),
    CertainWin(Certain),
    CertainDraw(Certain),
    Probabilistic(Probabilistic),
}

impl Node {
    pub fn is_certain(&self) -> bool {
        match *self {
            Node::Unvisited => false,
            Node::CertainLoss(..) => true,
            Node::CertainWin(..) => true,
            Node::CertainDraw(..) => true,
            Node::Probabilistic(..) => false,
        }
    }

    pub fn best_move(&self, b: &Board) -> LegalMove {
        match *self {
            Node::Unvisited => panic!("node is unvisited"),
            Node::CertainLoss(ref c) => {
                println!("Certain loss in {} move(s)", c.depth);
                c.best_move(b)
            },
            Node::CertainWin(ref c) => {
                println!("Certain win in {} move(s)", c.depth);
                c.best_move(b)
            },
            Node::CertainDraw(ref c) => c.best_move(b),
            Node::Probabilistic(ref p) => p.best_move(b),
        }
    }

    pub fn explore<R: Rng>(&mut self, rng: &mut R, b: Board) -> f64 {
        let result = match *self {
            Node::Unvisited => self.explore_unvisted(rng, b),
            Node::Probabilistic(ref mut p) => p.explore(rng, b),
            _ => Ok(self.score()),
        };
        match result {
            Ok(score) => score,
            Err(node) => {
                *self = node;
                self.score()
            },
        }
    }

    fn score(&self) -> f64 {
        match *self {
            Node::Unvisited => panic!("node is unvisited"),
            Node::CertainLoss(..) => 1.0,
            Node::CertainWin(..) => 0.0,
            Node::CertainDraw(..) => 0.5,
            Node::Probabilistic(ref p) => p.score(),
        }
    }

    fn explore_unvisted<R: Rng>(&mut self, rng: &mut R, mut b: Board)
                                -> Result<f64, Node> {
        let (n, i, m) = Node::choose_unvisited_first(rng, &b);
        match b.make_legal_move(m) {
            GameState::Won => Err(Node::CertainWin(Certain::new(1, i))),
            GameState::Drawn => Err(Node::CertainDraw(Certain::new(1, i))),
            GameState::Ongoing => {
                let score = Node::choose_unvisited_rest(rng, b);
                Err(Node::Probabilistic(Probabilistic::new(n, score)))
            }
        }
    }

    fn choose_unvisited_first<R: Rng>(rng: &mut R, b: &Board) -> (usize, usize, LegalMove) {
        let mut m = None;
        let mut i = 0;
        let mut n = 0;
        for (i1, m1) in b.legal_moves_iter().enumerate() {
            n += 1;
            if m1.is_winning() { return (n, i1, m1); }
            if rng.gen_range(0, n) == 0 {
                i = i1;
                m = Some(m1);
            }
        }
        (n, i, m.unwrap())
    }

    fn choose_unvisited_rest<R: Rng>(rng: &mut R, mut b: Board) -> f64 {
        let mut score = 1.0;
        loop {
            let m = super::choose_winning_or_random(&b, rng);
            match b.make_legal_move(m) {
                GameState::Won => return score,
                GameState::Drawn => return 0.5,
                GameState::Ongoing => score = 1.0 - score,
            }
        }
    }

    fn p_parent_win(&self) -> f64 {
        match *self {
            Node::Unvisited => 0.5,
            Node::CertainLoss(..) => 1.0,
            Node::CertainWin(..) => 0.0,
            Node::CertainDraw(..) => 0.5,
            Node::Probabilistic(ref p) => p.p_parent_win(),
        }
    }

    fn p_parent_win_sample<R: Rng>(&self, rng: &mut R) -> f64 {
        match *self {
            Node::Unvisited => beta_sample(rng, PRIOR, PRIOR),
            Node::Probabilistic(ref p) => p.p_parent_win_sample(rng),
            _ => self.p_parent_win(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Certain {
    depth: usize,
    index: usize,
}

impl Certain {
    fn new(depth: usize, index: usize) -> Self {
        Certain { depth, index }
    }

    fn parent(&self, index: usize) -> Self {
        Certain::new(self.depth + 1, index)
    }

    fn best_move(&self, b: &Board) -> LegalMove {
        b.legal_moves_iter().nth(self.index).unwrap()
    }
}

#[derive(Clone, Debug, PartialEq)]
struct Probabilistic {
    score: f64,
    nplay: f64,
    children: Box<[Node]>,
}

impl Probabilistic {
    fn new(nchildren: usize, score: f64) -> Self {
        let score = PRIOR + score;
        let nplay = PRIOR + PRIOR + 1.0;
        let children = iter::repeat(Node::Unvisited).
            take(nchildren).collect::<Vec<Node>>().into_boxed_slice();
        Probabilistic { score, nplay, children }
    }

    fn best_move(&self, b: &Board) -> LegalMove {
        self.children.iter().zip(b.legal_moves_iter()).max_by(|&(n1, _), &(n2, _)| {
            n1.p_parent_win().partial_cmp(&n2.p_parent_win()).unwrap()
        }).map(|(_, m)| m).unwrap()
    }

    fn p_parent_win(&self) -> f64 {
        self.score / self.nplay
    }

    fn p_parent_win_sample<R: Rng>(&self, rng: &mut R) -> f64 {
        beta_sample(rng, self.score, self.nplay - self.score)
    }

    fn explore<R: Rng>(&mut self, rng: &mut R, mut b: Board)
                       -> Result<f64, Node> {
        let mut closs = None;
        let mut nwin = 0;
        let mut cwin = None;
        let mut ndraw = 0;
        let mut cdraw = None;
        for (i, node) in self.children.iter().enumerate() {
            match *node {
                Node::CertainLoss(ref c1) => {
                    match closs {
                        None => closs = Some(c1.parent(i)),
                        Some(ref mut c) => *c = cmp::min(*c, c1.parent(i)),
                    }
                },
                Node::CertainWin(ref c1) => {
                    nwin += 1;
                    match cwin {
                        None => cwin = Some(c1.parent(i)),
                        Some(ref mut c) => *c = cmp::max(*c, c1.parent(i)),
                    }
                },
                Node::CertainDraw(ref c1) => {
                    ndraw += 1;
                    match cdraw {
                        None => cdraw = Some(c1.parent(i)),
                        Some(ref mut c) => *c = cmp::max(*c, c1.parent(i)),
                    }
                },
                _ => (),
            }
        }
        let n = self.children.len();
        if let Some(c) = closs { return Err(Node::CertainWin(c)); }
        if let Some(c) = cwin { if n == nwin { return Err(Node::CertainLoss(c)); } }
        if let Some(c) = cdraw { if n == nwin + ndraw { return Err(Node::CertainDraw(c)); } }
        let (_, node, m) = self.children.iter_mut().zip(b.legal_moves_iter()).
            map(|(node, m)| (node.p_parent_win_sample(rng), node, m)).
            max_by(|&(p1, _, _), &(p2, _, _)| p1.partial_cmp(&p2).unwrap()).
            unwrap();
        b.make_legal_move(m);
        let score = 1.0 - node.explore(rng, b);
        self.score += score;
        self.nplay += 1.0;
        Ok(score)
    }

    fn score(&self) -> f64 {
        self.score - PRIOR as f64
    }
}

fn beta_sample<R: Rng>(rng: &mut R, alpha: f64, beta: f64) -> f64 {
    if alpha <= 1.0 && beta <= 1.0 {
        loop {
            let u = rng.next_f64();
            let v = rng.next_f64();
            let x = u.powf(1.0 / alpha);
            let y = v.powf(1.0 / beta);

            if x + y > 1.0 { continue; }
            if x + y > 0.0 { return x / (x + y); }

            let ln_x = u.ln() / alpha;
            let ln_y = v.ln() / beta;
            let ln_m = if ln_x > ln_y { ln_x } else { ln_y };
            let ln_x = ln_x - ln_m;
            let ln_y = ln_y - ln_m;
            return (ln_x - (ln_x.exp() + ln_y.exp()).ln()).exp();
        }
    } else {
        let a = Gamma::new(alpha, 1.0).ind_sample(rng);
        let b = Gamma::new(beta, 1.0).ind_sample(rng);
        a / (a + b)
    }
}
<|endoftext|>


<commit_before>use super::serde_vistors::HexVisitor;
use super::{PublicKey, SecretKey};
use bls_aggregates::Signature as RawSignature;
use hex::encode as hex_encode;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use ssz::{
    decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
};

/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Signature(RawSignature);

impl Signature {
    /// Instantiate a new Signature from a message and a SecretKey.
    pub fn new(msg: &[u8], domain: u64, sk: &SecretKey) -> Self {
        Signature(RawSignature::new(msg, domain, sk.as_raw()))
    }

    /// Instantiate a new Signature from a message and a SecretKey, where the message has already
    /// been hashed.
    pub fn new_hashed(x_real_hashed: &[u8], x_imaginary_hashed: &[u8], sk: &SecretKey) -> Self {
        Signature(RawSignature::new_hashed(
            x_real_hashed,
            x_imaginary_hashed,
            sk.as_raw(),
        ))
    }

    /// Verify the Signature against a PublicKey.
    pub fn verify(&self, msg: &[u8], domain: u64, pk: &PublicKey) -> bool {
        self.0.verify(msg, domain, pk.as_raw())
    }

    /// Verify the Signature against a PublicKey, where the message has already been hashed.
    pub fn verify_hashed(
        &self,
        x_real_hashed: &[u8],
        x_imaginary_hashed: &[u8],
        pk: &PublicKey,
    ) -> bool {
        self.0
            .verify_hashed(x_real_hashed, x_imaginary_hashed, pk.as_raw())
    }

    /// Returns the underlying signature.
    pub fn as_raw(&self) -> &RawSignature {
        &self.0
    }

    /// Returns a new empty signature.
    pub fn empty_signature() -> Self {
        // Empty Signature is currently being represented as BLS::Signature.point_at_infinity()
        // However it should be represented as vec![0; 96] but this
        // would require all signatures to be represented in byte form as opposed to Signature
        let mut empty: Vec<u8> = vec![0; 96];
        // Sets C_flag and B_flag to 1 and all else to 0
        empty[0] += u8::pow(2, 6) + u8::pow(2, 7);
        Signature(RawSignature::from_bytes(&empty).unwrap())
    }
}

impl Encodable for Signature {
    fn ssz_append(&self, s: &mut SszStream) {
        s.append_vec(&self.0.as_bytes());
    }
}

impl Decodable for Signature {
    fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
        let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
        let raw_sig = RawSignature::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?;
        Ok((Signature(raw_sig), i))
    }
}

impl TreeHash for Signature {
    fn hash_tree_root(&self) -> Vec<u8> {
        hash(&self.0.as_bytes())
    }
}

impl Serialize for Signature {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex_encode(ssz_encode(self)))
    }
}

impl<'de> Deserialize<'de> for Signature {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bytes = deserializer.deserialize_str(HexVisitor)?;
        let (pubkey, _) = <_>::ssz_decode(&bytes[..], 0)
            .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
        Ok(pubkey)
    }
}

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

    #[test]
    pub fn test_ssz_round_trip() {
        let keypair = Keypair::random();

        let original = Signature::new(&[42, 42], 0, &keypair.sk);

        let bytes = ssz_encode(&original);
        let (decoded, _) = Signature::ssz_decode(&bytes, 0).unwrap();

        assert_eq!(original, decoded);
    }

    #[test]
    pub fn test_empty_signature() {
        let sig = Signature::empty_signature();

        let sig_as_bytes: Vec<u8> = sig.as_raw().as_bytes();

        assert_eq!(sig_as_bytes.len(), 96);
        for (i, one_byte) in sig_as_bytes.iter().enumerate() {
            if i == 0 {
                assert_eq!(*one_byte, u8::pow(2, 6) + u8::pow(2, 7));
            } else {
                assert_eq!(*one_byte, 0);
            }
        }
    }
}
<commit_msg>Allow for conversion to/from empty_signature<commit_after>use super::serde_vistors::HexVisitor;
use super::{PublicKey, SecretKey};
use bls_aggregates::Signature as RawSignature;
use hex::encode as hex_encode;
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use ssz::{
    decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
};

/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Signature {
    signature: RawSignature,
    is_empty: bool,
}

impl Signature {
    /// Instantiate a new Signature from a message and a SecretKey.
    pub fn new(msg: &[u8], domain: u64, sk: &SecretKey) -> Self {
        Signature {
            signature: RawSignature::new(msg, domain, sk.as_raw()),
            is_empty: false,
        }
    }

    /// Instantiate a new Signature from a message and a SecretKey, where the message has already
    /// been hashed.
    pub fn new_hashed(x_real_hashed: &[u8], x_imaginary_hashed: &[u8], sk: &SecretKey) -> Self {
        Signature {
            signature: RawSignature::new_hashed(
                x_real_hashed,
                x_imaginary_hashed,
                sk.as_raw(),
            ),
            is_empty: false,
        }
    }

    /// Verify the Signature against a PublicKey.
    pub fn verify(&self, msg: &[u8], domain: u64, pk: &PublicKey) -> bool {
        if self.is_empty {
            return false;
        }
        self.signature.verify(msg, domain, pk.as_raw())
    }

    /// Verify the Signature against a PublicKey, where the message has already been hashed.
    pub fn verify_hashed(
        &self,
        x_real_hashed: &[u8],
        x_imaginary_hashed: &[u8],
        pk: &PublicKey,
    ) -> bool {
        self.signature
            .verify_hashed(x_real_hashed, x_imaginary_hashed, pk.as_raw())
    }

    /// Returns the underlying signature.
    pub fn as_raw(&self) -> &RawSignature {
        &self.signature
    }

    /// Returns a new empty signature.
    pub fn empty_signature() -> Self {
        // Set RawSignature = infinity
        let mut empty: Vec<u8> = vec![0; 96];
        empty[0] += u8::pow(2, 6) + u8::pow(2, 7);
        Signature {
            signature: RawSignature::from_bytes(&empty).unwrap(),
            is_empty: true,
        }
    }

    // Converts a BLS Signature to bytes
    pub fn as_bytes(&self) -> Vec<u8> {
        if self.is_empty {
            return vec![0; 96];
        }
        self.signature.as_bytes()
    }

    // Convert bytes to BLS Signature
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
        for byte in bytes {
            if *byte != 0 {
                let raw_signature = RawSignature::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?;
                return Ok(Signature {
                    signature: raw_signature,
                    is_empty: false,
                });
            }
        }
        Ok(Signature::empty_signature())
    }

    // Check for empty Signature
    pub fn is_empty(&self) -> bool {
        self.is_empty
    }
}

impl Encodable for Signature {
    fn ssz_append(&self, s: &mut SszStream) {
        s.append_vec(&self.as_bytes());
    }
}

impl Decodable for Signature {
    fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
        let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
        let signature = Signature::from_bytes(&sig_bytes)?;
        Ok((signature, i))
    }
}

impl TreeHash for Signature {
    fn hash_tree_root(&self) -> Vec<u8> {
        hash(&self.as_bytes())
    }
}

impl Serialize for Signature {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&hex_encode(ssz_encode(self)))
    }
}

impl<'de> Deserialize<'de> for Signature {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bytes = deserializer.deserialize_str(HexVisitor)?;
        let (pubkey, _) = <_>::ssz_decode(&bytes[..], 0)
            .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
        Ok(pubkey)
    }
}

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

    #[test]
    pub fn test_ssz_round_trip() {
        let keypair = Keypair::random();

        let original = Signature::new(&[42, 42], 0, &keypair.sk);

        let bytes = ssz_encode(&original);
        let (decoded, _) = Signature::ssz_decode(&bytes, 0).unwrap();

        assert_eq!(original, decoded);
    }

    #[test]
    pub fn test_empty_signature() {
        let sig = Signature::empty_signature();

        let sig_as_bytes: Vec<u8> = sig.as_raw().as_bytes();

        assert_eq!(sig_as_bytes.len(), 96);
        for (i, one_byte) in sig_as_bytes.iter().enumerate() {
            if i == 0 {
                assert_eq!(*one_byte, u8::pow(2, 6) + u8::pow(2, 7));
            } else {
                assert_eq!(*one_byte, 0);
            }
        }
    }
}
<|endoftext|>