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

use clap::{App, Arg, ArgMatches};
use walkdir::WalkDir;
use serde_json;

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

#[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")
        .arg(Arg::with_name("org")
            .value_name("ORG")
            .long("org")
            .short("o")
            .help("The organization slug"))
        .arg(Arg::with_name("project")
            .value_name("PROJECT")
            .long("project")
            .short("p")
            .help("The project slug"))
        .arg(Arg::with_name("verbose")
            .long("verbose")
            .short("verbose")
            .help("Enable verbose mode"))
        .arg(Arg::with_name("build_script")
             .value_name("BUILD_SCRIPT")
             .index(1)
             .help("Optional path to the build script"))
}

fn load_info_plist<P: AsRef<Path>>(path: P) -> Result<InfoPlist> {
    let fpl_fn = Some("info.plist".to_string());
    for dent_res in WalkDir::new(path.as_ref()) {
        let dent = dent_res?;
        if dent.file_name().to_str().map(|x| x.to_lowercase()) == fpl_fn {
            let md = dent.metadata()?;
            if md.is_file() {
                return Ok(InfoPlist::from_path(dent.path())?);
            }
        }
    }
    Err("Could not find info.plist".into())
}

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 = match env::var("CONFIGURATION") {
        Ok(config) => {
            if &config == "Debug" {
                false
            } else {
                true
            }
        }
        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()?;

    info!("Using react-native build script at {}", base.display());

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap {
        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 = load_info_plist(&base)?;
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::new()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    // 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.
    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);

    let mut f = fs::File::open(report_file.path())?;
    let report : SourceMapReport = serde_json::from_reader(&mut f)?;

    // if the report is complete, we can now upload sourcemaps
    if report.bundle_path.is_some() && report.sourcemap_path.is_some() {
        println!("Processing react-native sourcemaps for Sentry upload.");
        let bundle_path = report.bundle_path.unwrap();
        info!("  bundle path: {}", bundle_path.display());
        let sourcemap_path = report.sourcemap_path.unwrap();
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new(matches.is_present("verbose"));
        processor.add(&format!("~/{}", bundle_path.file_name()
                               .unwrap().to_string_lossy()), &bundle_path)?;
        processor.add(&format!("~/{}", sourcemap_path.file_name()
                               .unwrap().to_string_lossy()), &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>Added a force option to react-native-xcode<commit_after>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process;

use clap::{App, Arg, ArgMatches};
use walkdir::WalkDir;
use serde_json;

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

#[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")
        .arg(Arg::with_name("org")
            .value_name("ORG")
            .long("org")
            .short("o")
            .help("The organization slug"))
        .arg(Arg::with_name("project")
            .value_name("PROJECT")
            .long("project")
            .short("p")
            .help("The project slug"))
        .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"))
        .arg(Arg::with_name("build_script")
             .value_name("BUILD_SCRIPT")
             .index(1)
             .help("Optional path to the build script"))
}

fn load_info_plist<P: AsRef<Path>>(path: P) -> Result<InfoPlist> {
    let fpl_fn = Some("info.plist".to_string());
    for dent_res in WalkDir::new(path.as_ref()) {
        let dent = dent_res?;
        if dent.file_name().to_str().map(|x| x.to_lowercase()) == fpl_fn {
            let md = dent.metadata()?;
            if md.is_file() {
                return Ok(InfoPlist::from_path(dent.path())?);
            }
        }
    }
    Err("Could not find info.plist".into())
}

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) => {
            if &config == "Debug" {
                false
            } else {
                true
            }
        }
        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()?;

    info!("Using react-native build script at {}", base.display());

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap {
        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 = load_info_plist(&base)?;
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::new()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    // 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.
    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);

    let mut f = fs::File::open(report_file.path())?;
    let report : SourceMapReport = serde_json::from_reader(&mut f)?;

    // if the report is complete, we can now upload sourcemaps
    if report.bundle_path.is_some() && report.sourcemap_path.is_some() {
        println!("Processing react-native sourcemaps for Sentry upload.");
        let bundle_path = report.bundle_path.unwrap();
        info!("  bundle path: {}", bundle_path.display());
        let sourcemap_path = report.sourcemap_path.unwrap();
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new(matches.is_present("verbose"));
        processor.add(&format!("~/{}", bundle_path.file_name()
                               .unwrap().to_string_lossy()), &bundle_path)?;
        processor.add(&format!("~/{}", sourcemap_path.file_name()
                               .unwrap().to_string_lossy()), &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>use specs::*;
use util::*;

use amq_protocol_types::{AMQPType, ShortShortUInt};
use handlebars::{self, Handlebars, Helper, Renderable, RenderContext, RenderError, to_json};
use serde_json::{self};

use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::collections::BTreeMap;

pub type CodeGenerator = Handlebars;

pub trait HandlebarsAMQPExtension {
    fn register_amqp_helpers(self) -> Self;
    fn simple_codegen(out_dir: &str, target: &str, template_name: &str, template: &str, var_name: &str);
}

impl HandlebarsAMQPExtension for CodeGenerator {
    fn register_amqp_helpers(mut self) -> CodeGenerator {
        self.register_escape_fn(handlebars::no_escape);
        self.register_helper("camel",         Box::new(camel_helper));
        self.register_helper("snake",         Box::new(snake_helper));
        self.register_helper("snake_type",    Box::new(snake_type_helper));
        self.register_helper("sanitize_name", Box::new(sanitize_name_helper));
        self.register_helper("bitmask",       Box::new(bitmask_helper));
        self.register_helper("each_argument", Box::new(each_argument_helper));
        self.register_helper("each_flag",     Box::new(each_flag_helper));
        self
    }

    fn simple_codegen(out_dir: &str, target: &str, template_name: &str, template: &str, var_name: &str) {
        let dest_path   = Path::new(out_dir).join(format!("{}.rs", target));
        let mut f       = File::create(&dest_path).expect(&format!("Failed to create {}.rs", target));
        let specs       = AMQProtocolDefinition::load();
        let mut codegen = CodeGenerator::new().register_amqp_helpers();
        let mut data    = BTreeMap::new();

        codegen.register_template_string(template_name, template.to_string()).expect(&format!("Failed to register {} template", template_name));
        data.insert(var_name.to_string(), specs);

        writeln!(f, "{}", codegen.render(template_name, &data).expect(&format!("Failed to render {} template", template_name))).expect(&format!("Failed to generate {}.rs", target));
    }
}

pub fn camel_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"camel\""))?;
    let param = value.value().as_str().ok_or_else(|| RenderError::new("Non-string param given to helper \"camel\""))?;
    rc.writer.write(camel_case(param).as_bytes())?;
    Ok(())
}

pub fn snake_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"snake\""))?;
    let param = value.value().as_str().ok_or_else(|| RenderError::new("Non-string param given to helper \"snake\""))?;
    rc.writer.write(snake_case(param).as_bytes())?;
    Ok(())
}

pub fn snake_type_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value           = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"snake\""))?;
    let param: AMQPType = serde_json::from_value(value.value().clone()).map_err(|_| RenderError::new("Param is not an AMQPType for helper \"snake_type\""))?;
    rc.writer.write(snake_case(&param.to_string()).as_bytes())?;
    Ok(())
}

