<commit_before>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use clap::{App, Arg, ArgMatches};
use serde_json;
use chrono::Duration;

use prelude::*;
use api::{Api, NewRelease};
use config::Config;
use utils::{ArgExt, TempFile, propagate_exit_status};
use sourcemaputils::SourceMapProcessor;
use xcode;

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("uploads react-native projects from within an xcode build step")
        .org_project_args()
        .arg(Arg::with_name("verbose")
            .long("verbose")
            .short("verbose")
            .help("Enable verbose mode"))
        .arg(Arg::with_name("force")
             .long("force")
             .short("f")
             .help("Forces the script to run, even in Debug configuration.  This rarely \
                    does what you want because the default build script does not actually \
                    produce any information that the sentry build tool could pick up on."))
        .arg(Arg::with_name("allow_fetch")
             .long("allow-fetch")
             .help("Enable sourcemap fetching from the packager.  If this is enabled \
                    the react native packager needs to run and sourcemaps are downloade \
                    from it if the simulator platform is detected."))
        .arg(Arg::with_name("fetch_from")
             .long("fetch-from")
             .value_name("URL")
             .help("When fetching is enabled this is the URL where fetches can be made \
                    from.  The default is http://127.0.0.1:8081/ where the react-native \
                    packager runs by default."))
        .arg(Arg::with_name("build_script")
             .value_name("BUILD_SCRIPT")
             .index(1)
             .help("Optional path to the build script{n}{n}\
                    This is the path to the react-native-xcode.sh script you want \
                    to use.  By default the bundled build script is used."))
        .arg(Arg::with_name("force_foreground")
             .long("force-foreground")
             .help("By default part of the build process will when triggered from Xcode \
                    detach and continue in the background.  When an error happens \
                    a dialog is shown.  If this parameter is passed Xcode will wait \
                    for the process to finish before the build finishes and output \
                    will be shown in the Xcode build output."))
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if path.len() > 0 {
            return path;
        }
    }
    "node".into()
}

pub fn execute<'a>(matches: &ArgMatches<'a>, config: &Config) -> Result<()> {
    let (org, project) = config.get_org_and_project(matches)?;
    let api = Api::new(config);
    let should_wrap = matches.is_present("force") || match env::var("CONFIGURATION") {
        Ok(config) => &config != "Debug",
        Err(_) => { return Err("Need to run this from Xcode".into()); }
    };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/packager/react-native-xcode.sh")
    }.canonicalize()?;

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match xcode::InfoPlist::discover_from_path(&base)? {
        Some(plist) => plist,
        None => { return Err("Could not find info.plist".into()); }
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::new()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    xcode::MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let url = url.trim_right_matches('/');
            bundle_file = TempFile::new()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::new()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                return Err("Error: react-native packager did not respond in time".into());
            }

            api.download(&format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                         &mut bundle_file.open())?;
            api.download(&format!("{}/index.ios.map?platform=ios&dev=true", url),
                         &mut sourcemap_file.open())?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env("SENTRY_RN_SOURCEMAP_REPORT", report_file.path().to_str().unwrap())
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report : SourceMapReport = serde_json::from_reader(&mut f)?;
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name()
                                 .unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!("~/{}", sourcemap_path.file_name()
                                    .unwrap().to_string_lossy());
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new(matches.is_present("verbose"));
        processor.add(&bundle_url, &bundle_path)?;
        processor.add(&sourcemap_url, &sourcemap_path)?;
        processor.rewrite(&vec![base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let release = api.new_release(&org, &project, &NewRelease {
            version: plist.release_name(),
            ..Default::default()
        })?;
        println!("Uploading sourcemaps for release {}", release.version);
        processor.upload(&api, &org, &project, &release.version)?;

        Ok(())
    })
}

pub fn wrap_call() -> Result<()> {
    let mut args : Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && args[1] == "bundle" {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().map(|x| x.to_string());
            } else if item.starts_with("--sourcemap-output=") {
                sourcemap_path = Some(item[19..].to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().map(|x| x.to_string());
            } else if item.starts_with("--bundle-output=") {
                bundle_path = Some(item[16..].to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let path = format!("{}.map", &bundle_path.as_ref().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path);
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(|x| PathBuf::from(x));

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<commit_msg>Discover Info.plist from env for react build step<commit_after>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use clap::{App, Arg, ArgMatches};
use serde_json;
use chrono::Duration;

use prelude::*;
use api::{Api, NewRelease};
use config::Config;
use utils::{ArgExt, TempFile, propagate_exit_status};
use sourcemaputils::SourceMapProcessor;
use xcode;

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("uploads react-native projects from within an xcode build step")
        .org_project_args()
        .arg(Arg::with_name("verbose")
            .long("verbose")
            .short("verbose")
            .help("Enable verbose mode"))
        .arg(Arg::with_name("force")
             .long("force")
             .short("f")
             .help("Forces the script to run, even in Debug configuration.  This rarely \
                    does what you want because the default build script does not actually \
                    produce any information that the sentry build tool could pick up on."))
        .arg(Arg::with_name("allow_fetch")
             .long("allow-fetch")
             .help("Enable sourcemap fetching from the packager.  If this is enabled \
                    the react native packager needs to run and sourcemaps are downloade \
                    from it if the simulator platform is detected."))
        .arg(Arg::with_name("fetch_from")
             .long("fetch-from")
             .value_name("URL")
             .help("When fetching is enabled this is the URL where fetches can be made \
                    from.  The default is http://127.0.0.1:8081/ where the react-native \
                    packager runs by default."))
        .arg(Arg::with_name("build_script")
             .value_name("BUILD_SCRIPT")
             .index(1)
             .help("Optional path to the build script{n}{n}\
                    This is the path to the react-native-xcode.sh script you want \
                    to use.  By default the bundled build script is used."))
        .arg(Arg::with_name("force_foreground")
             .long("force-foreground")
             .help("By default part of the build process will when triggered from Xcode \
                    detach and continue in the background.  When an error happens \
                    a dialog is shown.  If this parameter is passed Xcode will wait \
                    for the process to finish before the build finishes and output \
                    will be shown in the Xcode build output."))
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if path.len() > 0 {
            return path;
        }
    }
    "node".into()
}

pub fn execute<'a>(matches: &ArgMatches<'a>, config: &Config) -> Result<()> {
    let (org, project) = config.get_org_and_project(matches)?;
    let api = Api::new(config);
    let should_wrap = matches.is_present("force") || match env::var("CONFIGURATION") {
        Ok(config) => &config != "Debug",
        Err(_) => { return Err("Need to run this from Xcode".into()); }
    };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/packager/react-native-xcode.sh")
    }.canonicalize()?;

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match xcode::InfoPlist::discover_from_env(&base)? {
        Some(plist) => plist,
        None => { return Err("Could not find info.plist".into()); }
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::new()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    xcode::MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let url = url.trim_right_matches('/');
            bundle_file = TempFile::new()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::new()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                return Err("Error: react-native packager did not respond in time".into());
            }

            api.download(&format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                         &mut bundle_file.open())?;
            api.download(&format!("{}/index.ios.map?platform=ios&dev=true", url),
                         &mut sourcemap_file.open())?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env("SENTRY_RN_SOURCEMAP_REPORT", report_file.path().to_str().unwrap())
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report : SourceMapReport = serde_json::from_reader(&mut f)?;
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name()
                                 .unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!("~/{}", sourcemap_path.file_name()
                                    .unwrap().to_string_lossy());
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new(matches.is_present("verbose"));
        processor.add(&bundle_url, &bundle_path)?;
        processor.add(&sourcemap_url, &sourcemap_path)?;
        processor.rewrite(&vec![base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let release = api.new_release(&org, &project, &NewRelease {
            version: plist.release_name(),
            ..Default::default()
        })?;
        println!("Uploading sourcemaps for release {}", release.version);
        processor.upload(&api, &org, &project, &release.version)?;

        Ok(())
    })
}