pub fn sanitize_name_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"sanitize_name\""))?;
    let param = value.value().as_str().ok_or_else(|| RenderError::new("Non-string param given to helper \"sanitize_name\""))?;
    rc.writer.write(param.replace('-', "_").as_bytes())?;
    Ok(())
}

pub fn bitmask_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value1                = h.param(0).ok_or_else(|| RenderError::new("First param not found for helper \"bitmask\""))?;
    let value2                = h.param(1).ok_or_else(|| RenderError::new("Second param not found for helper \"bitmask\""))?;
    let nbits: ShortShortUInt = serde_json::from_value(value1.value().clone()).map_err(|_| RenderError::new("First param is not a ShortShortUInt for helper \"bitmask\""))?;
    let index: ShortShortUInt = serde_json::from_value(value2.value().clone()).map_err(|_| RenderError::new("Second param is not a ShortShortUInt for helper \"bitmask\""))?;
    rc.writer.write(format!("{}", 1 << (nbits - index - 1)).as_bytes())?;
    Ok(())
}

pub fn each_argument_helper (h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"each_argument\""))?;

    if let Some(t) = h.template() {
        rc.promote_local_vars();
        let local_path_root = value.path_root().map(|p| format!("{}/{}", rc.get_path(), p));
        let arguments : Vec<AMQPArgument> = serde_json::from_value(value.value().clone()).map_err(|_| RenderError::new("Param is not a Vec<AMQPArgument> for helper \"each_argument\""))?;
        for (index, argument) in arguments.iter().enumerate() {
            let mut local_rc = rc.derive();
            if let Some(ref p) = local_path_root {
                local_rc.push_local_path_root(p.clone());
            }
            local_rc.set_local_var("@index".to_string(), to_json(&index));
            if let Some(inner_path) = value.path() {
                let new_path = format!("{}/{}.[{}]", local_rc.get_path(), inner_path, index);
                local_rc.set_path(new_path.clone());
            }
            if let Some(block_param) = h.block_param() {
                let mut map = BTreeMap::new();
                match *argument {
                    AMQPArgument::Value(ref v) => {
                        map.insert(block_param.to_string(), to_json(v));
                        map.insert("argument_is_value".to_string(), to_json(&true));
                    },
                    AMQPArgument::Flags(ref f) => {
                        map.insert(block_param.to_string(), to_json(f));
                        map.insert("argument_is_value".to_string(), to_json(&false));
                    },
                };
                local_rc.push_block_context(&map);
            }
            t.render(r, &mut local_rc)?;
            if h.block_param().is_some() {
                local_rc.pop_block_context();
            }
            if local_path_root.is_some() {
                local_rc.pop_local_path_root();
            }
        }
        rc.demote_local_vars();
    }
    Ok(())
}