pub fn wrap_call() -> Result<()> {
    let mut args : Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && args[1] == "bundle" {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().map(|x| x.to_string());
            } else if item.starts_with("--sourcemap-output=") {
                sourcemap_path = Some(item[19..].to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().map(|x| x.to_string());
            } else if item.starts_with("--bundle-output=") {
                bundle_path = Some(item[16..].to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let path = format!("{}.map", &bundle_path.as_ref().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path);
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(|x| PathBuf::from(x));

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UserInterface") %>

${helpers.predefined_type("cursor",
                          "Cursor",
                          "computed::Cursor::auto()",
                          initial_specified_value="specified::Cursor::auto()",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-ui/#cursor")}

// NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact)
// is nonstandard, slated for CSS4-UI.
// TODO(pcwalton): SVG-only values.
${helpers.single_keyword("pointer-events", "auto none", animation_value_type="discrete",
                         extra_gecko_values="visiblepainted visiblefill visiblestroke visible painted fill stroke all",
                         flags="APPLIES_TO_PLACEHOLDER",
                         spec="https://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty")}

${helpers.single_keyword("-moz-user-input", "auto none",
                         products="gecko", gecko_ffi_name="mUserInput",
                         gecko_enum_prefix="StyleUserInput",
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-input)")}

${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only",
                         products="gecko", gecko_ffi_name="mUserModify",
                         gecko_enum_prefix="StyleUserModify",
                         needs_conversion=True,
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-modify)")}

${helpers.single_keyword("-moz-user-focus",
                         "none ignore normal select-after select-before select-menu select-same select-all",
                         products="gecko", gecko_ffi_name="mUserFocus",
                         gecko_enum_prefix="StyleUserFocus",
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)")}

${helpers.predefined_type(
    "caret-color",
    "ColorOrAuto",
    "Either::Second(Auto)",
    spec="https://drafts.csswg.org/css-ui/#caret-color",
    animation_value_type="AnimatedCaretColor",
    ignored_when_colors_disabled=True,
    products="gecko",
)}

// Only scrollbar-face-color and scrollbar-track-color are added here.
// These two are the only common parts of scrollbar among Windows and
// macOS. We may or may not want to provide other properties to allow
// finer-grain control.
//
// NOTE The syntax in spec is currently `normal | <color>`, but I think
//      reusing `auto | <color>` as `caret-color` makes more sense. See
//      https://github.com/w3c/csswg-drafts/issues/2660
% for part in ["face", "track"]:
${helpers.predefined_type(
    "scrollbar-%s-color" % part,
    "ColorOrAuto",
    "Either::Second(Auto)",
    spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-color-properties",
    gecko_pref="layout.css.scrollbar-colors.enabled",
    animation_value_type="ColorOrAuto",
    ignored_when_colors_disabled=True,
    enabled_in="chrome",
    products="gecko",
)}
% endfor
<commit_msg>style: Rename StyleUserInterface to StyleUI.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %>

${helpers.predefined_type("cursor",
                          "Cursor",
                          "computed::Cursor::auto()",
                          initial_specified_value="specified::Cursor::auto()",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-ui/#cursor")}

// NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact)
// is nonstandard, slated for CSS4-UI.
// TODO(pcwalton): SVG-only values.
${helpers.single_keyword("pointer-events", "auto none", animation_value_type="discrete",
                         extra_gecko_values="visiblepainted visiblefill visiblestroke visible painted fill stroke all",
                         flags="APPLIES_TO_PLACEHOLDER",
                         spec="https://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty")}

${helpers.single_keyword("-moz-user-input", "auto none",
                         products="gecko", gecko_ffi_name="mUserInput",
                         gecko_enum_prefix="StyleUserInput",
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-input)")}

${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only",
                         products="gecko", gecko_ffi_name="mUserModify",
                         gecko_enum_prefix="StyleUserModify",
                         needs_conversion=True,
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-modify)")}

${helpers.single_keyword("-moz-user-focus",
                         "none ignore normal select-after select-before select-menu select-same select-all",
                         products="gecko", gecko_ffi_name="mUserFocus",
                         gecko_enum_prefix="StyleUserFocus",
                         animation_value_type="discrete",
                         spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)")}

${helpers.predefined_type(
    "caret-color",
    "ColorOrAuto",
    "Either::Second(Auto)",
    spec="https://drafts.csswg.org/css-ui/#caret-color",
    animation_value_type="AnimatedCaretColor",
    ignored_when_colors_disabled=True,
    products="gecko",
)}

// Only scrollbar-face-color and scrollbar-track-color are added here.
// These two are the only common parts of scrollbar among Windows and
// macOS. We may or may not want to provide other properties to allow
// finer-grain control.
//
// NOTE The syntax in spec is currently `normal | <color>`, but I think
//      reusing `auto | <color>` as `caret-color` makes more sense. See
//      https://github.com/w3c/csswg-drafts/issues/2660
% for part in ["face", "track"]:
${helpers.predefined_type(
    "scrollbar-%s-color" % part,
    "ColorOrAuto",
    "Either::Second(Auto)",
    spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-color-properties",
    gecko_pref="layout.css.scrollbar-colors.enabled",
    animation_value_type="ColorOrAuto",
    ignored_when_colors_disabled=True,
    enabled_in="chrome",
    products="gecko",
)}
% endfor
<|endoftext|>


<commit_before>pub mod search;

use std::thread;
use std::cmp::min;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
use std::time::SystemTime;
use basetypes::*;
use uci::{UciEngine, UciEngineFactory, EngineReply, OptionName, OptionDescription};
use position::Position;
use chess_move::*;
use tt::*;

// use rand;
// use rand::distributions::{Sample, Range};
// let mut rng = rand::thread_rng();
// let mut between = Range::new(0, 10);
// let x = between.sample(&mut rng);

const VERSION: &'static str = "0.1";
const MAX_DEPTH: u8 = 126;

/// Implements `UciEngine` trait.
pub struct Engine {
    position: Position,
    pondering_is_allowed: bool,
    is_thinking: bool,
    is_pondering: bool,
    curr_depth: u8,
    replies: Vec<EngineReply>,
    tt: Arc<TranspositionTable>,
    commands: Sender<search::Command>,
    reports: Receiver<search::Report>,
    thinking_since: SystemTime,
    searched_nodes: NodeCount,
    searched_time: u64, // milliseconds
    stop_when: TimeManagement,
}

impl Engine {
    /// Creates a new instance.
    ///
    /// `tt_size_mb` is the preferred size of the transposition
    /// table in Mbytes.
    pub fn new(tt_size_mb: usize) -> Engine {
        let mut tt = TranspositionTable::new();
        tt.resize(tt_size_mb);
        let tt = Arc::new(tt);
        let (commands_tx, commands_rx) = channel();
        let (reports_tx, reports_rx) = channel();
        let tt2 = tt.clone();
        thread::spawn(move || {
            search::run_deepening(tt2, commands_rx, reports_tx);
        });

        Engine {
            position: Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 \
                                          1")
                          .ok()
                          .unwrap(),
            pondering_is_allowed: false,
            is_thinking: false,
            is_pondering: false,
            curr_depth: 0,
            replies: vec![],
            tt: tt,
            commands: commands_tx,
            reports: reports_rx,
            thinking_since: SystemTime::now(),
            searched_nodes: 0,
            searched_time: 0,
            stop_when: TimeManagement::Infinite,
        }
    }
}

impl UciEngine for Engine {
    fn set_option(&mut self, name: &str, value: &str) {
        match name {
            // Tells the engine that it will be allowed to ponder.
            // This option is needed because the engine might change
            // its time management algorithm when pondering is
            // allowed.
            "Ponder" => {
                self.pondering_is_allowed = value == "true";
            }

            // An invalid option. Notice that we do not support
            // re-sizing of the transposition table once the engine
            // had started.
            _ => (),
        }
    }

    fn new_game(&mut self) {
        // Clearing the transposition table would not change anything,
        // so we do nothing.
    }

    fn position(&mut self, fen: &str, moves: &mut Iterator<Item = &str>) {
        if !self.is_thinking {
            if let Ok(p) = Position::from_history(fen, moves) {
                self.position = p;
                self.tt.new_search();
            }
        }
    }

    #[allow(unused_variables)]
    fn go(&mut self,
          searchmoves: Option<Vec<String>>,
          ponder: bool,
          wtime: Option<u64>,
          btime: Option<u64>,
          winc: Option<u64>,
          binc: Option<u64>,
          movestogo: Option<u64>,
          depth: Option<u64>,
          nodes: Option<u64>,
          mate: Option<u64>,
          movetime: Option<u64>,
          infinite: bool) {
        if !self.is_thinking {
            // Note: We ignore the "searchmoves" parameter.

            self.is_thinking = true;
            self.is_pondering = ponder;
            self.thinking_since = SystemTime::now();
            self.searched_nodes = 0;
            self.searched_time = 0;
            self.curr_depth = 0;

            // Figure out when we should stop thinking.
            let (time, inc) = if self.position.board().to_move() == WHITE {
                (wtime, winc.unwrap_or(0))
            } else {
                (btime, binc.unwrap_or(0))
            };
            self.stop_when = if infinite {
                TimeManagement::Infinite
            } else if movetime.is_some() {
                TimeManagement::MoveTime(movetime.unwrap())
            } else if nodes.is_some() {
                TimeManagement::Nodes(nodes.unwrap())
            } else if depth.is_some() {
                TimeManagement::Depth(min(depth.unwrap(), MAX_DEPTH as u64) as u8)
            } else {
                // TODO: Robert Hyatt proposes this:
                //
                // nMoves =  min( numberOfMovesOutOfBook, 10 )
                // factor = 2 - nMoves / 10
                // target = timeLeft / numberOfMovesUntilNextTimeControl
                // time   = factor * target
                let time = time.unwrap_or(0);
                let movestogo = movestogo.unwrap_or(40);
                let movetime = (time + inc * movestogo) / movestogo;
                TimeManagement::MoveTimeHint(min(movetime, time / 2))
            };

            // Start a search with the maximum possible depth.
            self.commands
                .send(search::Command::Search {
                    search_id: 0,
                    position: self.position.clone(),
                    depth: MAX_DEPTH,
                    lower_bound: -20000,
                    upper_bound: 20000,
                })
                .unwrap();
        }
    }

    fn ponder_hit(&mut self) {
        if self.is_thinking && self.is_pondering {
            self.is_pondering = false;
        }
    }

    fn stop(&mut self) {
        if self.is_thinking {
            self.commands.send(search::Command::Stop).unwrap();
            let best_move = self.get_best_move();
            self.replies.push(EngineReply::BestMove {
                best_move: best_move,
                ponder_move: None,
            });
            self.is_thinking = false;
        }
    }

    fn is_thinking(&self) -> bool {
        self.is_thinking
    }

    fn get_reply(&mut self) -> Option<EngineReply> {
        // TODO: implement panic mode for MoveTimeHint management.
        if self.is_thinking && !self.is_pondering {
            if match self.stop_when {
                TimeManagement::MoveTime(t) => self.searched_time > t,
                TimeManagement::MoveTimeHint(t) => self.searched_time > t,
                TimeManagement::Nodes(n) => self.searched_nodes > n,
                TimeManagement::Depth(d) => self.curr_depth > d,
                TimeManagement::Infinite => false,
            } {
                self.stop();
            }
        }

        // Empty the reports queue.
        while let Ok(report) = self.reports.try_recv() {
            // let rep_type = match report {
            //     search::Report::Progress { searched_nodes, .. } => format!("progress {}", searched_nodes),
            //     search::Report::Done { .. } => "done".to_string(),
            // };
            // self.replies.push(EngineReply::Info(vec![
            //     ("string".to_string(), rep_type),
            // ]));
            if self.is_thinking {
                match report {
                    search::Report::Progress { searched_nodes, depth, .. } => {
                        let thinking_duration = self.thinking_since.elapsed().unwrap();
                        self.searched_nodes = searched_nodes;
                        self.searched_time = 1000 * thinking_duration.as_secs() +
                                             (thinking_duration.subsec_nanos() / 1000000) as u64;
                        if self.curr_depth < depth {
                            // We are done with the old depth, so we
                            // extract the PV, the value, and the
                            // score from the transposition table.
                            let mut prev_move = None;
                            let mut p = self.position.clone();
                            let mut value = -20000;
                            let mut bound = BOUND_LOWER;
                            let mut pv = Vec::new();
                            while let Some(entry) = self.tt.probe(p.hash()) {
                                if pv.len() < self.curr_depth as usize {
                                    if let Some(m) = prev_move {
                                        pv.push(m);
                                    }
                                    value = if pv.len() & 1 == 0 {
                                        entry.value()
                                    } else {
                                        -entry.value()
                                    };
                                    bound = entry.bound();
                                    if bound == BOUND_EXACT {
                                        if let Some(m) = p.try_move_digest(entry.move16()) {
                                            if p.do_move(m) {
                                                prev_move = Some(m);
                                                continue;
                                            }
                                        }
                                    }
                                }
                                break;
                            }

                            // Send the extracted info to the GUI.
                            let score_suffix = match bound {
                                BOUND_EXACT => "".to_string(),
                                BOUND_UPPER => " upperbound".to_string(),
                                BOUND_LOWER => " lowerbound".to_string(),
                                _ => panic!("unexpected bound type"),
                            };
                            let nps = if self.searched_time == 0 {
                                0
                            } else {
                                1000 * searched_nodes / self.searched_time
                            };
                            let mut pv_string = String::new();
                            for m in pv {
                                pv_string.push(' ');
                                pv_string.push_str(&m.notation());
                            }
                            self.replies.push(EngineReply::Info(vec![
                                ("depth".to_string(), format!("{}", self.curr_depth)),
                                ("score".to_string(), format!("cp {}{}", value, score_suffix)),
                                ("time".to_string(), format!("{}", self.searched_time)),
                                ("nodes".to_string(), format!("{}", searched_nodes)),
                                ("nps".to_string(), format!("{}", nps)),
                                ("pv".to_string(), pv_string),
                            ]));

                            // Update the old depth value.
                            self.curr_depth = depth;
                        }
                    }
                    _ => (),
                }
            }
        }

        if self.replies.len() > 0 {
            Some(self.replies.remove(0))
        } else {
            None
        }
    }
}

impl Engine {
    fn get_best_move(&mut self) -> String {
        let mut m = match self.tt.probe(self.position.hash()) {
            Some(entry) => entry.move16(),
            None => 0,
        };
        if m == 0 {
            // Pick the first legal move.
            let mut first_legal_move = Move::invalid();
            let mut v = MoveStack::new();
            self.position.generate_moves(&mut v);
            while let Some(x) = v.pop() {
                if self.position.do_move(x) {
                    self.position.undo_move();
                    first_legal_move = x;
                    break;
                }
            }
            m = first_legal_move.digest();
        }
        if m != 0 {
            let move_type = move_type(m);
            let orig_square = orig_square(m);
            let dest_square = dest_square(m);
            let promoted_piece = match aux_data(m) {
                0 => "q",
                1 => "r",
                2 => "b",
                3 => "n",
                _ => panic!("invalid promoted piece code"),
            };
            format!("{}{}{}",
                    notation(orig_square),
                    notation(dest_square),
                    if move_type == MOVE_PROMOTION {
                        promoted_piece
                    } else {
                        ""
                    })

        } else {
            "0000".to_string()
        }
    }
}

/// Implements `UciEngineFactory` trait.
pub struct EngineFactory;

impl UciEngineFactory<Engine> for EngineFactory {
    fn name(&self) -> String {
        format!("Alcibiades {}", VERSION)
    }

    fn author(&self) -> String {
        "Evgeni Pandurski".to_string()
    }

    fn options(&self) -> Vec<(OptionName, OptionDescription)> {
        vec![
            // TODO: Calculate a sane limit for the hash size.
            ("Hash".to_string(), OptionDescription::Spin { min: 1, max: 2048, default: 16 }),
            ("Ponder".to_string(), OptionDescription::Check { default: false }),
        ]
    }

    fn create(&self, hash_size_mb: Option<usize>) -> Engine {
        Engine::new(hash_size_mb.unwrap_or(16))
    }
}

enum TimeManagement {
    MoveTime(u64),
    MoveTimeHint(u64),
    Nodes(NodeCount),
    Depth(u8),
    Infinite,
}
<commit_msg>beautification<commit_after>pub mod search;

use std::thread;
use std::cmp::min;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver};
use std::time::SystemTime;
use basetypes::*;
use uci::{UciEngine, UciEngineFactory, EngineReply, OptionName, OptionDescription};
use position::Position;
use chess_move::*;
use tt::*;

// use rand;
// use rand::distributions::{Sample, Range};
// let mut rng = rand::thread_rng();
// let mut between = Range::new(0, 10);
// let x = between.sample(&mut rng);

const VERSION: &'static str = "0.1";
const MAX_DEPTH: u8 = 126;

/// Implements `UciEngine` trait.
pub struct Engine {
    position: Position,
    pondering_is_allowed: bool,
    is_thinking: bool,
    is_pondering: bool,
    curr_depth: u8,
    replies: Vec<EngineReply>,
    tt: Arc<TranspositionTable>,
    commands: Sender<search::Command>,
    reports: Receiver<search::Report>,
    thinking_since: SystemTime,
    searched_nodes: NodeCount,
    searched_time: u64, // milliseconds
    stop_when: TimeManagement,
}

impl Engine {
    /// Creates a new instance.
    ///
    /// `tt_size_mb` is the preferred size of the transposition
    /// table in Mbytes.
    pub fn new(tt_size_mb: usize) -> Engine {
        let mut tt = TranspositionTable::new();
        tt.resize(tt_size_mb);
        let tt = Arc::new(tt);
        let (commands_tx, commands_rx) = channel();
        let (reports_tx, reports_rx) = channel();
        let tt2 = tt.clone();
        thread::spawn(move || {
            search::run_deepening(tt2, commands_rx, reports_tx);
        });

        Engine {
            position: Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 \
                                          1")
                          .ok()
                          .unwrap(),
            pondering_is_allowed: false,
            is_thinking: false,
            is_pondering: false,
            curr_depth: 0,
            replies: vec![],
            tt: tt,
            commands: commands_tx,
            reports: reports_rx,
            thinking_since: SystemTime::now(),
            searched_nodes: 0,
            searched_time: 0,
            stop_when: TimeManagement::Infinite,
        }
    }
}

impl UciEngine for Engine {
    fn set_option(&mut self, name: &str, value: &str) {
        match name {
            // Tells the engine that it will be allowed to ponder.
            // This option is needed because the engine might change
            // its time management algorithm when pondering is
            // allowed.
            "Ponder" => {
                self.pondering_is_allowed = value == "true";
            }

            // An invalid option. Notice that we do not support
            // re-sizing of the transposition table once the engine
            // had started.
            _ => (),
        }
    }

    fn new_game(&mut self) {
        // Clearing the transposition table would not change anything,
        // so we do nothing.
    }

    fn position(&mut self, fen: &str, moves: &mut Iterator<Item = &str>) {
        if !self.is_thinking {
            if let Ok(p) = Position::from_history(fen, moves) {
                self.position = p;
                self.tt.new_search();
            }
        }
    }

    #[allow(unused_variables)]
    fn go(&mut self,
          searchmoves: Option<Vec<String>>,
          ponder: bool,
          wtime: Option<u64>,
          btime: Option<u64>,
          winc: Option<u64>,
          binc: Option<u64>,
          movestogo: Option<u64>,
          depth: Option<u64>,
          nodes: Option<u64>,
          mate: Option<u64>,
          movetime: Option<u64>,
          infinite: bool) {
        if !self.is_thinking {
            // Note: We ignore the "searchmoves" parameter.

            self.is_thinking = true;
            self.is_pondering = ponder;
            self.thinking_since = SystemTime::now();
            self.searched_nodes = 0;
            self.searched_time = 0;
            self.curr_depth = 0;

            // Figure out when we should stop thinking.
            let (time, inc) = if self.position.board().to_move() == WHITE {
                (wtime, winc.unwrap_or(0))
            } else {
                (btime, binc.unwrap_or(0))
            };
            self.stop_when = if infinite {
                TimeManagement::Infinite
            } else if movetime.is_some() {
                TimeManagement::MoveTime(movetime.unwrap())
            } else if nodes.is_some() {
                TimeManagement::Nodes(nodes.unwrap())
            } else if depth.is_some() {
                TimeManagement::Depth(min(depth.unwrap(), MAX_DEPTH as u64) as u8)
            } else {
                // TODO: Robert Hyatt proposes this:
                //
                // nMoves =  min( numberOfMovesOutOfBook, 10 )
                // factor = 2 - nMoves / 10
                // target = timeLeft / numberOfMovesUntilNextTimeControl
                // time   = factor * target
                let time = time.unwrap_or(0);
                let movestogo = movestogo.unwrap_or(40);
                let movetime = (time + inc * movestogo) / movestogo;
                TimeManagement::MoveTimeHint(min(movetime, time / 2))
            };

            // Start a search with the maximum possible depth.
            self.commands
                .send(search::Command::Search {
                    search_id: 0,
                    position: self.position.clone(),
                    depth: MAX_DEPTH,
                    lower_bound: -20000,
                    upper_bound: 20000,
                })
                .unwrap();
        }
    }

    fn ponder_hit(&mut self) {
        if self.is_thinking && self.is_pondering {
            self.is_pondering = false;
        }
    }

    fn stop(&mut self) {
        if self.is_thinking {
            self.commands.send(search::Command::Stop).unwrap();
            let best_move = self.get_best_move();
            self.replies.push(EngineReply::BestMove {
                best_move: best_move,
                ponder_move: None,
            });
            self.is_thinking = false;
        }
    }

    fn is_thinking(&self) -> bool {
        self.is_thinking
    }

    fn get_reply(&mut self) -> Option<EngineReply> {
        // TODO: implement panic mode for MoveTimeHint management.
        if self.is_thinking && !self.is_pondering {
            if match self.stop_when {
                TimeManagement::MoveTime(t) => self.searched_time > t,
                TimeManagement::MoveTimeHint(t) => self.searched_time > t,
                TimeManagement::Nodes(n) => self.searched_nodes > n,
                TimeManagement::Depth(d) => self.curr_depth > d,
                TimeManagement::Infinite => false,
            } {
                self.stop();
            }
        }

        // Empty the reports queue.
        while let Ok(report) = self.reports.try_recv() {
            // let rep_type = match report {
            //     search::Report::Progress { searched_nodes, .. } => format!("progress {}", searched_nodes),
            //     search::Report::Done { .. } => "done".to_string(),
            // };
            // self.replies.push(EngineReply::Info(vec![
            //     ("string".to_string(), rep_type),
            // ]));
            if self.is_thinking {
                match report {
                    search::Report::Progress { searched_nodes, depth, .. } => {
                        let thinking_duration = self.thinking_since.elapsed().unwrap();
                        self.searched_nodes = searched_nodes;
                        self.searched_time = 1000 * thinking_duration.as_secs() +
                                             (thinking_duration.subsec_nanos() / 1000000) as u64;
                        if self.curr_depth < depth {
                            // We are done with the old depth, so we
                            // extract the PV, the value, and the
                            // score from the transposition table.
                            let mut prev_move = None;
                            let mut p = self.position.clone();
                            let mut value = -20000;
                            let mut bound = BOUND_LOWER;
                            let mut pv = Vec::new();
                            while let Some(entry) = self.tt.probe(p.hash()) {
                                if pv.len() < self.curr_depth as usize {
                                    if let Some(m) = prev_move {
                                        pv.push(m);
                                    }
                                    value = if pv.len() & 1 == 0 {
                                        entry.value()
                                    } else {
                                        -entry.value()
                                    };
                                    bound = entry.bound();
                                    if bound == BOUND_EXACT {
                                        if let Some(m) = p.try_move_digest(entry.move16()) {
                                            if p.do_move(m) {
                                                prev_move = Some(m);
                                                continue;
                                            }
                                        }
                                    }
                                }
                                break;
                            }

                            // Send the extracted info to the GUI.
                            let score_suffix = match bound {
                                BOUND_EXACT => "",
                                BOUND_UPPER => " upperbound",
                                BOUND_LOWER => " lowerbound",
                                _ => panic!("unexpected bound type"),
                            };
                            let nps = if self.searched_time == 0 {
                                0
                            } else {
                                1000 * searched_nodes / self.searched_time
                            };
                            let mut pv_string = String::new();
                            for m in pv {
                                pv_string.push(' ');
                                pv_string.push_str(&m.notation());
                            }
                            self.replies.push(EngineReply::Info(vec![
                                ("depth".to_string(), format!("{}", self.curr_depth)),
                                ("score".to_string(), format!("cp {}{}", value, score_suffix)),
                                ("time".to_string(), format!("{}", self.searched_time)),
                                ("nodes".to_string(), format!("{}", searched_nodes)),
                                ("nps".to_string(), format!("{}", nps)),
                                ("pv".to_string(), pv_string),
                            ]));

                            // Update the old depth value.
                            self.curr_depth = depth;
                        }
                    }
                    _ => (),
                }
            }
        }

        if self.replies.len() > 0 {
            Some(self.replies.remove(0))
        } else {
            None
        }
    }
}

impl Engine {
    fn get_best_move(&mut self) -> String {
        let mut m = match self.tt.probe(self.position.hash()) {
            Some(entry) => entry.move16(),
            None => 0,
        };
        if m == 0 {
            // Pick the first legal move.
            let mut first_legal_move = Move::invalid();
            let mut v = MoveStack::new();
            self.position.generate_moves(&mut v);
            while let Some(x) = v.pop() {
                if self.position.do_move(x) {
                    self.position.undo_move();
                    first_legal_move = x;
                    break;
                }
            }
            m = first_legal_move.digest();
        }
        if m != 0 {
            let move_type = move_type(m);
            let orig_square = orig_square(m);
            let dest_square = dest_square(m);
            let promoted_piece = match aux_data(m) {
                0 => "q",
                1 => "r",
                2 => "b",
                3 => "n",
                _ => panic!("invalid promoted piece code"),
            };
            format!("{}{}{}",
                    notation(orig_square),
                    notation(dest_square),
                    if move_type == MOVE_PROMOTION {
                        promoted_piece
                    } else {
                        ""
                    })

        } else {
            "0000".to_string()
        }
    }
}

/// Implements `UciEngineFactory` trait.
pub struct EngineFactory;

impl UciEngineFactory<Engine> for EngineFactory {
    fn name(&self) -> String {
        format!("Alcibiades {}", VERSION)
    }

    fn author(&self) -> String {
        "Evgeni Pandurski".to_string()
    }

    fn options(&self) -> Vec<(OptionName, OptionDescription)> {
        vec![
            // TODO: Calculate a sane limit for the hash size.
            ("Hash".to_string(), OptionDescription::Spin { min: 1, max: 2048, default: 16 }),
            ("Ponder".to_string(), OptionDescription::Check { default: false }),
        ]
    }

    fn create(&self, hash_size_mb: Option<usize>) -> Engine {
        Engine::new(hash_size_mb.unwrap_or(16))
    }
}

enum TimeManagement {
    MoveTime(u64),
    MoveTimeHint(u64),
    Nodes(NodeCount),
    Depth(u8),
    Infinite,
}
<|endoftext|>


<commit_before>use std::convert::{From, TryFrom, TryInto};
use std::str::FromStr;
use stack::Stack;
use operate::Operate;

#[derive(Debug, Copy, Clone)]
pub enum Arithm<T, O: Operate<T>> {
    Operand(T),
    Operator(O),
}

#[derive(Debug)]
pub struct Expression<T, O: Operate<T>>(Vec<Arithm<T, O>>);

impl<T: Copy, O: Operate<T> + Copy> Expression<T, O> {
    pub fn operate(&self) -> Result<T, O::Err> {
        let mut stack = Stack::new();
        for arithm in &self.0 {
            match *arithm {
                Arithm::Operand(operand) => stack.push(operand),
                Arithm::Operator(operator) => operator.operate(&mut stack)?,
            }
        }
        Ok(stack.pop().unwrap())
    }
}

#[derive(Debug, PartialEq)]
pub enum ExprResult<A, B> {
    TooManyOperands,
    NotEnoughOperand,
    InvalidToken(A, B),
}

use self::ExprResult::*;

impl<'a, T, O> TryFrom<&'a str> for Expression<T, O>
    where T: FromStr,
          O: Operate<T> + TryFrom<&'a str>
{
    type Err = ExprResult<<T as FromStr>::Err, <O as TryFrom<&'a str>>::Err>;
    fn try_from(expr: &'a str) -> Result<Self, Self::Err> {
        let mut final_expr = Vec::new();
        let mut num_operands: usize = 0;
        for token in expr.split_whitespace() {
            let arithm = match token.parse() {
                Ok(operand) => {
                    num_operands += 1;
                    Arithm::Operand(operand)
                }
                Err(operand_err) => {
                    match TryInto::<O>::try_into(token) {
                        Ok(operator) => {
                            let needed = operator.operands_needed();
                            num_operands = num_operands.checked_sub(needed)
                                .ok_or(NotEnoughOperand)?;
                            num_operands += operator.operands_generated();
                            Arithm::Operator(operator)
                        }
                        Err(operator_err) => return Err(InvalidToken(operand_err, operator_err)),
                    }
                }
            };
            final_expr.push(arithm);
        }
        match num_operands {
            0 => Err(NotEnoughOperand),
            1 => Ok(Expression(final_expr)),
            _ => Err(TooManyOperands),
        }
    }
}

impl<T, O: Operate<T>> From<Vec<Arithm<T, O>>> for Expression<T, O> {
    fn from(expr: Vec<Arithm<T, O>>) -> Self {
        Expression(expr)
    }
}
<commit_msg>expression: Add conversion from Expression<T, O> to Vec<Arithm<T, O>><commit_after>use std::convert::{From, TryFrom, TryInto};
use std::str::FromStr;
use stack::Stack;
use operate::Operate;

#[derive(Debug, Copy, Clone)]
pub enum Arithm<T, O: Operate<T>> {
    Operand(T),
    Operator(O),
}

#[derive(Debug)]
pub struct Expression<T, O: Operate<T>>(Vec<Arithm<T, O>>);

impl<T: Copy, O: Operate<T> + Copy> Expression<T, O> {
    pub fn operate(&self) -> Result<T, O::Err> {
        let mut stack = Stack::new();
        for arithm in &self.0 {
            match *arithm {
                Arithm::Operand(operand) => stack.push(operand),
                Arithm::Operator(operator) => operator.operate(&mut stack)?,
            }
        }
        Ok(stack.pop().unwrap())
    }
}

#[derive(Debug, PartialEq)]
pub enum ExprResult<A, B> {
    TooManyOperands,
    NotEnoughOperand,
    InvalidToken(A, B),
}

use self::ExprResult::*;

impl<'a, T, O> TryFrom<&'a str> for Expression<T, O>
    where T: FromStr,
          O: Operate<T> + TryFrom<&'a str>
{
    type Err = ExprResult<<T as FromStr>::Err, <O as TryFrom<&'a str>>::Err>;
    fn try_from(expr: &'a str) -> Result<Self, Self::Err> {
        let mut final_expr = Vec::new();
        let mut num_operands: usize = 0;
        for token in expr.split_whitespace() {
            let arithm = match token.parse() {
                Ok(operand) => {
                    num_operands += 1;
                    Arithm::Operand(operand)
                }
                Err(operand_err) => {
                    match TryInto::<O>::try_into(token) {
                        Ok(operator) => {
                            let needed = operator.operands_needed();
                            num_operands = num_operands.checked_sub(needed)
                                .ok_or(NotEnoughOperand)?;
                            num_operands += operator.operands_generated();
                            Arithm::Operator(operator)
                        }
                        Err(operator_err) => return Err(InvalidToken(operand_err, operator_err)),
                    }
                }
            };
            final_expr.push(arithm);
        }
        match num_operands {
            0 => Err(NotEnoughOperand),
            1 => Ok(Expression(final_expr)),
            _ => Err(TooManyOperands),
        }
    }
}

impl<T, O: Operate<T>> From<Vec<Arithm<T, O>>> for Expression<T, O> {
    fn from(expr: Vec<Arithm<T, O>>) -> Self {
        Expression(expr)
    }
}

impl<T, O: Operate<T>> Into<Vec<Arithm<T, O>>> for Expression<T, O> {
    fn into(self) -> Vec<Arithm<T, O>> {
        self.0
    }
}
<|endoftext|>


<commit_before>use std::sync::{Mutex, Arc};
use std::net::{SocketAddr, ToSocketAddrs};
use std::f64::consts::PI;
use std::collections::VecDeque;
use std::error::Error;
use opengl_graphics::GlGraphics;
use opengl_graphics::glyph_cache::GlyphCache;
use piston::input::{Button, Key, MouseButton, RenderArgs, UpdateArgs};

use std::{thread, time};
use std::net::TcpStream;
use network::{Command, Message};

use bincode::{serialize_into, deserialize_from, Infinite};

use state::{ClientId, WorldState};
use shapes::Unit;
use colors::{BLACK, YELLOW, ORANGE};

pub mod menu;

use self::menu::Menu;

pub struct NetworkClient {
    pub world_state: Arc<Mutex<WorldState>>,
    server_addr: SocketAddr,
    stream: Option<TcpStream>,
    commands: Arc<Mutex<VecDeque<Command>>>,
}

impl NetworkClient {
    pub fn new<T: ToSocketAddrs>(server_addrs: T, world_state:
                                 Arc<Mutex<WorldState>>,
                                 commands: Arc<Mutex<VecDeque<Command>>>) -> NetworkClient {
        let server_addr = server_addrs.to_socket_addrs().unwrap().next().unwrap();
        NetworkClient {
            world_state: world_state,
            server_addr: server_addr,
            stream: None,
            commands: commands,
        }
    }

    // todo: Maybe return client_id here? Would allow the application to reconnect...
    pub fn connect(&mut self) -> Result<ClientId, Box<Error>>  {
        let mut stream = TcpStream::connect(self.server_addr)?;
        serialize_into(&mut stream, &Message::ClientHello, Infinite)?;
        let server_hello = deserialize_from(&mut stream, Infinite);

        self.stream = Some(stream);
        if let Ok(Message::ServerHello(_, world_state)) = server_hello {
            let mut world_state_lock = self.world_state.lock().unwrap();
            *world_state_lock = world_state;
        } else {
            panic!("Could not connect to server");
        }
        Ok(0.into())
    }

    pub fn update(&self) {
        let stream = self.stream.as_ref().expect("Stream not here :(");
        let mut command_stream = stream.try_clone().unwrap();
        let commands = self.commands.clone();

        // Command sender loop
        thread::spawn(move || {
            loop {
                let command = {
                    let mut commands = commands.lock().unwrap();
                    commands.pop_front()
                };
                command.map(|cmd| {
                    println!("Got command: {:?}", cmd);
                    //let cmd = Message::Command(Command::Move(0.into(), [100f64, 100f64]));
                    serialize_into(&mut command_stream, &Message::Command(cmd), Infinite)
                        .unwrap_or_else(|e|println!("Sending command failed: {}", e));
                });
                thread::sleep(time::Duration::from_millis(10));
            }
        });

        let mut game_state_stream = stream.try_clone().unwrap();
        let world_state = self.world_state.clone();
        thread::spawn(move || {
            loop {
                let game_state = deserialize_from(&mut game_state_stream,
                                                  Infinite);
                match game_state {
                    Ok(game) => {
                        //println!("{:?}", game);
                        let mut world_state_lock = world_state.lock().unwrap();
                        world_state_lock.game = game;

                    }
                    Err(e) => {
                        println!("{:?}", e);
                        thread::sleep(time::Duration::from_millis(200));
                    }
                }
            }
        });
    }
}

#[derive(Clone, Copy, Debug)]
pub enum State {
    Menu,
    Running,
}

pub struct App {
    pub gl: GlGraphics, // OpenGL drawing backend.
    pub world_state: Arc<Mutex<WorldState>>,
    pub units: Vec<Unit>,
    pub commands: Arc<Mutex<VecDeque<Command>>>,
    pub cursor: [f64; 2],
    pub state: State,
    menu: Menu,
    client_id: Option<ClientId>,
}

impl App {
    pub fn new(gl: GlGraphics) -> App {
        App {
            gl: gl,
            world_state: Arc::new(Mutex::new(WorldState::new(0, 0))),
            units: vec![],
            commands: Arc::new(Mutex::new(VecDeque::new())),
            cursor: [0.0, 0.0],
            state: State::Menu,
            menu: Menu::new(),
            client_id: None,
        }
    }

    pub fn start(&mut self) -> Result<(), Box<Error>> {
        let mut network_client = NetworkClient::new(("127.0.0.1", 8080), self.world_state.clone(), self.commands.clone());
        self.client_id = Some(network_client.connect()?);
        network_client.update();
        Ok(())
    }

    pub fn select(&mut self, position: [f64;2]) {
        for u in &mut self.units {
            u.selected = u.is_hit(position);
        };
    }

    fn render_game(&mut self, args: &RenderArgs, _: &mut GlyphCache) {
        use graphics::{polygon, clear};
        use graphics::Transformed;
        use graphics::types::Polygon;

        const FRONT_THICKNESS: f64 = 5.0;

        let units = &self.units;

        self.gl.draw(args.viewport(), |c, gl| {

            // Clear the screen.
            clear(BLACK, gl);

            for s in units.iter() {

                // Create a triangle polygon. The initial orientation is facing east.
                let triangle: Polygon = &s.get_shape();

                // Create a border on the front of the polygon. This is a trapezoid.
                // Because the angle of the trapezoid side is 22.5°, we know that `dx` is always `2 * dy`.
                let front: Polygon = &[
                    [s.size, s.size],                                           // Top right
                    [s.size, 0.0],                                                 // Bottom right
                    [s.size - FRONT_THICKNESS, FRONT_THICKNESS / 2.0],             // Bottom left
                    [s.size - FRONT_THICKNESS, s.size - FRONT_THICKNESS / 2.0], // Top left
                ];

                // Rotate the front to match the unit
                let transform_front = c.transform.trans(s.position[0], s.position[1])
                    .rot_rad(s.rotation)
                    .trans(-25.0, -25.0);

                // We don't need to apply any transformation to the units
                let transform_triangle = c.transform;

                // Draw the unit ORANGE if selected
                if s.selected {
                    polygon(ORANGE, triangle, transform_triangle, gl);
                    polygon(YELLOW, front, transform_front, gl);
                } else {
                    polygon(YELLOW, triangle, transform_triangle, gl);
                    polygon(ORANGE, front, transform_front, gl);
                }

            }
        });
    }

    pub fn render(&mut self, args: &RenderArgs, cache: &mut GlyphCache) {
        match self.state {
            State::Menu => self.menu.render(args, &mut self.gl, cache), //self.render_menu(args, cache),
            State::Running => self.render_game(args, cache),
        }
    }

    pub fn update(&mut self, _: &UpdateArgs) {
        let player = {
            let world_lock = self.world_state.lock().unwrap();
            world_lock.game.players.get(0).map(|v| v.clone())
        };
        if let Some(player) = player {
            for unit in player.units.iter() {
                self.units.get_mut(unit.id.0 as usize)
                    .map(|app_unit| {
                        app_unit.position = unit.position;
                        app_unit.rotation = unit.angle;
                    })
                    .or_else(|| {
                        self.units.push(Unit::new(unit.position.clone(), unit.angle));
                        None
                    });
            }
        }
    }

    pub fn on_button_press(&mut self, button: &Button) {
        match self.state {
            State::Menu => {
                match button {
                    &Button::Keyboard(Key::Up) => {
                        self.menu.previous();
                    }
                    &Button::Keyboard(Key::Down) => {
                        self.menu.next();
                    }
                    &Button::Keyboard(Key::Return) => {
                        match self.menu.get_selected_entry() {
                            menu::Entries::Start => {
                                // TODO: Proper error handling
                                if self.start().is_ok() {
                                    self.state = State::Running;
                                }
                            }
                            menu::Entries::Exit => {
                            }
                        }
                    }
                    _ => { }
                }
            }
            State::Running => {
                match button {
                    &Button::Keyboard(_) => { }
                    &Button::Mouse(button) => {
                        self.on_mouse_click(&button);
                    }
                    &Button::Controller(_) => { }
                }
            }
        };
    }

    pub fn on_mouse_click(&mut self, button: &MouseButton) {
        let cursor = self.cursor;
        match *button {
            MouseButton::Left  => self.select(cursor),
            MouseButton::Right => self.move_selected(cursor),
            _ => println!("Pressed mouse button '{:?}'", button),
        }
    }

    pub fn on_mouse_move(&mut self, cursor: [f64; 2]) {
        self.cursor = cursor;
    }

    pub fn move_selected(&mut self, position: [f64;2]) {
        for i in 0..self.units.len() {
            let s = &mut self.units[i];
            if s.selected {
                s.target = position;
                let dx = position[0] - s.position[0];
                let dy = position[1] - s.position[1];
                if dx.is_sign_negative() {
                    s.rotation = (dy / dx).atan() + PI;
                } else {
                    s.rotation = (dy / dx).atan();
                }
                let id = i as u32;
                let mut commands = self.commands.lock().unwrap();
                commands.push_back(Command::Move(id.into(), position));
                println!("dx: {}, dy: {}, new rotation: {}", dx, dy, s.rotation);
            }
        }
    }
}
<commit_msg>client: Return ClientId on connect<commit_after>use std::sync::{Mutex, Arc};
use std::net::{SocketAddr, ToSocketAddrs};
use std::f64::consts::PI;
use std::collections::VecDeque;
use std::error::Error;
use opengl_graphics::GlGraphics;
use opengl_graphics::glyph_cache::GlyphCache;
use piston::input::{Button, Key, MouseButton, RenderArgs, UpdateArgs};

use std::{thread, time};
use std::net::TcpStream;
use network::{Command, Message};

use bincode::{serialize_into, deserialize_from, Infinite};

use state::{ClientId, WorldState};
use shapes::Unit;
use colors::{BLACK, YELLOW, ORANGE};

pub mod menu;

use self::menu::Menu;

pub struct NetworkClient {
    pub world_state: Arc<Mutex<WorldState>>,
    server_addr: SocketAddr,
    stream: Option<TcpStream>,
    commands: Arc<Mutex<VecDeque<Command>>>,
}

impl NetworkClient {
    pub fn new<T: ToSocketAddrs>(server_addrs: T, world_state:
                                 Arc<Mutex<WorldState>>,
                                 commands: Arc<Mutex<VecDeque<Command>>>) -> NetworkClient {
        let server_addr = server_addrs.to_socket_addrs().unwrap().next().unwrap();
        NetworkClient {
            world_state: world_state,
            server_addr: server_addr,
            stream: None,
            commands: commands,
        }
    }

    pub fn connect(&mut self) -> Result<ClientId, Box<Error>>  {
        let mut stream = TcpStream::connect(self.server_addr)?;
        serialize_into(&mut stream, &Message::ClientHello, Infinite)?;
        let server_hello = deserialize_from(&mut stream, Infinite);

        self.stream = Some(stream);
        if let Ok(Message::ServerHello(client_id, world_state)) = server_hello {
            let mut world_state_lock = self.world_state.lock().unwrap();
            *world_state_lock = world_state;
            Ok(client_id)
        } else {
            Err("Could not connect to server".into())
        }
    }

    pub fn update(&self) {
        let stream = self.stream.as_ref().expect("Stream not here :(");
        let mut command_stream = stream.try_clone().unwrap();
        let commands = self.commands.clone();

        // Command sender loop
        thread::spawn(move || {
            loop {
                let command = {
                    let mut commands = commands.lock().unwrap();
                    commands.pop_front()
                };
                command.map(|cmd| {
                    println!("Got command: {:?}", cmd);
                    //let cmd = Message::Command(Command::Move(0.into(), [100f64, 100f64]));
                    serialize_into(&mut command_stream, &Message::Command(cmd), Infinite)
                        .unwrap_or_else(|e|println!("Sending command failed: {}", e));
                });
                thread::sleep(time::Duration::from_millis(10));
            }
        });

        let mut game_state_stream = stream.try_clone().unwrap();
        let world_state = self.world_state.clone();
        thread::spawn(move || {
            loop {
                let game_state = deserialize_from(&mut game_state_stream,
                                                  Infinite);
                match game_state {
                    Ok(game) => {
                        //println!("{:?}", game);
                        let mut world_state_lock = world_state.lock().unwrap();
                        world_state_lock.game = game;

                    }
                    Err(e) => {
                        println!("{:?}", e);
                        thread::sleep(time::Duration::from_millis(200));
                    }
                }
            }
        });
    }
}

#[derive(Clone, Copy, Debug)]
pub enum State {
    Menu,
    Running,
}

pub struct App {
    pub gl: GlGraphics, // OpenGL drawing backend.
    pub world_state: Arc<Mutex<WorldState>>,
    pub units: Vec<Unit>,
    pub commands: Arc<Mutex<VecDeque<Command>>>,
    pub cursor: [f64; 2],
    pub state: State,
    menu: Menu,
    client_id: Option<ClientId>,
}

impl App {
    pub fn new(gl: GlGraphics) -> App {
        App {
            gl: gl,
            world_state: Arc::new(Mutex::new(WorldState::new(0, 0))),
            units: vec![],
            commands: Arc::new(Mutex::new(VecDeque::new())),
            cursor: [0.0, 0.0],
            state: State::Menu,
            menu: Menu::new(),
            client_id: None,
        }
    }

    pub fn start(&mut self) -> Result<(), Box<Error>> {
        let mut network_client = NetworkClient::new(("127.0.0.1", 8080), self.world_state.clone(), self.commands.clone());
        self.client_id = Some(network_client.connect()?);
        network_client.update();
        Ok(())
    }

    pub fn select(&mut self, position: [f64;2]) {
        for u in &mut self.units {
            u.selected = u.is_hit(position);
        };
    }

    fn render_game(&mut self, args: &RenderArgs, _: &mut GlyphCache) {
        use graphics::{polygon, clear};
        use graphics::Transformed;
        use graphics::types::Polygon;

        const FRONT_THICKNESS: f64 = 5.0;

        let units = &self.units;

        self.gl.draw(args.viewport(), |c, gl| {

            // Clear the screen.
            clear(BLACK, gl);

            for s in units.iter() {

                // Create a triangle polygon. The initial orientation is facing east.
                let triangle: Polygon = &s.get_shape();

                // Create a border on the front of the polygon. This is a trapezoid.
                // Because the angle of the trapezoid side is 22.5°, we know that `dx` is always `2 * dy`.
                let front: Polygon = &[
                    [s.size, s.size],                                           // Top right
                    [s.size, 0.0],                                                 // Bottom right
                    [s.size - FRONT_THICKNESS, FRONT_THICKNESS / 2.0],             // Bottom left
                    [s.size - FRONT_THICKNESS, s.size - FRONT_THICKNESS / 2.0], // Top left
                ];

                // Rotate the front to match the unit
                let transform_front = c.transform.trans(s.position[0], s.position[1])
                    .rot_rad(s.rotation)
                    .trans(-25.0, -25.0);

                // We don't need to apply any transformation to the units
                let transform_triangle = c.transform;

                // Draw the unit ORANGE if selected
                if s.selected {
                    polygon(ORANGE, triangle, transform_triangle, gl);
                    polygon(YELLOW, front, transform_front, gl);
                } else {
                    polygon(YELLOW, triangle, transform_triangle, gl);
                    polygon(ORANGE, front, transform_front, gl);
                }

            }
        });
    }

    pub fn render(&mut self, args: &RenderArgs, cache: &mut GlyphCache) {
        match self.state {
            State::Menu => self.menu.render(args, &mut self.gl, cache), //self.render_menu(args, cache),
            State::Running => self.render_game(args, cache),
        }
    }

    pub fn update(&mut self, _: &UpdateArgs) {
        let player = {
            let world_lock = self.world_state.lock().unwrap();
            world_lock.game.players.get(0).map(|v| v.clone())
        };
        if let Some(player) = player {
            for unit in player.units.iter() {
                self.units.get_mut(unit.id.0 as usize)
                    .map(|app_unit| {
                        app_unit.position = unit.position;
                        app_unit.rotation = unit.angle;
                    })
                    .or_else(|| {
                        self.units.push(Unit::new(unit.position.clone(), unit.angle));
                        None
                    });
            }
        }
    }

    pub fn on_button_press(&mut self, button: &Button) {
        match self.state {
            State::Menu => {
                match button {
                    &Button::Keyboard(Key::Up) => {
                        self.menu.previous();
                    }
                    &Button::Keyboard(Key::Down) => {
                        self.menu.next();
                    }
                    &Button::Keyboard(Key::Return) => {
                        match self.menu.get_selected_entry() {
                            menu::Entries::Start => {
                                // TODO: Proper error handling
                                if self.start().is_ok() {
                                    self.state = State::Running;
                                }
                            }
                            menu::Entries::Exit => {
                            }
                        }
                    }
                    _ => { }
                }
            }
            State::Running => {
                match button {
                    &Button::Keyboard(_) => { }
                    &Button::Mouse(button) => {
                        self.on_mouse_click(&button);
                    }
                    &Button::Controller(_) => { }
                }
            }
        };
    }

    pub fn on_mouse_click(&mut self, button: &MouseButton) {
        let cursor = self.cursor;
        match *button {
            MouseButton::Left  => self.select(cursor),
            MouseButton::Right => self.move_selected(cursor),
            _ => println!("Pressed mouse button '{:?}'", button),
        }
    }

    pub fn on_mouse_move(&mut self, cursor: [f64; 2]) {
        self.cursor = cursor;
    }

    pub fn move_selected(&mut self, position: [f64;2]) {
        for i in 0..self.units.len() {
            let s = &mut self.units[i];
            if s.selected {
                s.target = position;
                let dx = position[0] - s.position[0];
                let dy = position[1] - s.position[1];
                if dx.is_sign_negative() {
                    s.rotation = (dy / dx).atan() + PI;
                } else {
                    s.rotation = (dy / dx).atan();
                }
                let id = i as u32;
                let mut commands = self.commands.lock().unwrap();
                commands.push_back(Command::Move(id.into(), position));
                println!("dx: {}, dy: {}, new rotation: {}", dx, dy, s.rotation);
            }
        }
    }
}
<|endoftext|>


<commit_before>use super::{ClientError, ClientErrorKind, hyper};
use super::entities::{Mbid, Resource};

use hyper::Url;
use hyper::header::UserAgent;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use std::io::Read;
use xpath_reader::reader::{FromXmlContained, XpathStrReader};

pub mod search;
use self::search::{AreaSearchBuilder, ArtistSearchBuilder, ReleaseGroupSearchBuilder};
pub use self::search::SearchBuilder;

mod error;
use self::error::check_response_error;

/// Configuration for the client.
pub struct ClientConfig {
    /// The user-agent to be sent with every request to the API.
    ///
    /// Provide a meaningful one as it will be used by MusicBrainz to identify
    /// your application and
    /// without a user agent sever throttling will be undertaken. The official
    /// suggestion is to use
    /// either one of the following two options:
    ///
    /// * `Application name/<version> ( contact-url )`
    /// * `Application name/<version> ( contact-email )`
    ///
    /// For more information see:
    /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
    pub user_agent: String,
}

/// The main struct to be used to communicate with the MusicBrainz API.
pub struct Client {
    config: ClientConfig,
    http_client: hyper::Client,
}

impl Client {
    pub fn new(config: ClientConfig) -> Result<Self, ClientError>
    {
        let ssl = NativeTlsClient::new()?;
        let connector = HttpsConnector::new(ssl);

        Ok(Client {
               config: config,
               http_client: hyper::Client::with_connector(connector),
           })
    }

    /// Fetch the specified ressource from the server and parse it.
    pub fn get_by_mbid<Res>(&self, mbid: &Mbid) -> Result<Res, ClientError>
        where Res: Resource + FromXmlContained
    {
        use entities::default_musicbrainz_context;

        let url = Res::get_url(mbid);
        let response_body = self.get_body(url.parse()?)?;

        // Parse the response.
        let context = default_musicbrainz_context();
        let reader = XpathStrReader::new(&response_body[..], &context)?;
        check_response_error(&reader)?;
        Ok(Res::from_xml(&reader)?)
    }

    fn get_body(&self, url: Url) -> Result<String, ClientError>
    {
        let mut response =
            self.http_client.get(url).header(UserAgent(self.config.user_agent.clone())).send()?;
        let mut response_body = String::new();
        response.read_to_string(&mut response_body)?;
        Ok(response_body)
    }

    /// Returns a search builder to search for an area.
    pub fn search_area<'cl>(&'cl self) -> AreaSearchBuilder<'cl>
    {
        AreaSearchBuilder::new(self)
    }

    /// Returns a search biulder to search for an artist.
    pub fn search_artist<'cl>(&'cl self) -> ArtistSearchBuilder<'cl>
    {
        ArtistSearchBuilder::new(self)
    }

    /// Returns a search builder to search for a release group.
    pub fn search_release_group<'cl>(&'cl self) -> ReleaseGroupSearchBuilder<'cl>
    {
        ReleaseGroupSearchBuilder::new(self)
    }
}

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

    fn get_client() -> Client
    {
        let config = ClientConfig { user_agent: "MusicBrainz-Rust/Testing".to_string() };
        Client::new(config).unwrap()
    }

    #[test]
    fn search_release_group()
    {
        let client = get_client();
        let results = client
            .search_release_group()
            .add(search::fields::release_group::ReleaseGroupName("霊魂消滅".to_owned()))
            .search()
            .unwrap();

        assert_eq!(results.len(), 1);
        assert_eq!(results[0].score, 100);
        assert_eq!(results[0].entity.mbid,
                   "739de9cd-7e81-4bb0-9fdb-0feb7ea709c7".parse().unwrap());
        assert_eq!(results[0].entity.title, "霊魂消滅".to_string());
    }
}
<commit_msg>Add docstring + derive<commit_after>use super::{ClientError, ClientErrorKind, hyper};
use super::entities::{Mbid, Resource};

use hyper::Url;
use hyper::header::UserAgent;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use std::io::Read;
use xpath_reader::reader::{FromXmlContained, XpathStrReader};

pub mod search;
use self::search::{AreaSearchBuilder, ArtistSearchBuilder, ReleaseGroupSearchBuilder};
pub use self::search::SearchBuilder;

mod error;
use self::error::check_response_error;

/// Configuration for the client.
#[derive(Clone, Debug)]
pub struct ClientConfig {
    /// The user-agent to be sent with every request to the API.
    ///
    /// Provide a meaningful one as it will be used by MusicBrainz to identify
    /// your application and
    /// without a user agent sever throttling will be undertaken. The official
    /// suggestion is to use
    /// either one of the following two options:
    ///
    /// * `Application name/<version> ( contact-url )`
    /// * `Application name/<version> ( contact-email )`
    ///
    /// For more information see:
    /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
    pub user_agent: String,
}

/// The main struct to be used to communicate with the MusicBrainz API.
pub struct Client {
    config: ClientConfig,
    http_client: hyper::Client,
}

impl Client {
    /// Create a new Client instance.
    pub fn new(config: ClientConfig) -> Result<Self, ClientError>
    {
        let ssl = NativeTlsClient::new()?;
        let connector = HttpsConnector::new(ssl);

        Ok(Client {
               config: config,
               http_client: hyper::Client::with_connector(connector),
           })
    }

    /// Fetch the specified ressource from the server and parse it.
    pub fn get_by_mbid<Res>(&self, mbid: &Mbid) -> Result<Res, ClientError>
        where Res: Resource + FromXmlContained
    {
        use entities::default_musicbrainz_context;

        let url = Res::get_url(mbid);
        let response_body = self.get_body(url.parse()?)?;

        // Parse the response.
        let context = default_musicbrainz_context();
        let reader = XpathStrReader::new(&response_body[..], &context)?;
        check_response_error(&reader)?;
        Ok(Res::from_xml(&reader)?)
    }

    fn get_body(&self, url: Url) -> Result<String, ClientError>
    {
        let mut response =
            self.http_client.get(url).header(UserAgent(self.config.user_agent.clone())).send()?;
        let mut response_body = String::new();
        response.read_to_string(&mut response_body)?;
        Ok(response_body)
    }

    /// Returns a search builder to search for an area.
    pub fn search_area<'cl>(&'cl self) -> AreaSearchBuilder<'cl>
    {
        AreaSearchBuilder::new(self)
    }

    /// Returns a search biulder to search for an artist.
    pub fn search_artist<'cl>(&'cl self) -> ArtistSearchBuilder<'cl>
    {
        ArtistSearchBuilder::new(self)
    }

    /// Returns a search builder to search for a release group.
    pub fn search_release_group<'cl>(&'cl self) -> ReleaseGroupSearchBuilder<'cl>
    {
        ReleaseGroupSearchBuilder::new(self)
    }
}

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

    fn get_client() -> Client
    {
        let config = ClientConfig { user_agent: "MusicBrainz-Rust/Testing".to_string() };
        Client::new(config).unwrap()
    }

    #[test]
    fn search_release_group()
    {
        let client = get_client();
        let results = client
            .search_release_group()
            .add(search::fields::release_group::ReleaseGroupName("霊魂消滅".to_owned()))
            .search()
            .unwrap();

        assert_eq!(results.len(), 1);
        assert_eq!(results[0].score, 100);
        assert_eq!(results[0].entity.mbid,
                   "739de9cd-7e81-4bb0-9fdb-0feb7ea709c7".parse().unwrap());
        assert_eq!(results[0].entity.title, "霊魂消滅".to_string());
    }
}
<|endoftext|>


<commit_before>use bytes::Bytes;
use client::types::ClientTypes;
use common::sender::CommonSender;
use common::sender::SendError;
use common::window_size::StreamDead;
use error;
use futures::Poll;
use futures::Stream;
use ErrorCode;
use Headers;
use HttpStreamAfterHeaders;

/// Reference to outgoing stream on the client side.
pub struct ClientRequest {
    pub(crate) common: CommonSender<ClientTypes>,
}

impl ClientRequest {
    /// Wait for stream to be ready to accept data.
    pub fn poll(&mut self) -> Poll<(), StreamDead> {
        self.common.poll()
    }

    /// Synchronously wait till outgoing stream has non-zero space
    pub fn block_wait(&mut self) -> Result<(), StreamDead> {
        self.common.block_wait()
    }

    /// Enqueue data to outgoing stream
    ///
    /// This operation fails if stream is in incorrect state.
    ///
    /// The operation does not fail if stream or connection windows is not available,
    /// in that case message will be queued until peer increases window.
    pub fn send_data(&mut self, data: Bytes) -> Result<(), SendError> {
        self.common.send_data(data)
    }

    /// Send last `DATA` frame
    pub fn send_data_end_of_stream(&mut self, data: Bytes) -> Result<(), SendError> {
        self.common.send_data_end_of_stream(data)
    }

    /// Send trailing headers
    pub fn send_trailers(&mut self, trailers: Headers) -> Result<(), SendError> {
        self.common.send_trailers(trailers)
    }

    pub fn pull_from_stream(&mut self, stream: HttpStreamAfterHeaders) -> Result<(), SendError> {
        self.common.pull_from_stream(stream)
    }

    pub fn pull_bytes_from_stream<S>(&mut self, stream: S) -> Result<(), SendError>
    where
        S: Stream<Item = Bytes, Error = error::Error> + Send + 'static,
    {
        self.common.pull_bytes_from_stream(stream)
    }

    pub fn reset(&mut self, error_code: ErrorCode) -> Result<(), SendError> {
        self.common.reset(error_code)
    }

    pub fn close(&mut self) -> Result<(), SendError> {
        self.common.close()
    }
}
<commit_msg>expose SenderState from ClientSender<commit_after>use bytes::Bytes;
use client::types::ClientTypes;
use common::sender::CommonSender;
use common::sender::SendError;
use common::window_size::StreamDead;
use error;
use futures::Poll;
use futures::Stream;
use ErrorCode;
use Headers;
use HttpStreamAfterHeaders;
use SenderState;

/// Reference to outgoing stream on the client side.
pub struct ClientRequest {
    pub(crate) common: CommonSender<ClientTypes>,
}

impl ClientRequest {
    pub fn state(&self) -> SenderState {
        self.common.state()
    }

    /// Wait for stream to be ready to accept data.
    pub fn poll(&mut self) -> Poll<(), StreamDead> {
        self.common.poll()
    }

    /// Synchronously wait till outgoing stream has non-zero space
    pub fn block_wait(&mut self) -> Result<(), StreamDead> {
        self.common.block_wait()
    }

    /// Enqueue data to outgoing stream
    ///
    /// This operation fails if stream is in incorrect state.
    ///
    /// The operation does not fail if stream or connection windows is not available,
    /// in that case message will be queued until peer increases window.
    pub fn send_data(&mut self, data: Bytes) -> Result<(), SendError> {
        self.common.send_data(data)
    }

    /// Send last `DATA` frame
    pub fn send_data_end_of_stream(&mut self, data: Bytes) -> Result<(), SendError> {
        self.common.send_data_end_of_stream(data)
    }

    /// Send trailing headers
    pub fn send_trailers(&mut self, trailers: Headers) -> Result<(), SendError> {
        self.common.send_trailers(trailers)
    }

    pub fn pull_from_stream(&mut self, stream: HttpStreamAfterHeaders) -> Result<(), SendError> {
        self.common.pull_from_stream(stream)
    }

    pub fn pull_bytes_from_stream<S>(&mut self, stream: S) -> Result<(), SendError>
    where
        S: Stream<Item = Bytes, Error = error::Error> + Send + 'static,
    {
        self.common.pull_bytes_from_stream(stream)
    }

    pub fn reset(&mut self, error_code: ErrorCode) -> Result<(), SendError> {
        self.common.reset(error_code)
    }

    pub fn close(&mut self) -> Result<(), SendError> {
        self.common.close()
    }
}
<|endoftext|>