pub fn each_flag_helper (h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"each_flag\""))?;

    if let Some(t) = h.template() {
        rc.promote_local_vars();
        let local_path_root = value.path_root().map(|p| format!("{}/{}", rc.get_path(), p));
        let flags : Vec<AMQPFlagArgument> = serde_json::from_value(value.value().clone()).map_err(|_| RenderError::new("Param is not a Vec<AMQPFlagArgument> for helper \"each_flag\""))?;
        for (index, flag) in flags.iter().enumerate() {
            let mut local_rc = rc.derive();
            if let Some(ref p) = local_path_root {
                local_rc.push_local_path_root(p.clone());
            }
            local_rc.set_local_var("@index".to_string(), to_json(&index));
            if let Some(inner_path) = value.path() {
                let new_path = format!("{}/{}.[{}]", local_rc.get_path(), inner_path, index);
                local_rc.set_path(new_path.clone());
            }
            if let Some(block_param) = h.block_param() {
                let mut map = BTreeMap::new();
                map.insert(block_param.to_string(), to_json(flag));
                local_rc.push_block_context(&map);
            }
            t.render(r, &mut local_rc)?;
            if h.block_param().is_some() {
                local_rc.pop_block_context();
            }
            if local_path_root.is_some() {
                local_rc.pop_local_path_root();
            }
        }
        rc.demote_local_vars();
    }
    Ok(())
}

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

    use amq_protocol_types::*;

    use std::collections::BTreeMap;

    pub const TEMPLATE: &'static str = r#"
{{protocol.name}} - {{protocol.major_version}}.{{protocol.minor_version}}.{{protocol.revision}}
{{protocol.copyright}}
port {{protocol.port}}
{{#each protocol.domains ~}}
{{@key}}: {{this}}
{{/each ~}}
{{#each protocol.constants as |constant| ~}}
{{constant.name}} = {{constant.value}}
{{/each ~}}
{{#each protocol.classes as |class| ~}}
{{class.id}} - {{class.name}}
{{#each class.properties as |property| ~}}
{{property.name}}: {{property.type}}
{{/each ~}}
{{#each class.methods as |method| ~}}
{{method.id}} - {{method.name}}
synchronous: {{method.synchronous}}
{{#each_argument method.arguments as |argument| ~}}
{{#if argument_is_value ~}}
{{argument.name}}({{argument.domain}}): {{argument.type}}
{{else}}
{{#each_flag argument as |flag| ~}}
{{flag.name}}: {{flag.default_value}}
{{/each_flag ~}}
{{/if ~}}
{{/each_argument ~}}
{{/each ~}}
{{/each ~}}
"#;

    fn specs() -> AMQProtocolDefinition {
        let mut domains = BTreeMap::new();
        domains.insert("domain1".to_string(), AMQPType::LongString);
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     "Copyright 1\nCopyright 2".to_string(),
            domains:       domains,
            constants:     vec![
                AMQPConstant {
                    name:      "constant1".to_string(),
                    amqp_type: AMQPType::ShortUInt,
                    value:     128,
                }
            ],
            soft_errors:   Vec::new(),
            hard_errors:   Vec::new(),
            classes:       vec![
                AMQPClass {
                    id:             42,
                    methods:        vec![
                        AMQPMethod {
                            id:            64,
                            arguments:     vec![
                                AMQPArgument::Value(AMQPValueArgument {
                                    amqp_type:     AMQPType::LongString,
                                    name:          "argument1".to_string(),
                                    default_value: Some(AMQPValue::LongString("value1".to_string())),
                                    domain:        Some("domain1".to_string()),
                                }),
                                AMQPArgument::Flags(vec![
                                    AMQPFlagArgument {
                                        name:         "flag1".to_string(),
                                        default_value: true,
                                    },
                                    AMQPFlagArgument {
                                        name:         "flag2".to_string(),
                                        default_value: false,
                                    },
                                ]),
                            ],
                            has_arguments: true,
                            has_flags:     true,
                            name:          "method1".to_string(),
                            synchronous:   true,
                        }
                    ],
                    name:           "class1".to_string(),
                    properties:     vec![
                        AMQPProperty {
                            amqp_type: AMQPType::LongString,
                            name:      "property1".to_string(),
                        }
                    ],
                    has_properties: true,
                    is_connection:  false,
                }
            ],
        }
    }

    #[test]
    fn main_template() {
        let mut data    = BTreeMap::new();
        let mut codegen = CodeGenerator::new().register_amqp_helpers();
        data.insert("protocol".to_string(), specs());
        codegen.register_template_string("main", TEMPLATE.to_string()).unwrap();
        assert_eq!(codegen.render("main", &data).unwrap(), r#"
AMQP - 0.9.1
Copyright 1
Copyright 2
port 5672
domain1: LongString
constant1 = 128
42 - class1
property1: LongString
64 - method1
synchronous: true
argument1(domain1): LongString

flag1: true
flag2: false

"#);
    }
}
<commit_msg>codegen: drop bitmask helper<commit_after>use specs::*;
use util::*;

use amq_protocol_types::AMQPType;
use handlebars::{self, Handlebars, Helper, Renderable, RenderContext, RenderError, to_json};
use serde_json::{self};

use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::collections::BTreeMap;

pub type CodeGenerator = Handlebars;

pub trait HandlebarsAMQPExtension {
    fn register_amqp_helpers(self) -> Self;
    fn simple_codegen(out_dir: &str, target: &str, template_name: &str, template: &str, var_name: &str);
}

impl HandlebarsAMQPExtension for CodeGenerator {
    fn register_amqp_helpers(mut self) -> CodeGenerator {
        self.register_escape_fn(handlebars::no_escape);
        self.register_helper("camel",         Box::new(camel_helper));
        self.register_helper("snake",         Box::new(snake_helper));
        self.register_helper("snake_type",    Box::new(snake_type_helper));
        self.register_helper("sanitize_name", Box::new(sanitize_name_helper));
        self.register_helper("each_argument", Box::new(each_argument_helper));
        self.register_helper("each_flag",     Box::new(each_flag_helper));
        self
    }

    fn simple_codegen(out_dir: &str, target: &str, template_name: &str, template: &str, var_name: &str) {
        let dest_path   = Path::new(out_dir).join(format!("{}.rs", target));
        let mut f       = File::create(&dest_path).expect(&format!("Failed to create {}.rs", target));
        let specs       = AMQProtocolDefinition::load();
        let mut codegen = CodeGenerator::new().register_amqp_helpers();
        let mut data    = BTreeMap::new();

        codegen.register_template_string(template_name, template.to_string()).expect(&format!("Failed to register {} template", template_name));
        data.insert(var_name.to_string(), specs);

        writeln!(f, "{}", codegen.render(template_name, &data).expect(&format!("Failed to render {} template", template_name))).expect(&format!("Failed to generate {}.rs", target));
    }
}

pub fn camel_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"camel\""))?;
    let param = value.value().as_str().ok_or_else(|| RenderError::new("Non-string param given to helper \"camel\""))?;
    rc.writer.write(camel_case(param).as_bytes())?;
    Ok(())
}

pub fn snake_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"snake\""))?;
    let param = value.value().as_str().ok_or_else(|| RenderError::new("Non-string param given to helper \"snake\""))?;
    rc.writer.write(snake_case(param).as_bytes())?;
    Ok(())
}

pub fn snake_type_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value           = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"snake\""))?;
    let param: AMQPType = serde_json::from_value(value.value().clone()).map_err(|_| RenderError::new("Param is not an AMQPType for helper \"snake_type\""))?;
    rc.writer.write(snake_case(&param.to_string()).as_bytes())?;
    Ok(())
}

pub fn sanitize_name_helper (h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"sanitize_name\""))?;
    let param = value.value().as_str().ok_or_else(|| RenderError::new("Non-string param given to helper \"sanitize_name\""))?;
    rc.writer.write(param.replace('-', "_").as_bytes())?;
    Ok(())
}

pub fn each_argument_helper (h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"each_argument\""))?;

    if let Some(t) = h.template() {
        rc.promote_local_vars();
        let local_path_root = value.path_root().map(|p| format!("{}/{}", rc.get_path(), p));
        let arguments : Vec<AMQPArgument> = serde_json::from_value(value.value().clone()).map_err(|_| RenderError::new("Param is not a Vec<AMQPArgument> for helper \"each_argument\""))?;
        for (index, argument) in arguments.iter().enumerate() {
            let mut local_rc = rc.derive();
            if let Some(ref p) = local_path_root {
                local_rc.push_local_path_root(p.clone());
            }
            local_rc.set_local_var("@index".to_string(), to_json(&index));
            if let Some(inner_path) = value.path() {
                let new_path = format!("{}/{}.[{}]", local_rc.get_path(), inner_path, index);
                local_rc.set_path(new_path.clone());
            }
            if let Some(block_param) = h.block_param() {
                let mut map = BTreeMap::new();
                match *argument {
                    AMQPArgument::Value(ref v) => {
                        map.insert(block_param.to_string(), to_json(v));
                        map.insert("argument_is_value".to_string(), to_json(&true));
                    },
                    AMQPArgument::Flags(ref f) => {
                        map.insert(block_param.to_string(), to_json(f));
                        map.insert("argument_is_value".to_string(), to_json(&false));
                    },
                };
                local_rc.push_block_context(&map);
            }
            t.render(r, &mut local_rc)?;
            if h.block_param().is_some() {
                local_rc.pop_block_context();
            }
            if local_path_root.is_some() {
                local_rc.pop_local_path_root();
            }
        }
        rc.demote_local_vars();
    }
    Ok(())
}

pub fn each_flag_helper (h: &Helper, r: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
    let value = h.param(0).ok_or_else(|| RenderError::new("Param not found for helper \"each_flag\""))?;

    if let Some(t) = h.template() {
        rc.promote_local_vars();
        let local_path_root = value.path_root().map(|p| format!("{}/{}", rc.get_path(), p));
        let flags : Vec<AMQPFlagArgument> = serde_json::from_value(value.value().clone()).map_err(|_| RenderError::new("Param is not a Vec<AMQPFlagArgument> for helper \"each_flag\""))?;
        for (index, flag) in flags.iter().enumerate() {
            let mut local_rc = rc.derive();
            if let Some(ref p) = local_path_root {
                local_rc.push_local_path_root(p.clone());
            }
            local_rc.set_local_var("@index".to_string(), to_json(&index));
            if let Some(inner_path) = value.path() {
                let new_path = format!("{}/{}.[{}]", local_rc.get_path(), inner_path, index);
                local_rc.set_path(new_path.clone());
            }
            if let Some(block_param) = h.block_param() {
                let mut map = BTreeMap::new();
                map.insert(block_param.to_string(), to_json(flag));
                local_rc.push_block_context(&map);
            }
            t.render(r, &mut local_rc)?;
            if h.block_param().is_some() {
                local_rc.pop_block_context();
            }
            if local_path_root.is_some() {
                local_rc.pop_local_path_root();
            }
        }
        rc.demote_local_vars();
    }
    Ok(())
}

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

    use amq_protocol_types::*;

    use std::collections::BTreeMap;

    pub const TEMPLATE: &'static str = r#"
{{protocol.name}} - {{protocol.major_version}}.{{protocol.minor_version}}.{{protocol.revision}}
{{protocol.copyright}}
port {{protocol.port}}
{{#each protocol.domains ~}}
{{@key}}: {{this}}
{{/each ~}}
{{#each protocol.constants as |constant| ~}}
{{constant.name}} = {{constant.value}}
{{/each ~}}
{{#each protocol.classes as |class| ~}}
{{class.id}} - {{class.name}}
{{#each class.properties as |property| ~}}
{{property.name}}: {{property.type}}
{{/each ~}}
{{#each class.methods as |method| ~}}
{{method.id}} - {{method.name}}
synchronous: {{method.synchronous}}
{{#each_argument method.arguments as |argument| ~}}
{{#if argument_is_value ~}}
{{argument.name}}({{argument.domain}}): {{argument.type}}
{{else}}
{{#each_flag argument as |flag| ~}}
{{flag.name}}: {{flag.default_value}}
{{/each_flag ~}}
{{/if ~}}
{{/each_argument ~}}
{{/each ~}}
{{/each ~}}
"#;

    fn specs() -> AMQProtocolDefinition {
        let mut domains = BTreeMap::new();
        domains.insert("domain1".to_string(), AMQPType::LongString);
        AMQProtocolDefinition {
            name:          "AMQP".to_string(),
            major_version: 0,
            minor_version: 9,
            revision:      1,
            port:          5672,
            copyright:     "Copyright 1\nCopyright 2".to_string(),
            domains:       domains,
            constants:     vec![
                AMQPConstant {
                    name:      "constant1".to_string(),
                    amqp_type: AMQPType::ShortUInt,
                    value:     128,
                }
            ],
            soft_errors:   Vec::new(),
            hard_errors:   Vec::new(),
            classes:       vec![
                AMQPClass {
                    id:             42,
                    methods:        vec![
                        AMQPMethod {
                            id:            64,
                            arguments:     vec![
                                AMQPArgument::Value(AMQPValueArgument {
                                    amqp_type:     AMQPType::LongString,
                                    name:          "argument1".to_string(),
                                    default_value: Some(AMQPValue::LongString("value1".to_string())),
                                    domain:        Some("domain1".to_string()),
                                }),
                                AMQPArgument::Flags(vec![
                                    AMQPFlagArgument {
                                        name:         "flag1".to_string(),
                                        default_value: true,
                                    },
                                    AMQPFlagArgument {
                                        name:         "flag2".to_string(),
                                        default_value: false,
                                    },
                                ]),
                            ],
                            has_arguments: true,
                            has_flags:     true,
                            name:          "method1".to_string(),
                            synchronous:   true,
                        }
                    ],
                    name:           "class1".to_string(),
                    properties:     vec![
                        AMQPProperty {
                            amqp_type: AMQPType::LongString,
                            name:      "property1".to_string(),
                        }
                    ],
                    has_properties: true,
                    is_connection:  false,
                }
            ],
        }
    }

    #[test]
    fn main_template() {
        let mut data    = BTreeMap::new();
        let mut codegen = CodeGenerator::new().register_amqp_helpers();
        data.insert("protocol".to_string(), specs());
        codegen.register_template_string("main", TEMPLATE.to_string()).unwrap();
        assert_eq!(codegen.render("main", &data).unwrap(), r#"
AMQP - 0.9.1
Copyright 1
Copyright 2
port 5672
domain1: LongString
constant1 = 128
42 - class1
property1: LongString
64 - method1
synchronous: true
argument1(domain1): LongString

flag1: true
flag2: false

"#);
    }
}
<|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/. */

use canvas_traits::{CanvasMsg, FromLayoutMsg};
use dom::attr::Attr;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
use dom::bindings::codegen::InheritTypes::HTMLCanvasElementDerived;
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::codegen::UnionTypes::CanvasRenderingContext2DOrWebGLRenderingContext;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{HeapGCValue, JS, LayoutJS, MutNullableHeap, Root};
use dom::bindings::utils::{Reflectable};
use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers};
use dom::document::Document;
use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::webglrenderingcontext::{LayoutCanvasWebGLRenderingContextHelpers, WebGLRenderingContext};
use euclid::size::Size2D;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::{HandleValue, JSContext};
use offscreen_gl_context::GLContextAttributes;
use std::cell::Cell;
use std::default::Default;
use util::str::{DOMString, parse_unsigned_integer};

const DEFAULT_WIDTH: u32 = 300;
const DEFAULT_HEIGHT: u32 = 150;

#[must_root]
#[derive(JSTraceable, Clone, Copy, HeapSizeOf)]
pub enum CanvasContext {
    Context2d(JS<CanvasRenderingContext2D>),
    WebGL(JS<WebGLRenderingContext>),
}

impl HeapGCValue for CanvasContext {}

#[dom_struct]
pub struct HTMLCanvasElement {
    htmlelement: HTMLElement,
    context: MutNullableHeap<CanvasContext>,
    width: Cell<u32>,
    height: Cell<u32>,
}

impl PartialEq for HTMLCanvasElement {
    fn eq(&self, other: &HTMLCanvasElement) -> bool {
        self as *const HTMLCanvasElement == &*other
    }
}

impl HTMLCanvasElementDerived for EventTarget {
    fn is_htmlcanvaselement(&self) -> bool {
        *self.type_id() ==
            EventTargetTypeId::Node(
                NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement)))
    }
}

impl HTMLCanvasElement {
    fn new_inherited(localName: DOMString,
                     prefix: Option<DOMString>,
                     document: &Document) -> HTMLCanvasElement {
        HTMLCanvasElement {
            htmlelement:
                HTMLElement::new_inherited(HTMLElementTypeId::HTMLCanvasElement, localName, prefix, document),
            context: Default::default(),
            width: Cell::new(DEFAULT_WIDTH),
            height: Cell::new(DEFAULT_HEIGHT),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(localName: DOMString,
               prefix: Option<DOMString>,
               document: &Document) -> Root<HTMLCanvasElement> {
        let element = HTMLCanvasElement::new_inherited(localName, prefix, document);
        Node::reflect_node(box element, document, HTMLCanvasElementBinding::Wrap)
    }

    fn recreate_contexts(&self) {
        let size = self.get_size();
        if let Some(context) = self.context.get() {
            match context {
                CanvasContext::Context2d(context) => context.root().r().recreate(size),
                CanvasContext::WebGL(context) => context.root().r().recreate(size),
            }
        }
    }

    pub fn get_size(&self) -> Size2D<i32> {
        Size2D::new(self.width.get() as i32, self.height.get() as i32)
    }
}

pub trait LayoutHTMLCanvasElementHelpers {
    #[allow(unsafe_code)]
    unsafe fn get_renderer_id(&self) -> Option<usize>;
    #[allow(unsafe_code)]
    unsafe fn get_ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>>;
    #[allow(unsafe_code)]
    unsafe fn get_canvas_width(&self) -> u32;
    #[allow(unsafe_code)]
    unsafe fn get_canvas_height(&self) -> u32;
}

impl LayoutHTMLCanvasElementHelpers for LayoutJS<HTMLCanvasElement> {
    #[allow(unsafe_code)]
    unsafe fn get_renderer_id(&self) -> Option<usize> {
        let ref canvas = *self.unsafe_get();
        canvas.context.get().map(|context| {
            match context {
                CanvasContext::Context2d(context) => context.to_layout().get_renderer_id(),
                CanvasContext::WebGL(context) => context.to_layout().get_renderer_id(),
            }
        })
    }

    #[allow(unsafe_code)]
    unsafe fn get_ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>> {
        let ref canvas = *self.unsafe_get();
        canvas.context.get().map(|context| {
            match context {
                CanvasContext::Context2d(context) => context.to_layout().get_ipc_renderer(),
                CanvasContext::WebGL(context) => context.to_layout().get_ipc_renderer(),
            }
        })
    }

    #[allow(unsafe_code)]
    unsafe fn get_canvas_width(&self) -> u32 {
        (*self.unsafe_get()).width.get()
    }

    #[allow(unsafe_code)]
    unsafe fn get_canvas_height(&self) -> u32 {
        (*self.unsafe_get()).height.get()
    }
}

impl HTMLCanvasElement {
    pub fn ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>> {
        self.context.get().map(|context| {
            match context {
                CanvasContext::Context2d(context) => context.root().r().ipc_renderer(),
                CanvasContext::WebGL(context) => context.root().r().ipc_renderer(),
            }
        })
    }

    pub fn get_or_init_2d_context(&self) -> Option<Root<CanvasRenderingContext2D>> {
        if self.context.get().is_none() {
            let window = window_from_node(self);
            let size = self.get_size();
            let context = CanvasRenderingContext2D::new(GlobalRef::Window(window.r()), self, size);
            self.context.set(Some(CanvasContext::Context2d(JS::from_rooted(&context))));
        }

        match self.context.get().unwrap() {
            CanvasContext::Context2d(context) => Some(context.root()),
            _   => None,
        }
    }

    pub fn get_or_init_webgl_context(&self,
                                 cx: *mut JSContext,
                                 attrs: Option<HandleValue>) -> Option<Root<WebGLRenderingContext>> {
        if self.context.get().is_none() {
            let window = window_from_node(self);
            let size = self.get_size();

            let attrs = if let Some(webgl_attributes) = attrs {
                if let Ok(ref attrs) = WebGLContextAttributes::new(cx, webgl_attributes) {
                    From::from(attrs)
                } else {
                    debug!("Unexpected error on conversion of WebGLContextAttributes");
                    return None;
                }
            } else {
                GLContextAttributes::default()
            };

            let maybe_ctx = WebGLRenderingContext::new(GlobalRef::Window(window.r()), self, size, attrs);

            self.context.set(maybe_ctx.map( |ctx| CanvasContext::WebGL(JS::from_rooted(&ctx))));
        }

        if let Some(context) = self.context.get() {
            match context {
                CanvasContext::WebGL(context) => Some(context.root()),
                _ => None,
            }
        } else {
            None
        }
    }

    pub fn is_valid(&self) -> bool {
        self.height.get() != 0 && self.width.get() != 0
    }

    pub fn fetch_all_data(&self) -> Option<(Vec<u8>, Size2D<i32>)> {
        let size = self.get_size();

        if size.width == 0 || size.height == 0 {
            return None
        }

        let renderer = match self.ipc_renderer() {
            Some(renderer) => renderer,
            None => return None,
        };

        let (sender, receiver) = ipc::channel().unwrap();
        let msg = CanvasMsg::FromLayout(FromLayoutMsg::SendPixelContents(sender));
        renderer.send(msg).unwrap();

        let data = receiver.recv().unwrap().to_vec();

        Some((data, size))
    }
}

impl HTMLCanvasElementMethods for HTMLCanvasElement {
    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    fn Width(&self) -> u32 {
        self.width.get()
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    fn SetWidth(&self, width: u32) {
        let elem = ElementCast::from_ref(self);
        elem.set_uint_attribute(&atom!("width"), width)
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    fn Height(&self) -> u32 {
        self.height.get()
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    fn SetHeight(&self, height: u32) {
        let elem = ElementCast::from_ref(self);
        elem.set_uint_attribute(&atom!("height"), height)
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-getcontext
    fn GetContext(&self,
                  cx: *mut JSContext,
                  id: DOMString,
                  attributes: Vec<HandleValue>)
        -> Option<CanvasRenderingContext2DOrWebGLRenderingContext> {
        match &*id {
            "2d" => {
                self.get_or_init_2d_context()
                    .map(|ctx| CanvasRenderingContext2DOrWebGLRenderingContext::eCanvasRenderingContext2D(
                                   ctx))
            }
            "webgl" | "experimental-webgl" => {
                self.get_or_init_webgl_context(cx, attributes.get(0).map(|p| *p))
                    .map(|ctx| CanvasRenderingContext2DOrWebGLRenderingContext::eWebGLRenderingContext(
                                   ctx))
            }
            _ => None
        }
    }
}

impl VirtualMethods for HTMLCanvasElement {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let element: &HTMLElement = HTMLElementCast::from_ref(self);
        Some(element as &VirtualMethods)
    }

    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
        self.super_type().unwrap().attribute_mutated(attr, mutation);
        let recreate = match attr.local_name() {
            &atom!(width) => {
                let width = mutation.new_value(attr).and_then(|value| {
                    parse_unsigned_integer(value.chars())
                });
                self.width.set(width.unwrap_or(DEFAULT_WIDTH));
                true
            },
            &atom!(height) => {
                let height = mutation.new_value(attr).and_then(|value| {
                    parse_unsigned_integer(value.chars())
                });
                self.height.set(height.unwrap_or(DEFAULT_HEIGHT));
                true
            },
            _ => false,
        };
        if recreate {
            self.recreate_contexts();
        }
    }
}

impl<'a> From<&'a WebGLContextAttributes> for GLContextAttributes {
    fn from(attrs: &'a WebGLContextAttributes) -> GLContextAttributes {
        GLContextAttributes {
            alpha: attrs.alpha,
            depth: attrs.depth,
            stencil: attrs.stencil,
            antialias: attrs.antialias,
            premultiplied_alpha: attrs.premultipliedAlpha,
            preserve_drawing_buffer: attrs.preserveDrawingBuffer,
        }
    }
}

pub mod utils {
    use dom::window::Window;
    use ipc_channel::ipc;
    use net_traits::image_cache_task::{ImageCacheChan, ImageResponse};
    use url::Url;

    pub fn request_image_from_cache(window: &Window, url: Url) -> ImageResponse {
        let image_cache = window.image_cache_task();
        let (response_chan, response_port) = ipc::channel().unwrap();
        image_cache.request_image(url, ImageCacheChan(response_chan), None);
        let result = response_port.recv().unwrap();
        result.image_response
    }
}
<commit_msg>script: Return a white bitmap if the canvas has a size but no context<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/. */

use canvas_traits::{CanvasMsg, FromLayoutMsg};
use dom::attr::Attr;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
use dom::bindings::codegen::InheritTypes::HTMLCanvasElementDerived;
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
use dom::bindings::codegen::UnionTypes::CanvasRenderingContext2DOrWebGLRenderingContext;
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{HeapGCValue, JS, LayoutJS, MutNullableHeap, Root};
use dom::bindings::utils::{Reflectable};
use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers};
use dom::document::Document;
use dom::element::{AttributeMutation, ElementTypeId};
use dom::eventtarget::{EventTarget, EventTargetTypeId};
use dom::htmlelement::{HTMLElement, HTMLElementTypeId};
use dom::node::{Node, NodeTypeId, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::webglrenderingcontext::{LayoutCanvasWebGLRenderingContextHelpers, WebGLRenderingContext};
use euclid::size::Size2D;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::{HandleValue, JSContext};
use offscreen_gl_context::GLContextAttributes;
use std::cell::Cell;
use std::default::Default;
use std::iter::repeat;
use util::str::{DOMString, parse_unsigned_integer};

const DEFAULT_WIDTH: u32 = 300;
const DEFAULT_HEIGHT: u32 = 150;

#[must_root]
#[derive(JSTraceable, Clone, Copy, HeapSizeOf)]
pub enum CanvasContext {
    Context2d(JS<CanvasRenderingContext2D>),
    WebGL(JS<WebGLRenderingContext>),
}

impl HeapGCValue for CanvasContext {}

#[dom_struct]
pub struct HTMLCanvasElement {
    htmlelement: HTMLElement,
    context: MutNullableHeap<CanvasContext>,
    width: Cell<u32>,
    height: Cell<u32>,
}

impl PartialEq for HTMLCanvasElement {
    fn eq(&self, other: &HTMLCanvasElement) -> bool {
        self as *const HTMLCanvasElement == &*other
    }
}

impl HTMLCanvasElementDerived for EventTarget {
    fn is_htmlcanvaselement(&self) -> bool {
        *self.type_id() ==
            EventTargetTypeId::Node(
                NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement)))
    }
}

impl HTMLCanvasElement {
    fn new_inherited(localName: DOMString,
                     prefix: Option<DOMString>,
                     document: &Document) -> HTMLCanvasElement {
        HTMLCanvasElement {
            htmlelement:
                HTMLElement::new_inherited(HTMLElementTypeId::HTMLCanvasElement, localName, prefix, document),
            context: Default::default(),
            width: Cell::new(DEFAULT_WIDTH),
            height: Cell::new(DEFAULT_HEIGHT),
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(localName: DOMString,
               prefix: Option<DOMString>,
               document: &Document) -> Root<HTMLCanvasElement> {
        let element = HTMLCanvasElement::new_inherited(localName, prefix, document);
        Node::reflect_node(box element, document, HTMLCanvasElementBinding::Wrap)
    }

    fn recreate_contexts(&self) {
        let size = self.get_size();
        if let Some(context) = self.context.get() {
            match context {
                CanvasContext::Context2d(context) => context.root().r().recreate(size),
                CanvasContext::WebGL(context) => context.root().r().recreate(size),
            }
        }
    }

    pub fn get_size(&self) -> Size2D<i32> {
        Size2D::new(self.width.get() as i32, self.height.get() as i32)
    }
}

pub trait LayoutHTMLCanvasElementHelpers {
    #[allow(unsafe_code)]
    unsafe fn get_renderer_id(&self) -> Option<usize>;
    #[allow(unsafe_code)]
    unsafe fn get_ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>>;
    #[allow(unsafe_code)]
    unsafe fn get_canvas_width(&self) -> u32;
    #[allow(unsafe_code)]
    unsafe fn get_canvas_height(&self) -> u32;
}

impl LayoutHTMLCanvasElementHelpers for LayoutJS<HTMLCanvasElement> {
    #[allow(unsafe_code)]
    unsafe fn get_renderer_id(&self) -> Option<usize> {
        let ref canvas = *self.unsafe_get();
        canvas.context.get().map(|context| {
            match context {
                CanvasContext::Context2d(context) => context.to_layout().get_renderer_id(),
                CanvasContext::WebGL(context) => context.to_layout().get_renderer_id(),
            }
        })
    }

    #[allow(unsafe_code)]
    unsafe fn get_ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>> {
        let ref canvas = *self.unsafe_get();
        canvas.context.get().map(|context| {
            match context {
                CanvasContext::Context2d(context) => context.to_layout().get_ipc_renderer(),
                CanvasContext::WebGL(context) => context.to_layout().get_ipc_renderer(),
            }
        })
    }

    #[allow(unsafe_code)]
    unsafe fn get_canvas_width(&self) -> u32 {
        (*self.unsafe_get()).width.get()
    }

    #[allow(unsafe_code)]
    unsafe fn get_canvas_height(&self) -> u32 {
        (*self.unsafe_get()).height.get()
    }
}

impl HTMLCanvasElement {
    pub fn ipc_renderer(&self) -> Option<IpcSender<CanvasMsg>> {
        self.context.get().map(|context| {
            match context {
                CanvasContext::Context2d(context) => context.root().r().ipc_renderer(),
                CanvasContext::WebGL(context) => context.root().r().ipc_renderer(),
            }
        })
    }

    pub fn get_or_init_2d_context(&self) -> Option<Root<CanvasRenderingContext2D>> {
        if self.context.get().is_none() {
            let window = window_from_node(self);
            let size = self.get_size();
            let context = CanvasRenderingContext2D::new(GlobalRef::Window(window.r()), self, size);
            self.context.set(Some(CanvasContext::Context2d(JS::from_rooted(&context))));
        }

        match self.context.get().unwrap() {
            CanvasContext::Context2d(context) => Some(context.root()),
            _   => None,
        }
    }

    pub fn get_or_init_webgl_context(&self,
                                 cx: *mut JSContext,
                                 attrs: Option<HandleValue>) -> Option<Root<WebGLRenderingContext>> {
        if self.context.get().is_none() {
            let window = window_from_node(self);
            let size = self.get_size();

            let attrs = if let Some(webgl_attributes) = attrs {
                if let Ok(ref attrs) = WebGLContextAttributes::new(cx, webgl_attributes) {
                    From::from(attrs)
                } else {
                    debug!("Unexpected error on conversion of WebGLContextAttributes");
                    return None;
                }
            } else {
                GLContextAttributes::default()
            };

            let maybe_ctx = WebGLRenderingContext::new(GlobalRef::Window(window.r()), self, size, attrs);

            self.context.set(maybe_ctx.map( |ctx| CanvasContext::WebGL(JS::from_rooted(&ctx))));
        }

        if let Some(context) = self.context.get() {
            match context {
                CanvasContext::WebGL(context) => Some(context.root()),
                _ => None,
            }
        } else {
            None
        }
    }

    pub fn is_valid(&self) -> bool {
        self.height.get() != 0 && self.width.get() != 0
    }

    pub fn fetch_all_data(&self) -> Option<(Vec<u8>, Size2D<i32>)> {
        let size = self.get_size();

        if size.width == 0 || size.height == 0 {
            return None
        }

        let data = if let Some(renderer) = self.ipc_renderer() {
            let (sender, receiver) = ipc::channel().unwrap();
            let msg = CanvasMsg::FromLayout(FromLayoutMsg::SendPixelContents(sender));
            renderer.send(msg).unwrap();

            receiver.recv().unwrap().to_vec()
        } else {
            repeat(0xffu8).take((size.height as usize) * (size.width as usize) * 4).collect()
        };

        Some((data, size))
    }
}

impl HTMLCanvasElementMethods for HTMLCanvasElement {
    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    fn Width(&self) -> u32 {
        self.width.get()
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    fn SetWidth(&self, width: u32) {
        let elem = ElementCast::from_ref(self);
        elem.set_uint_attribute(&atom!("width"), width)
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    fn Height(&self) -> u32 {
        self.height.get()
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    fn SetHeight(&self, height: u32) {
        let elem = ElementCast::from_ref(self);
        elem.set_uint_attribute(&atom!("height"), height)
    }

    // https://html.spec.whatwg.org/multipage/#dom-canvas-getcontext
    fn GetContext(&self,
                  cx: *mut JSContext,
                  id: DOMString,
                  attributes: Vec<HandleValue>)
        -> Option<CanvasRenderingContext2DOrWebGLRenderingContext> {
        match &*id {
            "2d" => {
                self.get_or_init_2d_context()
                    .map(|ctx| CanvasRenderingContext2DOrWebGLRenderingContext::eCanvasRenderingContext2D(
                                   ctx))
            }
            "webgl" | "experimental-webgl" => {
                self.get_or_init_webgl_context(cx, attributes.get(0).map(|p| *p))
                    .map(|ctx| CanvasRenderingContext2DOrWebGLRenderingContext::eWebGLRenderingContext(
                                   ctx))
            }
            _ => None
        }
    }
}

impl VirtualMethods for HTMLCanvasElement {
    fn super_type<'b>(&'b self) -> Option<&'b VirtualMethods> {
        let element: &HTMLElement = HTMLElementCast::from_ref(self);
        Some(element as &VirtualMethods)
    }

    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
        self.super_type().unwrap().attribute_mutated(attr, mutation);
        let recreate = match attr.local_name() {
            &atom!(width) => {
                let width = mutation.new_value(attr).and_then(|value| {
                    parse_unsigned_integer(value.chars())
                });
                self.width.set(width.unwrap_or(DEFAULT_WIDTH));
                true
            },
            &atom!(height) => {
                let height = mutation.new_value(attr).and_then(|value| {
                    parse_unsigned_integer(value.chars())
                });
                self.height.set(height.unwrap_or(DEFAULT_HEIGHT));
                true
            },
            _ => false,
        };
        if recreate {
            self.recreate_contexts();
        }
    }
}

impl<'a> From<&'a WebGLContextAttributes> for GLContextAttributes {
    fn from(attrs: &'a WebGLContextAttributes) -> GLContextAttributes {
        GLContextAttributes {
            alpha: attrs.alpha,
            depth: attrs.depth,
            stencil: attrs.stencil,
            antialias: attrs.antialias,
            premultiplied_alpha: attrs.premultipliedAlpha,
            preserve_drawing_buffer: attrs.preserveDrawingBuffer,
        }
    }
}

pub mod utils {
    use dom::window::Window;
    use ipc_channel::ipc;
    use net_traits::image_cache_task::{ImageCacheChan, ImageResponse};
    use url::Url;

    pub fn request_image_from_cache(window: &Window, url: Url) -> ImageResponse {
        let image_cache = window.image_cache_task();
        let (response_chan, response_port) = ipc::channel().unwrap();
        image_cache.request_image(url, ImageCacheChan(response_chan), None);
        let result = response_port.recv().unwrap();
        result.image_response
    }
}
<|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/. */

use dom::attr::Attr;
use dom::bindings::codegen::Bindings::NamedNodeMapBinding;
use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, Root};
use dom::bindings::utils::{Reflector, namespace_from_domstring, reflect_dom_object};
use dom::element::Element;
use dom::window::Window;
use string_cache::Atom;
use util::str::DOMString;

#[dom_struct]
pub struct NamedNodeMap {
    reflector_: Reflector,
    owner: JS<Element>,
}

impl NamedNodeMap {
    fn new_inherited(elem: &Element) -> NamedNodeMap {
        NamedNodeMap {
            reflector_: Reflector::new(),
            owner: JS::from_ref(elem),
        }
    }

    pub fn new(window: &Window, elem: &Element) -> Root<NamedNodeMap> {
        reflect_dom_object(box NamedNodeMap::new_inherited(elem),
                           GlobalRef::Window(window), NamedNodeMapBinding::Wrap)
    }
}

impl NamedNodeMapMethods for NamedNodeMap {
    // https://dom.spec.whatwg.org/#dom-namednodemap-length
    fn Length(&self) -> u32 {
        let owner = self.owner.root();
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let owner = owner.r();
        let attrs = owner.attrs();
        attrs.len() as u32
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-item
    fn Item(&self, index: u32) -> Option<Root<Attr>> {
        let owner = self.owner.root();
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let owner = owner.r();
        let attrs = owner.attrs();
        attrs.get(index as usize).map(|t| t.root())
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem
    fn GetNamedItem(&self, name: DOMString) -> Option<Root<Attr>> {
        let owner = self.owner.root();
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let owner = owner.r();
        owner.get_attribute_by_name(name)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditemns
    fn GetNamedItemNS(&self, namespace: Option<DOMString>, local_name: DOMString)
                     -> Option<Root<Attr>> {
        let owner = self.owner.root();
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let owner = owner.r();
        let ns = namespace_from_domstring(namespace);
        owner.get_attribute(&ns, &Atom::from_slice(&local_name))
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
    fn RemoveNamedItem(&self, name: DOMString) -> Fallible<Root<Attr>> {
        let owner = self.owner.root();
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let owner = owner.r();
        let name = owner.parsed_name(name);
        owner.remove_attribute_by_name(&name).ok_or(Error::NotFound)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns
    fn RemoveNamedItemNS(&self, namespace: Option<DOMString>, local_name: DOMString)
                      -> Fallible<Root<Attr>> {
        let owner = self.owner.root();
        // FIXME(https://github.com/rust-lang/rust/issues/23338)
        let owner = owner.r();
        let ns = namespace_from_domstring(namespace);
        owner.remove_attribute(&ns, &Atom::from_slice(&local_name)).ok_or(Error::NotFound)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-item
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Attr>> {
        let item = self.Item(index);
        *found = item.is_some();
        item
    }

    // check-tidy: no specs after this line
    fn NamedGetter(&self, name: DOMString, found: &mut bool) -> Option<Root<Attr>> {
        let item = self.GetNamedItem(name);
        *found = item.is_some();
        item
    }

    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
        // FIXME: unimplemented (https://github.com/servo/servo/issues/7273)
        vec![]
    }
}
<commit_msg>Do not root NamedNodeMap::owner<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/. */

use dom::attr::Attr;
use dom::bindings::codegen::Bindings::NamedNodeMapBinding;
use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::js::{JS, Root};
use dom::bindings::utils::{Reflector, namespace_from_domstring, reflect_dom_object};
use dom::element::Element;
use dom::window::Window;
use string_cache::Atom;
use util::str::DOMString;

#[dom_struct]
pub struct NamedNodeMap {
    reflector_: Reflector,
    owner: JS<Element>,
}

impl NamedNodeMap {
    fn new_inherited(elem: &Element) -> NamedNodeMap {
        NamedNodeMap {
            reflector_: Reflector::new(),
            owner: JS::from_ref(elem),
        }
    }

    pub fn new(window: &Window, elem: &Element) -> Root<NamedNodeMap> {
        reflect_dom_object(box NamedNodeMap::new_inherited(elem),
                           GlobalRef::Window(window), NamedNodeMapBinding::Wrap)
    }
}

impl NamedNodeMapMethods for NamedNodeMap {
    // https://dom.spec.whatwg.org/#dom-namednodemap-length
    fn Length(&self) -> u32 {
        self.owner.attrs().len() as u32
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-item
    fn Item(&self, index: u32) -> Option<Root<Attr>> {
        self.owner.attrs().get(index as usize).map(JS::root)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditem
    fn GetNamedItem(&self, name: DOMString) -> Option<Root<Attr>> {
        self.owner.get_attribute_by_name(name)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-getnameditemns
    fn GetNamedItemNS(&self, namespace: Option<DOMString>, local_name: DOMString)
                     -> Option<Root<Attr>> {
        let ns = namespace_from_domstring(namespace);
        self.owner.get_attribute(&ns, &Atom::from_slice(&local_name))
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
    fn RemoveNamedItem(&self, name: DOMString) -> Fallible<Root<Attr>> {
        let name = self.owner.parsed_name(name);
        self.owner.remove_attribute_by_name(&name).ok_or(Error::NotFound)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns
    fn RemoveNamedItemNS(&self, namespace: Option<DOMString>, local_name: DOMString)
                      -> Fallible<Root<Attr>> {
        let ns = namespace_from_domstring(namespace);
        self.owner.remove_attribute(&ns, &Atom::from_slice(&local_name))
            .ok_or(Error::NotFound)
    }

    // https://dom.spec.whatwg.org/#dom-namednodemap-item
    fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Root<Attr>> {
        let item = self.Item(index);
        *found = item.is_some();
        item
    }

    // check-tidy: no specs after this line
    fn NamedGetter(&self, name: DOMString, found: &mut bool) -> Option<Root<Attr>> {
        let item = self.GetNamedItem(name);
        *found = item.is_some();
        item
    }

    fn SupportedPropertyNames(&self) -> Vec<DOMString> {
        // FIXME: unimplemented (https://github.com/servo/servo/issues/7273)
        vec![]
    }
}
<|endoftext|>


<commit_before>// This is a test which attempts to blow out the system limit with how many
// arguments can be passed to a process. This'll successively call rustc with
// larger and larger argument lists in an attempt to find one that's way too
// big for the system at hand. This file itself is then used as a "linker" to
// detect when the process creation succeeds.
//
// Eventually we should see an argument that looks like `@` as we switch from
// passing literal arguments to passing everything in the file.

use std::env;
use std::fs::{self, File};
use std::io::{BufWriter, Write, Read};
use std::path::PathBuf;
use std::process::Command;

fn main() {
    let tmpdir = PathBuf::from(env::var_os("TMPDIR").unwrap());
    let ok = tmpdir.join("ok");
    if env::var("YOU_ARE_A_LINKER").is_ok() {
        if let Some(file) = env::args_os().find(|a| a.to_string_lossy().contains("@")) {
            let file = file.to_str().expect("non-utf8 file argument");
            fs::copy(&file[1..], &ok).unwrap();
        }
        return
    }

    let rustc = env::var_os("RUSTC").unwrap_or("rustc".into());
    let me_as_linker = format!("linker={}", env::current_exe().unwrap().display());
    for i in (1..).map(|i| i * 100) {
        println!("attempt: {}", i);
        let file = tmpdir.join("bar.rs");
        let mut f = BufWriter::new(File::create(&file).unwrap());
        let mut lib_name = String::new();
        for _ in 0..i {
            lib_name.push_str("foo");
        }
        for j in 0..i {
            writeln!(f, "#[link(name = \"{}{}\")]", lib_name, j).unwrap();
        }
        writeln!(f, "extern {{}}\nfn main() {{}}").unwrap();
        f.into_inner().unwrap();

        drop(fs::remove_file(&ok));
        let output = Command::new(&rustc)
            .arg(&file)
            .arg("-C").arg(&me_as_linker)
            .arg("--out-dir").arg(&tmpdir)
            .env("YOU_ARE_A_LINKER", "1")
            .output()
            .unwrap();

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            panic!("status: {}\nstdout:\n{}\nstderr:\n{}",
                   output.status,
                   String::from_utf8_lossy(&output.stdout),
                   stderr.lines().map(|l| {
                       if l.len() > 200 {
                           format!("{}...\n", &l[..200])
                       } else {
                           format!("{}\n", l)
                       }
                   }).collect::<String>());
        }

        if !ok.exists() {
            continue
        }

        let mut contents = Vec::new();
        File::open(&ok).unwrap().read_to_end(&mut contents).unwrap();

        for j in 0..i {
            let exp = format!("{}{}", lib_name, j);
            let exp = if cfg!(target_env = "msvc") {
                let mut out = Vec::with_capacity(exp.len() * 2);
                for c in exp.encode_utf16() {
                    // encode in little endian
                    out.push(c as u8);
                    out.push((c >> 8) as u8);
                }
                out
            } else {
                exp.into_bytes()
            };
            assert!(contents.windows(exp.len()).any(|w| w == &exp[..]));
        }

        break
    }
}
<commit_msg>Rollup merge of #65945 - tmiasko:long-linker-command-line, r=alexcrichton<commit_after>// This is a test which attempts to blow out the system limit with how many
// arguments can be passed to a process. This'll successively call rustc with
// larger and larger argument lists in an attempt to find one that's way too
// big for the system at hand. This file itself is then used as a "linker" to
// detect when the process creation succeeds.
//
// Eventually we should see an argument that looks like `@` as we switch from
// passing literal arguments to passing everything in the file.

use std::collections::HashSet;
use std::env;
use std::fs::{self, File};
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::Command;

fn write_test_case(file: &Path, n: usize) -> HashSet<String> {
    let mut libs = HashSet::new();
    let mut f = BufWriter::new(File::create(&file).unwrap());
    let mut prefix = String::new();
    for _ in 0..n {
        prefix.push_str("foo");
    }
    for i in 0..n {
        writeln!(f, "#[link(name = \"S{}{}S\")]", prefix, i).unwrap();
        libs.insert(format!("{}{}", prefix, i));
    }
    writeln!(f, "extern {{}}\nfn main() {{}}").unwrap();
    f.into_inner().unwrap();

    libs
}

fn read_linker_args(path: &Path) -> String {
    let contents = fs::read(path).unwrap();
    if cfg!(target_env = "msvc") {
        let mut i = contents.chunks(2).map(|c| {
            c[0] as u16 | ((c[1] as u16) << 8)
        });
        assert_eq!(i.next(), Some(0xfeff), "Expected UTF-16 BOM");
        String::from_utf16(&i.collect::<Vec<u16>>()).unwrap()
    } else {
        String::from_utf8(contents).unwrap()
    }
}

fn main() {
    let tmpdir = PathBuf::from(env::var_os("TMPDIR").unwrap());
    let ok = tmpdir.join("ok");
    if env::var("YOU_ARE_A_LINKER").is_ok() {
        if let Some(file) = env::args_os().find(|a| a.to_string_lossy().contains("@")) {
            let file = file.to_str().expect("non-utf8 file argument");
            fs::copy(&file[1..], &ok).unwrap();
        }
        return
    }

    let rustc = env::var_os("RUSTC").unwrap_or("rustc".into());
    let me_as_linker = format!("linker={}", env::current_exe().unwrap().display());
    for i in (1..).map(|i| i * 100) {
        println!("attempt: {}", i);
        let file = tmpdir.join("bar.rs");
        let mut expected_libs = write_test_case(&file, i);

        drop(fs::remove_file(&ok));
        let output = Command::new(&rustc)
            .arg(&file)
            .arg("-C").arg(&me_as_linker)
            .arg("--out-dir").arg(&tmpdir)
            .env("YOU_ARE_A_LINKER", "1")
            .output()
            .unwrap();

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            panic!("status: {}\nstdout:\n{}\nstderr:\n{}",
                   output.status,
                   String::from_utf8_lossy(&output.stdout),
                   stderr.lines().map(|l| {
                       if l.len() > 200 {
                           format!("{}...\n", &l[..200])
                       } else {
                           format!("{}\n", l)
                       }
                   }).collect::<String>());
        }

        if !ok.exists() {
            continue
        }

        let linker_args = read_linker_args(&ok);
        for mut arg in linker_args.split('S') {
            expected_libs.remove(arg);
        }

        assert!(
            expected_libs.is_empty(),
            "expected but missing libraries: {:#?}\nlinker arguments: \n{}",
            expected_libs,
            linker_args,
        );

        break
    }
}
<|endoftext|>