<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::ast::{Ast, AstBuilder, AstKey, ObjectEntry, Primitive, QueryID, RequestParameters};
use crate::build_ast::{
    build_fragment, build_operation, build_request, build_request_params,
    build_request_params_ast_key,
};
use crate::constants::CODEGEN_CONSTANTS;
use crate::indentation::print_indentation;
use crate::js_module_format::JsModuleFormat;
use crate::utils::escape;

use graphql_ir::{FragmentDefinition, OperationDefinition};
use schema::SDLSchema;

use fnv::{FnvBuildHasher, FnvHashSet};
use indexmap::IndexMap;
use intern::string_key::StringKey;
use std::fmt::{Result as FmtResult, Write};

pub fn print_operation(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    js_module_format: JsModuleFormat,
) -> String {
    Printer::without_dedupe(js_module_format).print_operation(schema, operation)
}

pub fn print_fragment(
    schema: &SDLSchema,
    fragment: &FragmentDefinition,
    js_module_format: JsModuleFormat,
) -> String {
    Printer::without_dedupe(js_module_format).print_fragment(schema, fragment)
}

pub fn print_request(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    fragment: &FragmentDefinition,
    request_parameters: RequestParameters<'_>,
    js_module_format: JsModuleFormat,
) -> String {
    Printer::without_dedupe(js_module_format).print_request(
        schema,
        operation,
        fragment,
        request_parameters,
    )
}

pub fn print_request_params(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    query_id: &Option<QueryID>,
    js_module_format: JsModuleFormat,
) -> String {
    let mut request_parameters = build_request_params(operation);
    request_parameters.id = query_id;

    let mut builder = AstBuilder::default();
    let request_parameters_ast_key =
        build_request_params_ast_key(schema, request_parameters, &mut builder, operation);
    let printer = JSONPrinter::new(&builder, js_module_format);
    printer.print(request_parameters_ast_key, false)
}

pub struct Printer {
    js_module_format: JsModuleFormat,
    builder: AstBuilder,
    dedupe: bool,
}

impl Printer {
    pub fn with_dedupe(js_module_format: JsModuleFormat) -> Self {
        Self {
            js_module_format,
            builder: Default::default(),
            dedupe: true,
        }
    }

    pub fn without_dedupe(js_module_format: JsModuleFormat) -> Self {
        Self {
            js_module_format,
            builder: Default::default(),
            dedupe: false,
        }
    }

    pub fn print_request(
        &mut self,
        schema: &SDLSchema,
        operation: &OperationDefinition,
        fragment: &FragmentDefinition,
        request_parameters: RequestParameters<'_>,
    ) -> String {
        let request_parameters =
            build_request_params_ast_key(schema, request_parameters, &mut self.builder, operation);
        let key = build_request(
            schema,
            &mut self.builder,
            operation,
            fragment,
            request_parameters,
        );
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }

    pub fn print_operation(
        &mut self,
        schema: &SDLSchema,
        operation: &OperationDefinition,
    ) -> String {
        let key = build_operation(schema, &mut self.builder, operation);
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }

    pub fn print_fragment(&mut self, schema: &SDLSchema, fragment: &FragmentDefinition) -> String {
        let key = build_fragment(schema, &mut self.builder, fragment);
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }
}

type VariableDefinitions = IndexMap<AstKey, String, FnvBuildHasher>;
struct JSONPrinter<'b> {
    variable_definitions: VariableDefinitions,
    duplicates: FnvHashSet<AstKey>,
    builder: &'b AstBuilder,
    js_module_format: JsModuleFormat,
}

impl<'b> JSONPrinter<'b> {
    fn new(builder: &'b AstBuilder, js_module_format: JsModuleFormat) -> Self {
        Self {
            variable_definitions: Default::default(),
            duplicates: Default::default(),
            builder,
            js_module_format,
        }
    }

    fn print(mut self, root_key: AstKey, dedupe: bool) -> String {
        if dedupe {
            let mut visited = Default::default();
            self.collect_value_duplicates(&mut visited, root_key);
        }
        let mut result = String::new();
        self.print_ast(&mut result, root_key, 0, false);
        if self.variable_definitions.is_empty() {
            result
        } else {
            let mut with_variables = String::new();
            with_variables.push_str("(function(){\nvar ");
            let last = self.variable_definitions.len() - 1;
            for (i, (_, value)) in self.variable_definitions.drain(..).enumerate() {
                writeln!(
                    &mut with_variables,
                    "v{} = {}{}",
                    i,
                    value,
                    if i == last { ";" } else { "," }
                )
                .unwrap();
            }
            write!(&mut with_variables, "return {};\n}})()", result).unwrap();
            with_variables
        }
    }

    /// We don't dedupe in an already deduped AST unless the duplicate
    /// also appears on a subtree that's not a duplicate
    /// Input:
    /// [
    ///     [{a: 1}, {b:2}],
    ///     [{a: 1}, {b:2}],
    ///     {b: 2}
    /// ]
    /// Output:
    /// v0 = {b: 2};
    /// v1 = [{a: 1}, v0];
    fn collect_value_duplicates(&mut self, visited: &mut FnvHashSet<AstKey>, key: AstKey) {
        match self.builder.lookup(key) {
            Ast::Array(array) => {
                if array.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    self.duplicates.insert(key);
                    return;
                }
                for val in array {
                    if let Primitive::Key(key) = val {
                        self.collect_value_duplicates(visited, *key);
                    }
                }
            }
            Ast::Object(object) => {
                if object.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    self.duplicates.insert(key);
                    return;
                }
                for entry in object {
                    if let Primitive::Key(key) = entry.value {
                        self.collect_value_duplicates(visited, key);
                    }
                }
            }
        }
    }

    fn print_ast(&mut self, f: &mut String, key: AstKey, indent: usize, is_dedupe_var: bool) {
        // Only use variable references at depth beyond the top level.
        if indent > 0 && self.duplicates.contains(&key) {
            let v = if self.variable_definitions.contains_key(&key) {
                self.variable_definitions.get_full(&key).unwrap().0
            } else {
                let mut variable = String::new();
                self.print_ast(&mut variable, key, 0, true);
                let v = self.variable_definitions.len();
                self.variable_definitions.insert(key, variable);
                v
            };
            return write!(f, "(v{}/*: any*/)", v).unwrap();
        }

        let ast = self.builder.lookup(key);
        match ast {
            Ast::Object(object) => {
                if object.is_empty() {
                    f.push_str("{}");
                } else {
                    let next_indent = indent + 1;
                    f.push('{');
                    for ObjectEntry { key, value } in object {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        write!(f, "\"{}\": ", key).unwrap();
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push('}');
                }
            }
            Ast::Array(array) => {
                if array.is_empty() {
                    if is_dedupe_var {
                        // Empty arrays can only have one inferred flow type and then conflict if
                        // used in different places, this is unsound if we would write to them but
                        // this whole module is based on the idea of a read only JSON tree.
                        f.push_str("([]/*: any*/)");
                    } else {
                        f.push_str("[]");
                    }
                } else {
                    f.push('[');
                    let next_indent = indent + 1;
                    for value in array {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push(']');
                }
            }
        }
    }

    fn print_primitive(
        &mut self,
        f: &mut String,
        primitive: &Primitive,
        indent: usize,
        is_dedupe_var: bool,
    ) -> FmtResult {
        match primitive {
            Primitive::Null => write!(f, "null"),
            Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
            Primitive::RawString(str) => {
                f.push('\"');
                escape(str, f);
                f.push('\"');
                Ok(())
            }
            Primitive::String(key) => write!(f, "\"{}\"", key),
            Primitive::Float(value) => write!(f, "{}", value.as_float()),
            Primitive::Int(value) => write!(f, "{}", value),
            Primitive::Key(key) => {
                self.print_ast(f, *key, indent, is_dedupe_var);
                Ok(())
            }
            Primitive::StorageKey(field_name, key) => {
                write_static_storage_key(f, self.builder, *field_name, *key)
            }
            Primitive::GraphQLModuleDependency(key) => match self.js_module_format {
                JsModuleFormat::CommonJS => write!(f, "require('./{}.graphql')", key),
                JsModuleFormat::Haste => write!(f, "require('{}.graphql')", key),
            },
            Primitive::JSModuleDependency(key) => match self.js_module_format {
                JsModuleFormat::CommonJS => write!(f, "require('./{}')", key),
                JsModuleFormat::Haste => write!(f, "require('{}')", key),
            },
        }
    }
}

fn write_static_storage_key(
    f: &mut String,
    builder: &AstBuilder,
    field_name: StringKey,
    args_key: AstKey,
) -> FmtResult {
    write!(f, "\"{}(", field_name)?;
    let args = builder.lookup(args_key).assert_array();
    for arg_key in args {
        let arg = builder.lookup(arg_key.assert_key()).assert_object();
        let name = &arg
            .iter()
            .find(|ObjectEntry { key, value: _ }| *key == CODEGEN_CONSTANTS.name)
            .expect("Expected `name` to exist")
            .value;
        let name = name.assert_string();
        write!(f, "{}:", name)?;
        write_argument_value(f, builder, arg)?;
        f.push(',');
    }
    f.pop(); // args won't be empty
    f.push_str(")\"");
    Ok(())
}

fn write_argument_value(f: &mut String, builder: &AstBuilder, arg: &[ObjectEntry]) -> FmtResult {
    let key = &arg
        .iter()
        .find(|entry| entry.key == CODEGEN_CONSTANTS.kind)
        .expect("Expected `kind` to exist")
        .value;
    let key = key.assert_string();
    // match doesn't allow `CODEGEN_CONSTANTS.<>` on the match arm, falling back to if statements
    if key == CODEGEN_CONSTANTS.literal {
        let literal = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.value)
            .expect("Expected `name` to exist")
            .value;
        write_constant_value(f, builder, literal)?;
    } else if key == CODEGEN_CONSTANTS.list_value {
        let items = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.items)
            .expect("Expected `items` to exist")
            .value;
        let array = builder.lookup(items.assert_key()).assert_array();

        f.push('[');
        let mut after_first = false;
        for key_or_null in array {
            match key_or_null {
                Primitive::Null => {}
                Primitive::Key(key) => {
                    if after_first {
                        f.push(',');
                    } else {
                        after_first = true;
                    }
                    let object = builder.lookup(*key).assert_object();
                    write_argument_value(f, builder, object)?;
                }
                _ => panic!("Expected an object key or null"),
            }
        }
        f.push(']');
    } else {
        // We filtered out Variables, here it should only be ObjectValue
        let fields = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.fields)
            .expect("Expected `fields` to exist")
            .value;
        let fields = builder.lookup(fields.assert_key()).assert_array();

        f.push('{');
        for field in fields {
            let field = builder.lookup(field.assert_key()).assert_object();
            let name = &field
                .iter()
                .find(|entry| entry.key == CODEGEN_CONSTANTS.name)
                .expect("Expected `name` to exist")
                .value;
            let name = name.assert_string();
            write!(f, "\\\"{}\\\":", name)?;
            write_argument_value(f, builder, field)?;
            f.push(',');
        }
        if !fields.is_empty() {
            f.pop();
        }
        f.push('}');
    }
    Ok(())
}

fn write_constant_value(f: &mut String, builder: &AstBuilder, value: &Primitive) -> FmtResult {
    match value {
        Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
        Primitive::String(key) => write!(f, "\\\"{}\\\"", key),
        Primitive::Float(value) => write!(f, "{}", value.as_float()),
        Primitive::Int(value) => write!(f, "{}", value),
        Primitive::Key(key) => {
            let ast = builder.lookup(*key);
            match ast {
                Ast::Array(arr) => {
                    f.push('[');
                    for value in arr {
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !arr.is_empty() {
                        f.pop();
                    }
                    f.push(']');
                    Ok(())
                }
                Ast::Object(obj) => {
                    f.push('{');
                    for ObjectEntry { key: name, value } in obj {
                        write!(f, "\\\"{}\\\":", name)?;
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !obj.is_empty() {
                        f.pop();
                    }
                    f.push('}');
                    Ok(())
                }
            }
        }
        Primitive::Null => {
            f.push_str("null");
            Ok(())
        }
        Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
        Primitive::RawString(_) => panic!("Unexpected RawString"),
        Primitive::GraphQLModuleDependency(_) => panic!("Unexpected GraphQLModuleDependency"),
        Primitive::JSModuleDependency(_) => panic!("Unexpected JSModuleDependency"),
    }
}
<commit_msg>Generate basic JS structure for normalization artifact<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use crate::ast::{Ast, AstBuilder, AstKey, ObjectEntry, Primitive, QueryID, RequestParameters};
use crate::build_ast::{
    build_fragment, build_operation, build_request, build_request_params,
    build_request_params_ast_key,
};
use crate::constants::CODEGEN_CONSTANTS;
use crate::indentation::print_indentation;
use crate::js_module_format::JsModuleFormat;
use crate::utils::escape;

use graphql_ir::{FragmentDefinition, OperationDefinition};
use schema::SDLSchema;

use fnv::{FnvBuildHasher, FnvHashSet};
use indexmap::IndexMap;
use intern::string_key::StringKey;
use std::fmt::{Result as FmtResult, Write};

pub fn print_operation(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    js_module_format: JsModuleFormat,
) -> String {
    Printer::without_dedupe(js_module_format).print_operation(schema, operation)
}

pub fn print_fragment(
    schema: &SDLSchema,
    fragment: &FragmentDefinition,
    js_module_format: JsModuleFormat,
) -> String {
    Printer::without_dedupe(js_module_format).print_fragment(schema, fragment)
}

pub fn print_request(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    fragment: &FragmentDefinition,
    request_parameters: RequestParameters<'_>,
    js_module_format: JsModuleFormat,
) -> String {
    Printer::without_dedupe(js_module_format).print_request(
        schema,
        operation,
        fragment,
        request_parameters,
    )
}

pub fn print_request_params(
    schema: &SDLSchema,
    operation: &OperationDefinition,
    query_id: &Option<QueryID>,
    js_module_format: JsModuleFormat,
) -> String {
    let mut request_parameters = build_request_params(operation);
    request_parameters.id = query_id;

    let mut builder = AstBuilder::default();
    let request_parameters_ast_key =
        build_request_params_ast_key(schema, request_parameters, &mut builder, operation);
    let printer = JSONPrinter::new(&builder, js_module_format);
    printer.print(request_parameters_ast_key, false)
}

pub struct Printer {
    js_module_format: JsModuleFormat,
    builder: AstBuilder,
    dedupe: bool,
}

impl Printer {
    pub fn with_dedupe(js_module_format: JsModuleFormat) -> Self {
        Self {
            js_module_format,
            builder: Default::default(),
            dedupe: true,
        }
    }

    pub fn without_dedupe(js_module_format: JsModuleFormat) -> Self {
        Self {
            js_module_format,
            builder: Default::default(),
            dedupe: false,
        }
    }

    pub fn print_request(
        &mut self,
        schema: &SDLSchema,
        operation: &OperationDefinition,
        fragment: &FragmentDefinition,
        request_parameters: RequestParameters<'_>,
    ) -> String {
        let request_parameters =
            build_request_params_ast_key(schema, request_parameters, &mut self.builder, operation);
        let key = build_request(
            schema,
            &mut self.builder,
            operation,
            fragment,
            request_parameters,
        );
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }

    pub fn print_operation(
        &mut self,
        schema: &SDLSchema,
        operation: &OperationDefinition,
    ) -> String {
        let key = build_operation(schema, &mut self.builder, operation);
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }

    pub fn print_fragment(&mut self, schema: &SDLSchema, fragment: &FragmentDefinition) -> String {
        let key = build_fragment(schema, &mut self.builder, fragment);
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }

    pub fn print_request_params(
        &mut self,
        schema: &SDLSchema,
        request_parameters: RequestParameters<'_>,
        operation: &OperationDefinition,
    ) -> String {
        let key =
            build_request_params_ast_key(schema, request_parameters, &mut self.builder, operation);
        let printer = JSONPrinter::new(&self.builder, self.js_module_format);
        printer.print(key, self.dedupe)
    }
}

type VariableDefinitions = IndexMap<AstKey, String, FnvBuildHasher>;
struct JSONPrinter<'b> {
    variable_definitions: VariableDefinitions,
    duplicates: FnvHashSet<AstKey>,
    builder: &'b AstBuilder,
    js_module_format: JsModuleFormat,
}

impl<'b> JSONPrinter<'b> {
    fn new(builder: &'b AstBuilder, js_module_format: JsModuleFormat) -> Self {
        Self {
            variable_definitions: Default::default(),
            duplicates: Default::default(),
            builder,
            js_module_format,
        }
    }

    fn print(mut self, root_key: AstKey, dedupe: bool) -> String {
        if dedupe {
            let mut visited = Default::default();
            self.collect_value_duplicates(&mut visited, root_key);
        }
        let mut result = String::new();
        self.print_ast(&mut result, root_key, 0, false);
        if self.variable_definitions.is_empty() {
            result
        } else {
            let mut with_variables = String::new();
            with_variables.push_str("(function(){\nvar ");
            let last = self.variable_definitions.len() - 1;
            for (i, (_, value)) in self.variable_definitions.drain(..).enumerate() {
                writeln!(
                    &mut with_variables,
                    "v{} = {}{}",
                    i,
                    value,
                    if i == last { ";" } else { "," }
                )
                .unwrap();
            }
            write!(&mut with_variables, "return {};\n}})()", result).unwrap();
            with_variables
        }
    }

    /// We don't dedupe in an already deduped AST unless the duplicate
    /// also appears on a subtree that's not a duplicate
    /// Input:
    /// [
    ///     [{a: 1}, {b:2}],
    ///     [{a: 1}, {b:2}],
    ///     {b: 2}
    /// ]
    /// Output:
    /// v0 = {b: 2};
    /// v1 = [{a: 1}, v0];
    fn collect_value_duplicates(&mut self, visited: &mut FnvHashSet<AstKey>, key: AstKey) {
        match self.builder.lookup(key) {
            Ast::Array(array) => {
                if array.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    self.duplicates.insert(key);
                    return;
                }
                for val in array {
                    if let Primitive::Key(key) = val {
                        self.collect_value_duplicates(visited, *key);
                    }
                }
            }
            Ast::Object(object) => {
                if object.is_empty() {
                    return;
                }
                if !visited.insert(key) {
                    self.duplicates.insert(key);
                    return;
                }
                for entry in object {
                    if let Primitive::Key(key) = entry.value {
                        self.collect_value_duplicates(visited, key);
                    }
                }
            }
        }
    }

    fn print_ast(&mut self, f: &mut String, key: AstKey, indent: usize, is_dedupe_var: bool) {
        // Only use variable references at depth beyond the top level.
        if indent > 0 && self.duplicates.contains(&key) {
            let v = if self.variable_definitions.contains_key(&key) {
                self.variable_definitions.get_full(&key).unwrap().0
            } else {
                let mut variable = String::new();
                self.print_ast(&mut variable, key, 0, true);
                let v = self.variable_definitions.len();
                self.variable_definitions.insert(key, variable);
                v
            };
            return write!(f, "(v{}/*: any*/)", v).unwrap();
        }

        let ast = self.builder.lookup(key);
        match ast {
            Ast::Object(object) => {
                if object.is_empty() {
                    f.push_str("{}");
                } else {
                    let next_indent = indent + 1;
                    f.push('{');
                    for ObjectEntry { key, value } in object {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        write!(f, "\"{}\": ", key).unwrap();
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push('}');
                }
            }
            Ast::Array(array) => {
                if array.is_empty() {
                    if is_dedupe_var {
                        // Empty arrays can only have one inferred flow type and then conflict if
                        // used in different places, this is unsound if we would write to them but
                        // this whole module is based on the idea of a read only JSON tree.
                        f.push_str("([]/*: any*/)");
                    } else {
                        f.push_str("[]");
                    }
                } else {
                    f.push('[');
                    let next_indent = indent + 1;
                    for value in array {
                        f.push('\n');
                        print_indentation(f, next_indent);
                        self.print_primitive(f, value, next_indent, is_dedupe_var)
                            .unwrap();
                        f.push(',');
                    }
                    f.pop();
                    f.push('\n');
                    print_indentation(f, indent);
                    f.push(']');
                }
            }
        }
    }

    fn print_primitive(
        &mut self,
        f: &mut String,
        primitive: &Primitive,
        indent: usize,
        is_dedupe_var: bool,
    ) -> FmtResult {
        match primitive {
            Primitive::Null => write!(f, "null"),
            Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
            Primitive::RawString(str) => {
                f.push('\"');
                escape(str, f);
                f.push('\"');
                Ok(())
            }
            Primitive::String(key) => write!(f, "\"{}\"", key),
            Primitive::Float(value) => write!(f, "{}", value.as_float()),
            Primitive::Int(value) => write!(f, "{}", value),
            Primitive::Key(key) => {
                self.print_ast(f, *key, indent, is_dedupe_var);
                Ok(())
            }
            Primitive::StorageKey(field_name, key) => {
                write_static_storage_key(f, self.builder, *field_name, *key)
            }
            Primitive::GraphQLModuleDependency(key) => match self.js_module_format {
                JsModuleFormat::CommonJS => write!(f, "require('./{}.graphql')", key),
                JsModuleFormat::Haste => write!(f, "require('{}.graphql')", key),
            },
            Primitive::JSModuleDependency(key) => match self.js_module_format {
                JsModuleFormat::CommonJS => write!(f, "require('./{}')", key),
                JsModuleFormat::Haste => write!(f, "require('{}')", key),
            },
        }
    }
}

fn write_static_storage_key(
    f: &mut String,
    builder: &AstBuilder,
    field_name: StringKey,
    args_key: AstKey,
) -> FmtResult {
    write!(f, "\"{}(", field_name)?;
    let args = builder.lookup(args_key).assert_array();
    for arg_key in args {
        let arg = builder.lookup(arg_key.assert_key()).assert_object();
        let name = &arg
            .iter()
            .find(|ObjectEntry { key, value: _ }| *key == CODEGEN_CONSTANTS.name)
            .expect("Expected `name` to exist")
            .value;
        let name = name.assert_string();
        write!(f, "{}:", name)?;
        write_argument_value(f, builder, arg)?;
        f.push(',');
    }
    f.pop(); // args won't be empty
    f.push_str(")\"");
    Ok(())
}

fn write_argument_value(f: &mut String, builder: &AstBuilder, arg: &[ObjectEntry]) -> FmtResult {
    let key = &arg
        .iter()
        .find(|entry| entry.key == CODEGEN_CONSTANTS.kind)
        .expect("Expected `kind` to exist")
        .value;
    let key = key.assert_string();
    // match doesn't allow `CODEGEN_CONSTANTS.<>` on the match arm, falling back to if statements
    if key == CODEGEN_CONSTANTS.literal {
        let literal = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.value)
            .expect("Expected `name` to exist")
            .value;
        write_constant_value(f, builder, literal)?;
    } else if key == CODEGEN_CONSTANTS.list_value {
        let items = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.items)
            .expect("Expected `items` to exist")
            .value;
        let array = builder.lookup(items.assert_key()).assert_array();

        f.push('[');
        let mut after_first = false;
        for key_or_null in array {
            match key_or_null {
                Primitive::Null => {}
                Primitive::Key(key) => {
                    if after_first {
                        f.push(',');
                    } else {
                        after_first = true;
                    }
                    let object = builder.lookup(*key).assert_object();
                    write_argument_value(f, builder, object)?;
                }
                _ => panic!("Expected an object key or null"),
            }
        }
        f.push(']');
    } else {
        // We filtered out Variables, here it should only be ObjectValue
        let fields = &arg
            .iter()
            .find(|entry| entry.key == CODEGEN_CONSTANTS.fields)
            .expect("Expected `fields` to exist")
            .value;
        let fields = builder.lookup(fields.assert_key()).assert_array();

        f.push('{');
        for field in fields {
            let field = builder.lookup(field.assert_key()).assert_object();
            let name = &field
                .iter()
                .find(|entry| entry.key == CODEGEN_CONSTANTS.name)
                .expect("Expected `name` to exist")
                .value;
            let name = name.assert_string();
            write!(f, "\\\"{}\\\":", name)?;
            write_argument_value(f, builder, field)?;
            f.push(',');
        }
        if !fields.is_empty() {
            f.pop();
        }
        f.push('}');
    }
    Ok(())
}

fn write_constant_value(f: &mut String, builder: &AstBuilder, value: &Primitive) -> FmtResult {
    match value {
        Primitive::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
        Primitive::String(key) => write!(f, "\\\"{}\\\"", key),
        Primitive::Float(value) => write!(f, "{}", value.as_float()),
        Primitive::Int(value) => write!(f, "{}", value),
        Primitive::Key(key) => {
            let ast = builder.lookup(*key);
            match ast {
                Ast::Array(arr) => {
                    f.push('[');
                    for value in arr {
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !arr.is_empty() {
                        f.pop();
                    }
                    f.push(']');
                    Ok(())
                }
                Ast::Object(obj) => {
                    f.push('{');
                    for ObjectEntry { key: name, value } in obj {
                        write!(f, "\\\"{}\\\":", name)?;
                        write_constant_value(f, builder, value)?;
                        f.push(',');
                    }
                    if !obj.is_empty() {
                        f.pop();
                    }
                    f.push('}');
                    Ok(())
                }
            }
        }
        Primitive::Null => {
            f.push_str("null");
            Ok(())
        }
        Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
        Primitive::RawString(_) => panic!("Unexpected RawString"),
        Primitive::GraphQLModuleDependency(_) => panic!("Unexpected GraphQLModuleDependency"),
        Primitive::JSModuleDependency(_) => panic!("Unexpected JSModuleDependency"),
    }
}
<|endoftext|>


<commit_before>// Copyright 2015-2017 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

//! Host primitive.

#[macro_use]
pub mod data;
pub mod ffi;
pub mod telemetry;

pub use self::telemetry::TelemetryTarget;

#[cfg(feature = "remote-run")]
use czmq::{ZCert, ZMsg, ZSock, SocketType};
#[cfg(feature = "remote-run")]
use error::Error;
use error::Result;
#[cfg(feature = "remote-run")]
use serde_json;
use serde_json::Value;
#[cfg(feature = "remote-run")]
use std::mem;
use std::path::Path;
use std::rc::Rc;
#[cfg(feature = "remote-run")]
use zfilexfer;

#[cfg(feature = "local-run")]
/// Primitive for communicating with a managed host.
///
///# Examples
///
/// ```no_run
/// # use inapi::{Command, Host};
///let path: Option<String> = None;
///let mut host = Host::local(path).unwrap();
///
///let cmd = Command::new("whoami");
///let result = cmd.exec(&mut host).unwrap();
/// ```
pub struct Host {
    /// Data for host, comprising data files and telemetry
    data: Rc<Value>,
}

#[cfg(feature = "remote-run")]
/// Primitive for communicating with a managed host.
///
///# Examples
///
/// ```no_run
/// # use inapi::{Command, Host};
///let mut host = Host::connect("hosts/myhost.json").unwrap();
///
///let cmd = Command::new("whoami");
///let result = cmd.exec(&mut host).unwrap();
/// ```
pub struct Host {
    /// Hostname or IP of managed host
    pub hostname: String,
    /// API socket
    api_sock: Option<ZSock>,
    /// File transfer socket
    file_sock: Option<ZSock>,
    /// Data for host, comprising data files and telemetry
    data: Rc<Value>,
}

impl Host {
    #[cfg(feature = "local-run")]
    /// Create a new Host connected to localhost.
    pub fn local<P: AsRef<Path>>(path: Option<P>) -> Result<Host> {
        let mut me = Host {
            data: Rc::new(Value::Null),
        };

        let telemetry = try!(telemetry::Telemetry::init(&mut me));

        match path {
            Some(p) => {
                let value = try!(data::open(p));
                me.data = Rc::new(try!(data::merge(value, telemetry)));
            },
            None => me.data = Rc::new(telemetry),
        }

        Ok(me)
    }

    #[cfg(feature = "remote-run")]
    /// Create a new Host connected to the endpoint specified in the
    /// data file.
    ///
    /// This function expects to find the following keys in the root
    /// namespace: "hostname", "api_port", "file_port".
    pub fn connect<P: AsRef<Path>>(path: P) -> Result<Host> {
        let value = try!(data::open(path.as_ref()));
        let mut me = try!(Self::connect_endpoint(try!(needstr!(value => "/hostname")),
                                                 try!(needu64!(value => "/api_port")) as u32,
                                                 try!(needu64!(value => "/file_port")) as u32));

        let mut telemetry = Rc::new(Value::Null);
        mem::swap(&mut telemetry, &mut me.data);
        // We can use unwrap() here safely as we can guarantee that
        // there is only one strong reference to telemetry.
        me.data = Rc::new(try!(data::merge(value, Rc::try_unwrap(telemetry).unwrap())));

        Ok(me)
    }

    #[cfg(feature = "remote-run")]
    /// Create a new Host connected to the specified endpoint. Note
    /// that this function does not load any user data.
    pub fn connect_endpoint(hostname: &str, api_port: u32, file_port: u32) -> Result<Host> {
        let user_cert = try!(ZCert::load("user.crt"));
        let server_cert = try!(Self::lookup_server_cert(hostname, &user_cert));

        let mut api_sock = ZSock::new(SocketType::REQ);
        user_cert.apply(&mut api_sock);
        api_sock.set_curve_serverkey(server_cert.public_txt());
        api_sock.set_sndtimeo(Some(1800000));
        api_sock.set_rcvtimeo(Some(1800000));
        try!(api_sock.connect(&format!("tcp://{}:{}", hostname, api_port)));

        let mut file_sock = ZSock::new(SocketType::DEALER);
        user_cert.apply(&mut file_sock);
        file_sock.set_curve_serverkey(server_cert.public_txt());
        file_sock.set_sndtimeo(Some(1800000));
        file_sock.set_rcvtimeo(Some(1800000));
        try!(file_sock.connect(&format!("tcp://{}:{}", hostname, file_port)));

        let mut me = Host {
            hostname: hostname.into(),
            api_sock: Some(api_sock),
            file_sock: Some(file_sock),
            data: Rc::new(Value::Null),
        };
        me.data = Rc::new(try!(telemetry::Telemetry::init(&mut me)));

        Ok(me)
    }

    #[cfg(feature = "remote-run")]
    /// Create a new Host specifically for use inside a payload.
    pub fn connect_payload(api_endpoint: &str, file_endpoint: &str) -> Result<Host> {
        let api_sock = ZSock::new(SocketType::DEALER);
        try!(api_sock.connect(api_endpoint));

        let file_sock = ZSock::new(SocketType::DEALER);
        try!(file_sock.connect(file_endpoint));

        let data_json = try!(api_sock.recv_str()).unwrap();
        let data = try!(serde_json::from_str(&data_json));

        Ok(Host {
            hostname: "payload".into(),
            api_sock: Some(api_sock),
            file_sock: Some(file_sock),
            data: Rc::new(data),
        })
    }

    /// Get data for Host.
    pub fn data(&self) -> &Value {
        &self.data
    }

    /// Get a reference counted version of data for Host.
    pub fn data_owned(&self) -> Rc<Value> {
        self.data.clone()
    }

    #[cfg(feature = "remote-run")]
    fn lookup_server_cert(hostname: &str, user_cert: &ZCert) -> Result<ZCert> {
        let auth_cert = try!(ZCert::load("auth.crt"));

        let mut auth_sock = ZSock::new(SocketType::REQ);
        user_cert.apply(&mut auth_sock);
        auth_sock.set_curve_serverkey(auth_cert.public_txt());
        auth_sock.set_sndtimeo(Some(10000));
        auth_sock.set_rcvtimeo(Some(10000));
        try!(auth_sock.connect(&format!("tcp://{}:{}", ::PROJECT_CONFIG.auth_server, ::PROJECT_CONFIG.auth_api_port)));

        // Get server cert from Auth server
        let msg = ZMsg::new();
        try!(msg.addstr("cert::lookup"));
        try!(msg.addstr(hostname));
        try!(msg.send(&mut auth_sock));

        let reply = try!(ZMsg::recv(&mut auth_sock));

        if reply.size() != 2 {
            return Err(Error::HostResponse);
        }

        match try!(reply.popstr().unwrap().or(Err(Error::HostResponse))).as_ref() {
            "Ok" => {
                let pk = try!(reply.popstr().unwrap().or(Err(Error::HostResponse)));
                Ok(try!(ZCert::from_txt(&pk, "0000000000000000000000000000000000000000")))
            },
            "Err" => Err(Error::Auth(try!(reply.popstr().unwrap().or(Err(Error::HostResponse))))),
            _ => Err(Error::HostResponse),
        }
    }

    #[cfg(all(test, feature = "remote-run"))]
    pub fn test_new(hostname: Option<String>, api_sock: Option<ZSock>, file_sock: Option<ZSock>, data: Option<Value>) -> Host {
        let host = Host {
            hostname: hostname.unwrap_or(String::new()),
            api_sock: api_sock,
            file_sock: file_sock,
            data: match data {
                Some(d) => Rc::new(d),
                None => Rc::new(Value::Null),
            },
        };

        host
    }
}

#[cfg(feature = "remote-run")]
pub trait HostSendRecv {
    fn send(&mut self, msg: ZMsg) -> Result<()>;
    fn send_file(&mut self, msg: ZMsg) -> Result<()>;
    fn send_fs_file<P: AsRef<Path>>(&mut self, file: &mut zfilexfer::File, remote_path: P) -> Result<()>;
    fn recv(&mut self, min: usize, max: Option<usize>) -> Result<ZMsg>;
    fn recv_raw(&mut self) -> Result<ZMsg>;
    fn recv_file_raw(&mut self) -> Result<ZMsg>;
    fn extract_header(msg: &ZMsg) -> Result<()>;
}

#[cfg(feature = "remote-run")]
impl HostSendRecv for Host {
    fn send(&mut self, msg: ZMsg) -> Result<()> {
        if self.api_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        try!(msg.send(self.api_sock.as_mut().unwrap()));
        Ok(())
    }

    fn send_file(&mut self, msg: ZMsg) -> Result<()> {
        if self.file_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        try!(msg.send(self.file_sock.as_mut().unwrap()));
        Ok(())
    }

    fn send_fs_file<P: AsRef<Path>>(&mut self, file: &mut zfilexfer::File, remote_path: P) -> Result<()> {
        if self.file_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        try!(file.send(self.file_sock.as_mut().unwrap(), remote_path));
        Ok(())
    }

    fn recv(&mut self, min: usize, max: Option<usize>) -> Result<ZMsg> {
        if self.api_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        let msg = try!(ZMsg::recv(self.api_sock.as_mut().unwrap()));
        try!(Self::extract_header(&msg));

        // Check msg size
        if msg.size() < min || (max.is_some() && msg.size() > max.unwrap()) {
            Err(Error::HostResponse)
        } else {
            Ok(msg)
        }
    }

    fn recv_raw(&mut self) -> Result<ZMsg> {
        if self.api_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        Ok(try!(ZMsg::recv(self.api_sock.as_mut().unwrap())))
    }

    fn recv_file_raw(&mut self) -> Result<ZMsg> {
        if self.file_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        Ok(try!(ZMsg::recv(self.file_sock.as_mut().unwrap())))
    }

    fn extract_header(msg: &ZMsg) -> Result<()> {
        if msg.size() == 0 {
            return Err(Error::HostResponse);
        }

        match try!(msg.popstr().unwrap().or(Err(Error::HostResponse))).as_ref() {
            "Ok" => Ok(()),
            "Err" => {
                if msg.size() == 0 {
                    Err(Error::HostResponse)
                } else {
                    Err(Error::Agent(try!(msg.popstr().unwrap().or(Err(Error::HostResponse)))))
                }
            },
            _ => Err(Error::HostResponse),
        }
    }
}

#[cfg(feature = "remote-run")]
#[cfg(test)]
mod tests {
    use czmq::{ZMsg, ZSock, SocketType, ZSys};
    use std::fs;
    use std::thread;
    use super::*;
    use tempdir::TempDir;
    use zfilexfer::File;

    #[test]
    fn test_connect_payload() {
        let api = ZSock::new(SocketType::DEALER);
        let port = api.bind("tcp://127.0.0.1:*").unwrap();
        let api_endpoint = format!("tcp://127.0.0.1:{}", port);

        let handle = thread::spawn(move || {
            api.send_str(r#"{
                "key": "value"
            }"#).unwrap();

            api.recv_str().unwrap().unwrap();
        });

        let mut host = Host::connect_payload(&api_endpoint, "inproc://file_endpoint").unwrap();
        assert_eq!(host.data()["key"], json!("value"));

        let msg = ZMsg::new();
        msg.addstr("test").unwrap();
        host.send(msg).unwrap();

        handle.join().unwrap();
    }

    #[test]
    fn test_send_recv() {
        let _ = ::_MOCK_ENV.init();

        let (client, server) = ZSys::create_pipe().unwrap();

        let mut host1 = Host::test_new(None, Some(client), None, None);
        let mut host2 = Host::test_new(None, Some(server), None, None);

        let msg = ZMsg::new();
        msg.addstr("Ok").unwrap();
        msg.addstr("moo").unwrap();
        msg.addstr("cow").unwrap();
        host1.send(msg).unwrap();

        let reply = host2.recv(2, Some(2)).unwrap();
        assert_eq!(reply.popstr().unwrap().unwrap(), "moo");
        assert_eq!(reply.popstr().unwrap().unwrap(), "cow");

        let msg = ZMsg::new();
        msg.addstr("No header").unwrap();
        host2.send(msg).unwrap();

        assert!(host1.recv(0, None).is_err());
        let msg = ZMsg::new();
        msg.addstr("Err").unwrap();
        host1.send(msg).unwrap();

        assert!(host2.recv(0, None).is_err());
    }

    #[test]
    fn test_send_file() {
        let _ = ::_MOCK_ENV.init();

        let tempdir = TempDir::new("host_test_send_file").unwrap();
        let path = format!("{}/file.txt", tempdir.path().to_str().unwrap());
        fs::File::create(&path).unwrap();
        let mut file = File::open(&path, None).unwrap();

        let (client, mut server) = ZSys::create_pipe().unwrap();
        client.set_rcvtimeo(Some(500));
        server.set_rcvtimeo(Some(500));

        let handle = thread::spawn(move|| {
            let msg = ZMsg::recv(&mut server).unwrap();
            assert_eq!(msg.popstr().unwrap().unwrap(), "NEW");

            server.flush();
            server.send_str("Ok").unwrap();
        });

        let mut host = Host::test_new(None, None, Some(client), None);
        assert!(host.send_fs_file(&mut file, &path).is_ok());

        handle.join().unwrap();
    }
}
<commit_msg>Allow 10 second timeout on connect, then 30 minute timeout for transactions<commit_after>// Copyright 2015-2017 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

//! Host primitive.

#[macro_use]
pub mod data;
pub mod ffi;
pub mod telemetry;

pub use self::telemetry::TelemetryTarget;

#[cfg(feature = "remote-run")]
use czmq::{ZCert, ZMsg, ZSock, SocketType};
#[cfg(feature = "remote-run")]
use error::Error;
use error::Result;
#[cfg(feature = "remote-run")]
use serde_json;
use serde_json::Value;
#[cfg(feature = "remote-run")]
use std::mem;
use std::path::Path;
use std::rc::Rc;
#[cfg(feature = "remote-run")]
use zfilexfer;

#[cfg(feature = "local-run")]
/// Primitive for communicating with a managed host.
///
///# Examples
///
/// ```no_run
/// # use inapi::{Command, Host};
///let path: Option<String> = None;
///let mut host = Host::local(path).unwrap();
///
///let cmd = Command::new("whoami");
///let result = cmd.exec(&mut host).unwrap();
/// ```
pub struct Host {
    /// Data for host, comprising data files and telemetry
    data: Rc<Value>,
}

#[cfg(feature = "remote-run")]
/// Primitive for communicating with a managed host.
///
///# Examples
///
/// ```no_run
/// # use inapi::{Command, Host};
///let mut host = Host::connect("hosts/myhost.json").unwrap();
///
///let cmd = Command::new("whoami");
///let result = cmd.exec(&mut host).unwrap();
/// ```
pub struct Host {
    /// Hostname or IP of managed host
    pub hostname: String,
    /// API socket
    api_sock: Option<ZSock>,
    /// File transfer socket
    file_sock: Option<ZSock>,
    /// Data for host, comprising data files and telemetry
    data: Rc<Value>,
}

impl Host {
    #[cfg(feature = "local-run")]
    /// Create a new Host connected to localhost.
    pub fn local<P: AsRef<Path>>(path: Option<P>) -> Result<Host> {
        let mut me = Host {
            data: Rc::new(Value::Null),
        };

        let telemetry = try!(telemetry::Telemetry::init(&mut me));

        match path {
            Some(p) => {
                let value = try!(data::open(p));
                me.data = Rc::new(try!(data::merge(value, telemetry)));
            },
            None => me.data = Rc::new(telemetry),
        }

        Ok(me)
    }

    #[cfg(feature = "remote-run")]
    /// Create a new Host connected to the endpoint specified in the
    /// data file.
    ///
    /// This function expects to find the following keys in the root
    /// namespace: "hostname", "api_port", "file_port".
    pub fn connect<P: AsRef<Path>>(path: P) -> Result<Host> {
        let value = try!(data::open(path.as_ref()));
        let mut me = try!(Self::connect_endpoint(try!(needstr!(value => "/hostname")),
                                                 try!(needu64!(value => "/api_port")) as u32,
                                                 try!(needu64!(value => "/file_port")) as u32));

        let mut telemetry = Rc::new(Value::Null);
        mem::swap(&mut telemetry, &mut me.data);
        // We can use unwrap() here safely as we can guarantee that
        // there is only one strong reference to telemetry.
        me.data = Rc::new(try!(data::merge(value, Rc::try_unwrap(telemetry).unwrap())));

        Ok(me)
    }

    #[cfg(feature = "remote-run")]
    /// Create a new Host connected to the specified endpoint. Note
    /// that this function does not load any user data.
    pub fn connect_endpoint(hostname: &str, api_port: u32, file_port: u32) -> Result<Host> {
        let user_cert = try!(ZCert::load("user.crt"));
        let server_cert = try!(Self::lookup_server_cert(hostname, &user_cert));

        let mut api_sock = ZSock::new(SocketType::REQ);
        user_cert.apply(&mut api_sock);
        api_sock.set_curve_serverkey(server_cert.public_txt());
        api_sock.set_sndtimeo(Some(10000)); // 10 seconds
        api_sock.set_rcvtimeo(Some(10000));
        try!(api_sock.connect(&format!("tcp://{}:{}", hostname, api_port)));
        api_sock.set_sndtimeo(Some(1800000)); // 30 minutes
        api_sock.set_rcvtimeo(Some(1800000));

        let mut file_sock = ZSock::new(SocketType::DEALER);
        user_cert.apply(&mut file_sock);
        file_sock.set_curve_serverkey(server_cert.public_txt());
        file_sock.set_sndtimeo(Some(10000)); // 10 seconds
        file_sock.set_rcvtimeo(Some(10000));
        try!(file_sock.connect(&format!("tcp://{}:{}", hostname, file_port)));
        file_sock.set_sndtimeo(Some(1800000)); // 30 minutes
        file_sock.set_rcvtimeo(Some(1800000));

        let mut me = Host {
            hostname: hostname.into(),
            api_sock: Some(api_sock),
            file_sock: Some(file_sock),
            data: Rc::new(Value::Null),
        };
        me.data = Rc::new(try!(telemetry::Telemetry::init(&mut me)));

        Ok(me)
    }

    #[cfg(feature = "remote-run")]
    /// Create a new Host specifically for use inside a payload.
    pub fn connect_payload(api_endpoint: &str, file_endpoint: &str) -> Result<Host> {
        let api_sock = ZSock::new(SocketType::DEALER);
        try!(api_sock.connect(api_endpoint));

        let file_sock = ZSock::new(SocketType::DEALER);
        try!(file_sock.connect(file_endpoint));

        let data_json = try!(api_sock.recv_str()).unwrap();
        let data = try!(serde_json::from_str(&data_json));

        Ok(Host {
            hostname: "payload".into(),
            api_sock: Some(api_sock),
            file_sock: Some(file_sock),
            data: Rc::new(data),
        })
    }

    /// Get data for Host.
    pub fn data(&self) -> &Value {
        &self.data
    }

    /// Get a reference counted version of data for Host.
    pub fn data_owned(&self) -> Rc<Value> {
        self.data.clone()
    }

    #[cfg(feature = "remote-run")]
    fn lookup_server_cert(hostname: &str, user_cert: &ZCert) -> Result<ZCert> {
        let auth_cert = try!(ZCert::load("auth.crt"));

        let mut auth_sock = ZSock::new(SocketType::REQ);
        user_cert.apply(&mut auth_sock);
        auth_sock.set_curve_serverkey(auth_cert.public_txt());
        auth_sock.set_sndtimeo(Some(10000));
        auth_sock.set_rcvtimeo(Some(10000));
        try!(auth_sock.connect(&format!("tcp://{}:{}", ::PROJECT_CONFIG.auth_server, ::PROJECT_CONFIG.auth_api_port)));

        // Get server cert from Auth server
        let msg = ZMsg::new();
        try!(msg.addstr("cert::lookup"));
        try!(msg.addstr(hostname));
        try!(msg.send(&mut auth_sock));

        let reply = try!(ZMsg::recv(&mut auth_sock));

        if reply.size() != 2 {
            return Err(Error::HostResponse);
        }

        match try!(reply.popstr().unwrap().or(Err(Error::HostResponse))).as_ref() {
            "Ok" => {
                let pk = try!(reply.popstr().unwrap().or(Err(Error::HostResponse)));
                Ok(try!(ZCert::from_txt(&pk, "0000000000000000000000000000000000000000")))
            },
            "Err" => Err(Error::Auth(try!(reply.popstr().unwrap().or(Err(Error::HostResponse))))),
            _ => Err(Error::HostResponse),
        }
    }

    #[cfg(all(test, feature = "remote-run"))]
    pub fn test_new(hostname: Option<String>, api_sock: Option<ZSock>, file_sock: Option<ZSock>, data: Option<Value>) -> Host {
        let host = Host {
            hostname: hostname.unwrap_or(String::new()),
            api_sock: api_sock,
            file_sock: file_sock,
            data: match data {
                Some(d) => Rc::new(d),
                None => Rc::new(Value::Null),
            },
        };

        host
    }
}

#[cfg(feature = "remote-run")]
pub trait HostSendRecv {
    fn send(&mut self, msg: ZMsg) -> Result<()>;
    fn send_file(&mut self, msg: ZMsg) -> Result<()>;
    fn send_fs_file<P: AsRef<Path>>(&mut self, file: &mut zfilexfer::File, remote_path: P) -> Result<()>;
    fn recv(&mut self, min: usize, max: Option<usize>) -> Result<ZMsg>;
    fn recv_raw(&mut self) -> Result<ZMsg>;
    fn recv_file_raw(&mut self) -> Result<ZMsg>;
    fn extract_header(msg: &ZMsg) -> Result<()>;
}

#[cfg(feature = "remote-run")]
impl HostSendRecv for Host {
    fn send(&mut self, msg: ZMsg) -> Result<()> {
        if self.api_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        try!(msg.send(self.api_sock.as_mut().unwrap()));
        Ok(())
    }

    fn send_file(&mut self, msg: ZMsg) -> Result<()> {
        if self.file_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        try!(msg.send(self.file_sock.as_mut().unwrap()));
        Ok(())
    }

    fn send_fs_file<P: AsRef<Path>>(&mut self, file: &mut zfilexfer::File, remote_path: P) -> Result<()> {
        if self.file_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        try!(file.send(self.file_sock.as_mut().unwrap(), remote_path));
        Ok(())
    }

    fn recv(&mut self, min: usize, max: Option<usize>) -> Result<ZMsg> {
        if self.api_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        let msg = try!(ZMsg::recv(self.api_sock.as_mut().unwrap()));
        try!(Self::extract_header(&msg));

        // Check msg size
        if msg.size() < min || (max.is_some() && msg.size() > max.unwrap()) {
            Err(Error::HostResponse)
        } else {
            Ok(msg)
        }
    }

    fn recv_raw(&mut self) -> Result<ZMsg> {
        if self.api_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        Ok(try!(ZMsg::recv(self.api_sock.as_mut().unwrap())))
    }

    fn recv_file_raw(&mut self) -> Result<ZMsg> {
        if self.file_sock.is_none() {
            return Err(Error::HostDisconnected);
        }

        Ok(try!(ZMsg::recv(self.file_sock.as_mut().unwrap())))
    }

    fn extract_header(msg: &ZMsg) -> Result<()> {
        if msg.size() == 0 {
            return Err(Error::HostResponse);
        }

        match try!(msg.popstr().unwrap().or(Err(Error::HostResponse))).as_ref() {
            "Ok" => Ok(()),
            "Err" => {
                if msg.size() == 0 {
                    Err(Error::HostResponse)
                } else {
                    Err(Error::Agent(try!(msg.popstr().unwrap().or(Err(Error::HostResponse)))))
                }
            },
            _ => Err(Error::HostResponse),
        }
    }
}

#[cfg(feature = "remote-run")]
#[cfg(test)]
mod tests {
    use czmq::{ZMsg, ZSock, SocketType, ZSys};
    use std::fs;
    use std::thread;
    use super::*;
    use tempdir::TempDir;
    use zfilexfer::File;

    #[test]
    fn test_connect_payload() {
        let api = ZSock::new(SocketType::DEALER);
        let port = api.bind("tcp://127.0.0.1:*").unwrap();
        let api_endpoint = format!("tcp://127.0.0.1:{}", port);

        let handle = thread::spawn(move || {
            api.send_str(r#"{
                "key": "value"
            }"#).unwrap();

            api.recv_str().unwrap().unwrap();
        });

        let mut host = Host::connect_payload(&api_endpoint, "inproc://file_endpoint").unwrap();
        assert_eq!(host.data()["key"], json!("value"));

        let msg = ZMsg::new();
        msg.addstr("test").unwrap();
        host.send(msg).unwrap();

        handle.join().unwrap();
    }

    #[test]
    fn test_send_recv() {
        let _ = ::_MOCK_ENV.init();

        let (client, server) = ZSys::create_pipe().unwrap();

        let mut host1 = Host::test_new(None, Some(client), None, None);
        let mut host2 = Host::test_new(None, Some(server), None, None);

        let msg = ZMsg::new();
        msg.addstr("Ok").unwrap();
        msg.addstr("moo").unwrap();
        msg.addstr("cow").unwrap();
        host1.send(msg).unwrap();

        let reply = host2.recv(2, Some(2)).unwrap();
        assert_eq!(reply.popstr().unwrap().unwrap(), "moo");
        assert_eq!(reply.popstr().unwrap().unwrap(), "cow");

        let msg = ZMsg::new();
        msg.addstr("No header").unwrap();
        host2.send(msg).unwrap();

        assert!(host1.recv(0, None).is_err());
        let msg = ZMsg::new();
        msg.addstr("Err").unwrap();
        host1.send(msg).unwrap();

        assert!(host2.recv(0, None).is_err());
    }

    #[test]
    fn test_send_file() {
        let _ = ::_MOCK_ENV.init();

        let tempdir = TempDir::new("host_test_send_file").unwrap();
        let path = format!("{}/file.txt", tempdir.path().to_str().unwrap());
        fs::File::create(&path).unwrap();
        let mut file = File::open(&path, None).unwrap();

        let (client, mut server) = ZSys::create_pipe().unwrap();
        client.set_rcvtimeo(Some(500));
        server.set_rcvtimeo(Some(500));

        let handle = thread::spawn(move|| {
            let msg = ZMsg::recv(&mut server).unwrap();
            assert_eq!(msg.popstr().unwrap().unwrap(), "NEW");

            server.flush();
            server.send_str("Ok").unwrap();
        });

        let mut host = Host::test_new(None, None, Some(client), None);
        assert!(host.send_fs_file(&mut file, &path).is_ok());

        handle.join().unwrap();
    }
}
<|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/. */

//! Configuration options for a single run of the servo application. Created
//! from command line arguments.

use geometry::ScreenPx;

use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D;
use layers::geometry::DevicePixel;
use getopts;
use std::cmp;
use std::io;
use std::mem;
use std::os;
use std::ptr;
use std::rt;

/// Global flags for Servo, currently set on the command line.
#[deriving(Clone)]
pub struct Opts {
    /// The initial URLs to load.
    pub urls: Vec<String>,

    /// How many threads to use for CPU rendering (`-t`).
    ///
    /// FIXME(pcwalton): This is not currently used. All rendering is sequential.
    pub n_render_threads: uint,

    /// True to use CPU painting, false to use GPU painting via Skia-GL (`-c`). Note that
    /// compositing is always done on the GPU.
    pub cpu_painting: bool,

    /// The maximum size of each tile in pixels (`-s`).
    pub tile_size: uint,

    /// The ratio of device pixels per px at the default scale. If unspecified, will use the
    /// platform default setting.
    pub device_pixels_per_px: Option<ScaleFactor<ScreenPx, DevicePixel, f32>>,

    /// `None` to disable the time profiler or `Some` with an interval in seconds to enable it and
    /// cause it to produce output on that interval (`-p`).
    pub time_profiler_period: Option<f64>,

    /// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it
    /// and cause it to produce output on that interval (`-m`).
    pub memory_profiler_period: Option<f64>,

    /// Enable experimental web features (`-e`).
    pub enable_experimental: bool,

    /// The number of threads to use for layout (`-y`). Defaults to 1, which results in a recursive
    /// sequential algorithm.
    pub layout_threads: uint,

    pub nonincremental_layout: bool,

    /// True to exit after the page load (`-x`).
    pub exit_after_load: bool,

    pub output_file: Option<String>,
    pub headless: bool,
    pub hard_fail: bool,

    /// True if we should bubble intrinsic widths sequentially (`-b`). If this is true, then
    /// intrinsic widths are computed as a separate pass instead of during flow construction. You
    /// may wish to turn this flag on in order to benchmark style recalculation against other
    /// browser engines.
    pub bubble_inline_sizes_separately: bool,

    /// True if we should show borders on all layers and tiles for
    /// debugging purposes (`--show-debug-borders`).
    pub show_debug_borders: bool,

    /// True if we should show borders on all fragments for debugging purposes (`--show-debug-fragment-borders`).
    pub show_debug_fragment_borders: bool,

    /// If set with --disable-text-aa, disable antialiasing on fonts. This is primarily useful for reftests
    /// where pixel perfect results are required when using fonts such as the Ahem
    /// font for layout tests.
    pub enable_text_antialiasing: bool,

    /// True if each step of layout is traced to an external JSON file
    /// for debugging purposes. Settings this implies sequential layout
    /// and render.
    pub trace_layout: bool,

    /// `None` to disable devtools or `Some` with a port number to start a server to listen to
    /// remote Firefox devtools connections.
    pub devtools_port: Option<u16>,

    /// The initial requested size of the window.
    pub initial_window_size: TypedSize2D<ScreenPx, uint>,

    /// An optional string allowing the user agent to be set for testing.
    pub user_agent: Option<String>,

    /// Dumps the flow tree after a layout.
    pub dump_flow_tree: bool,

    /// Whether to show an error when display list geometry escapes flow overflow regions.
    pub validate_display_list_geometry: bool,
}

fn print_usage(app: &str, opts: &[getopts::OptGroup]) {
    let message = format!("Usage: {} [ options ... ] [URL]\n\twhere options include", app);
    println!("{}", getopts::usage(message.as_slice(), opts));
}

fn args_fail(msg: &str) {
    io::stderr().write_line(msg).unwrap();
    os::set_exit_status(1);
}

// Always use CPU rendering on android.

#[cfg(target_os="android")]
static FORCE_CPU_PAINTING: bool = true;

#[cfg(not(target_os="android"))]
static FORCE_CPU_PAINTING: bool = false;

pub fn from_cmdline_args(args: &[String]) -> bool {
    let app_name = args[0].to_string();
    let args = args.tail();

    let opts = vec!(
        getopts::optflag("c", "cpu", "CPU rendering"),
        getopts::optopt("o", "output", "Output file", "output.png"),
        getopts::optopt("r", "rendering", "Rendering backend", "direct2d|core-graphics|core-graphics-accelerated|cairo|skia."),
        getopts::optopt("s", "size", "Size of tiles", "512"),
        getopts::optopt("", "device-pixel-ratio", "Device pixels per px", ""),
        getopts::optflag("e", "experimental", "Enable experimental web features"),
        getopts::optopt("t", "threads", "Number of render threads", "1"),
        getopts::optflagopt("p", "profile", "Profiler flag and output interval", "10"),
        getopts::optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10"),
        getopts::optflag("x", "exit", "Exit after load flag"),
        getopts::optopt("y", "layout-threads", "Number of threads to use for layout", "1"),
        getopts::optflag("i", "nonincremental-layout", "Enable to turn off incremental layout."),
        getopts::optflag("z", "headless", "Headless mode"),
        getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"),
        getopts::optflag("b", "bubble-widths", "Bubble intrinsic widths separately like other engines"),
        getopts::optflag("", "show-debug-borders", "Show debugging borders on layers and tiles."),
        getopts::optflag("", "show-debug-fragment-borders", "Show debugging borders on fragments."),
        getopts::optflag("", "disable-text-aa", "Disable antialiasing for text rendering."),
        getopts::optflag("", "trace-layout", "Write layout trace to external file for debugging."),
        getopts::optflagopt("", "devtools", "Start remote devtools server on port", "6000"),
        getopts::optopt("", "resolution", "Set window resolution.", "800x600"),
        getopts::optopt("u", "user-agent", "Set custom user agent string", "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)"),
        getopts::optflag("", "dump-flow-tree", "Dump the flow (render) tree during each layout."),
        getopts::optflag("", "validate-display-list-geometry", "Display an error when display list geometry escapes overflow region."),
        getopts::optflag("h", "help", "Print this message")
    );

    let opt_match = match getopts::getopts(args, opts.as_slice()) {
        Ok(m) => m,
        Err(f) => {
            args_fail(format!("{}", f).as_slice());
            return false;
        }
    };

    if opt_match.opt_present("h") || opt_match.opt_present("help") {
        print_usage(app_name.as_slice(), opts.as_slice());
        return false;
    };

    let urls = if opt_match.free.is_empty() {
        print_usage(app_name.as_slice(), opts.as_slice());
        args_fail("servo asks that you provide 1 or more URLs");
        return false;
    } else {
        opt_match.free.clone()
    };

    let tile_size: uint = match opt_match.opt_str("s") {
        Some(tile_size_str) => from_str(tile_size_str.as_slice()).unwrap(),
        None => 512,
    };

    let device_pixels_per_px = opt_match.opt_str("device-pixel-ratio").map(|dppx_str|
        ScaleFactor(from_str(dppx_str.as_slice()).unwrap())
    );

    let mut n_render_threads: uint = match opt_match.opt_str("t") {
        Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(),
        None => 1,      // FIXME: Number of cores.
    };

    // If only the flag is present, default to a 5 second period for both profilers.
    let time_profiler_period = opt_match.opt_default("p", "5").map(|period| {
        from_str(period.as_slice()).unwrap()
    });
    let memory_profiler_period = opt_match.opt_default("m", "5").map(|period| {
        from_str(period.as_slice()).unwrap()
    });

    let cpu_painting = FORCE_CPU_PAINTING || opt_match.opt_present("c");

    let mut layout_threads: uint = match opt_match.opt_str("y") {
        Some(layout_threads_str) => from_str(layout_threads_str.as_slice()).unwrap(),
        None => cmp::max(rt::default_sched_threads() * 3 / 4, 1),
    };

    let nonincremental_layout = opt_match.opt_present("i");

    let mut bubble_inline_sizes_separately = opt_match.opt_present("b");

    let trace_layout = opt_match.opt_present("trace-layout");
    if trace_layout {
        n_render_threads = 1;
        layout_threads = 1;
        bubble_inline_sizes_separately = true;
    }

    let devtools_port = opt_match.opt_default("devtools", "6000").map(|port| {
        from_str(port.as_slice()).unwrap()
    });

    let initial_window_size = match opt_match.opt_str("resolution") {
        Some(res_string) => {
            let res: Vec<uint> = res_string.as_slice().split('x').map(|r| from_str(r).unwrap()).collect();
            TypedSize2D(res[0], res[1])
        }
        None => {
            TypedSize2D(800, 600)
        }
    };

    let opts = Opts {
        urls: urls,
        n_render_threads: n_render_threads,
        cpu_painting: cpu_painting,
        tile_size: tile_size,
        device_pixels_per_px: device_pixels_per_px,
        time_profiler_period: time_profiler_period,
        memory_profiler_period: memory_profiler_period,
        enable_experimental: opt_match.opt_present("e"),
        layout_threads: layout_threads,
        nonincremental_layout: nonincremental_layout,
        exit_after_load: opt_match.opt_present("x"),
        output_file: opt_match.opt_str("o"),
        headless: opt_match.opt_present("z"),
        hard_fail: opt_match.opt_present("f"),
        bubble_inline_sizes_separately: bubble_inline_sizes_separately,
        show_debug_borders: opt_match.opt_present("show-debug-borders"),
        show_debug_fragment_borders: opt_match.opt_present("show-debug-fragment-borders"),
        enable_text_antialiasing: !opt_match.opt_present("disable-text-aa"),
        trace_layout: trace_layout,
        devtools_port: devtools_port,
        initial_window_size: initial_window_size,
        user_agent: opt_match.opt_str("u"),
        dump_flow_tree: opt_match.opt_present("dump-flow-tree"),
        validate_display_list_geometry: opt_match.opt_present("validate-display-list-geometry"),
    };

    set_opts(opts);
    true
}

static mut EXPERIMENTAL_ENABLED: bool = false;

pub fn set_experimental_enabled(new_value: bool) {
    unsafe {
        EXPERIMENTAL_ENABLED = new_value;
    }
}

pub fn experimental_enabled() -> bool {
    unsafe {
        EXPERIMENTAL_ENABLED
    }
}

// Make Opts available globally. This saves having to clone and pass
// opts everywhere it is used, which gets particularly cumbersome
// when passing through the DOM structures.
static mut OPTIONS: *mut Opts = 0 as *mut Opts;

pub fn set_opts(opts: Opts) {
    unsafe {
        let box_opts = box opts;
        OPTIONS = mem::transmute(box_opts);
    }
}

#[inline]
pub fn get<'a>() -> &'a Opts {
    unsafe {
        assert!(OPTIONS != ptr::null_mut());
        mem::transmute(OPTIONS)
    }
}
<commit_msg>Remove the help text for render backend option. follow up #3711.<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/. */

//! Configuration options for a single run of the servo application. Created
//! from command line arguments.

use geometry::ScreenPx;

use geom::scale_factor::ScaleFactor;
use geom::size::TypedSize2D;
use layers::geometry::DevicePixel;
use getopts;
use std::cmp;
use std::io;
use std::mem;
use std::os;
use std::ptr;
use std::rt;

/// Global flags for Servo, currently set on the command line.
#[deriving(Clone)]
pub struct Opts {
    /// The initial URLs to load.
    pub urls: Vec<String>,

    /// How many threads to use for CPU rendering (`-t`).
    ///
    /// FIXME(pcwalton): This is not currently used. All rendering is sequential.
    pub n_render_threads: uint,

    /// True to use CPU painting, false to use GPU painting via Skia-GL (`-c`). Note that
    /// compositing is always done on the GPU.
    pub cpu_painting: bool,

    /// The maximum size of each tile in pixels (`-s`).
    pub tile_size: uint,

    /// The ratio of device pixels per px at the default scale. If unspecified, will use the
    /// platform default setting.
    pub device_pixels_per_px: Option<ScaleFactor<ScreenPx, DevicePixel, f32>>,

    /// `None` to disable the time profiler or `Some` with an interval in seconds to enable it and
    /// cause it to produce output on that interval (`-p`).
    pub time_profiler_period: Option<f64>,

    /// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it
    /// and cause it to produce output on that interval (`-m`).
    pub memory_profiler_period: Option<f64>,

    /// Enable experimental web features (`-e`).
    pub enable_experimental: bool,

    /// The number of threads to use for layout (`-y`). Defaults to 1, which results in a recursive
    /// sequential algorithm.
    pub layout_threads: uint,

    pub nonincremental_layout: bool,

    /// True to exit after the page load (`-x`).
    pub exit_after_load: bool,

    pub output_file: Option<String>,
    pub headless: bool,
    pub hard_fail: bool,

    /// True if we should bubble intrinsic widths sequentially (`-b`). If this is true, then
    /// intrinsic widths are computed as a separate pass instead of during flow construction. You
    /// may wish to turn this flag on in order to benchmark style recalculation against other
    /// browser engines.
    pub bubble_inline_sizes_separately: bool,

    /// True if we should show borders on all layers and tiles for
    /// debugging purposes (`--show-debug-borders`).
    pub show_debug_borders: bool,

    /// True if we should show borders on all fragments for debugging purposes (`--show-debug-fragment-borders`).
    pub show_debug_fragment_borders: bool,

    /// If set with --disable-text-aa, disable antialiasing on fonts. This is primarily useful for reftests
    /// where pixel perfect results are required when using fonts such as the Ahem
    /// font for layout tests.
    pub enable_text_antialiasing: bool,

    /// True if each step of layout is traced to an external JSON file
    /// for debugging purposes. Settings this implies sequential layout
    /// and render.
    pub trace_layout: bool,

    /// `None` to disable devtools or `Some` with a port number to start a server to listen to
    /// remote Firefox devtools connections.
    pub devtools_port: Option<u16>,

    /// The initial requested size of the window.
    pub initial_window_size: TypedSize2D<ScreenPx, uint>,

    /// An optional string allowing the user agent to be set for testing.
    pub user_agent: Option<String>,

    /// Dumps the flow tree after a layout.
    pub dump_flow_tree: bool,

    /// Whether to show an error when display list geometry escapes flow overflow regions.
    pub validate_display_list_geometry: bool,
}

fn print_usage(app: &str, opts: &[getopts::OptGroup]) {
    let message = format!("Usage: {} [ options ... ] [URL]\n\twhere options include", app);
    println!("{}", getopts::usage(message.as_slice(), opts));
}

fn args_fail(msg: &str) {
    io::stderr().write_line(msg).unwrap();
    os::set_exit_status(1);
}

// Always use CPU rendering on android.

#[cfg(target_os="android")]
static FORCE_CPU_PAINTING: bool = true;

#[cfg(not(target_os="android"))]
static FORCE_CPU_PAINTING: bool = false;

pub fn from_cmdline_args(args: &[String]) -> bool {
    let app_name = args[0].to_string();
    let args = args.tail();

    let opts = vec!(
        getopts::optflag("c", "cpu", "CPU rendering"),
        getopts::optopt("o", "output", "Output file", "output.png"),
        getopts::optopt("s", "size", "Size of tiles", "512"),
        getopts::optopt("", "device-pixel-ratio", "Device pixels per px", ""),
        getopts::optflag("e", "experimental", "Enable experimental web features"),
        getopts::optopt("t", "threads", "Number of render threads", "1"),
        getopts::optflagopt("p", "profile", "Profiler flag and output interval", "10"),
        getopts::optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10"),
        getopts::optflag("x", "exit", "Exit after load flag"),
        getopts::optopt("y", "layout-threads", "Number of threads to use for layout", "1"),
        getopts::optflag("i", "nonincremental-layout", "Enable to turn off incremental layout."),
        getopts::optflag("z", "headless", "Headless mode"),
        getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"),
        getopts::optflag("b", "bubble-widths", "Bubble intrinsic widths separately like other engines"),
        getopts::optflag("", "show-debug-borders", "Show debugging borders on layers and tiles."),
        getopts::optflag("", "show-debug-fragment-borders", "Show debugging borders on fragments."),
        getopts::optflag("", "disable-text-aa", "Disable antialiasing for text rendering."),
        getopts::optflag("", "trace-layout", "Write layout trace to external file for debugging."),
        getopts::optflagopt("", "devtools", "Start remote devtools server on port", "6000"),
        getopts::optopt("", "resolution", "Set window resolution.", "800x600"),
        getopts::optopt("u", "user-agent", "Set custom user agent string", "NCSA Mosaic/1.0 (X11;SunOS 4.1.4 sun4m)"),
        getopts::optflag("", "dump-flow-tree", "Dump the flow (render) tree during each layout."),
        getopts::optflag("", "validate-display-list-geometry", "Display an error when display list geometry escapes overflow region."),
        getopts::optflag("h", "help", "Print this message")
    );

    let opt_match = match getopts::getopts(args, opts.as_slice()) {
        Ok(m) => m,
        Err(f) => {
            args_fail(format!("{}", f).as_slice());
            return false;
        }
    };

    if opt_match.opt_present("h") || opt_match.opt_present("help") {
        print_usage(app_name.as_slice(), opts.as_slice());
        return false;
    };

    let urls = if opt_match.free.is_empty() {
        print_usage(app_name.as_slice(), opts.as_slice());
        args_fail("servo asks that you provide 1 or more URLs");
        return false;
    } else {
        opt_match.free.clone()
    };

    let tile_size: uint = match opt_match.opt_str("s") {
        Some(tile_size_str) => from_str(tile_size_str.as_slice()).unwrap(),
        None => 512,
    };

    let device_pixels_per_px = opt_match.opt_str("device-pixel-ratio").map(|dppx_str|
        ScaleFactor(from_str(dppx_str.as_slice()).unwrap())
    );

    let mut n_render_threads: uint = match opt_match.opt_str("t") {
        Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(),
        None => 1,      // FIXME: Number of cores.
    };

    // If only the flag is present, default to a 5 second period for both profilers.
    let time_profiler_period = opt_match.opt_default("p", "5").map(|period| {
        from_str(period.as_slice()).unwrap()
    });
    let memory_profiler_period = opt_match.opt_default("m", "5").map(|period| {
        from_str(period.as_slice()).unwrap()
    });

    let cpu_painting = FORCE_CPU_PAINTING || opt_match.opt_present("c");

    let mut layout_threads: uint = match opt_match.opt_str("y") {
        Some(layout_threads_str) => from_str(layout_threads_str.as_slice()).unwrap(),
        None => cmp::max(rt::default_sched_threads() * 3 / 4, 1),
    };

    let nonincremental_layout = opt_match.opt_present("i");

    let mut bubble_inline_sizes_separately = opt_match.opt_present("b");

    let trace_layout = opt_match.opt_present("trace-layout");
    if trace_layout {
        n_render_threads = 1;
        layout_threads = 1;
        bubble_inline_sizes_separately = true;
    }

    let devtools_port = opt_match.opt_default("devtools", "6000").map(|port| {
        from_str(port.as_slice()).unwrap()
    });

    let initial_window_size = match opt_match.opt_str("resolution") {
        Some(res_string) => {
            let res: Vec<uint> = res_string.as_slice().split('x').map(|r| from_str(r).unwrap()).collect();
            TypedSize2D(res[0], res[1])
        }
        None => {
            TypedSize2D(800, 600)
        }
    };

    let opts = Opts {
        urls: urls,
        n_render_threads: n_render_threads,
        cpu_painting: cpu_painting,
        tile_size: tile_size,
        device_pixels_per_px: device_pixels_per_px,
        time_profiler_period: time_profiler_period,
        memory_profiler_period: memory_profiler_period,
        enable_experimental: opt_match.opt_present("e"),
        layout_threads: layout_threads,
        nonincremental_layout: nonincremental_layout,
        exit_after_load: opt_match.opt_present("x"),
        output_file: opt_match.opt_str("o"),
        headless: opt_match.opt_present("z"),
        hard_fail: opt_match.opt_present("f"),
        bubble_inline_sizes_separately: bubble_inline_sizes_separately,
        show_debug_borders: opt_match.opt_present("show-debug-borders"),
        show_debug_fragment_borders: opt_match.opt_present("show-debug-fragment-borders"),
        enable_text_antialiasing: !opt_match.opt_present("disable-text-aa"),
        trace_layout: trace_layout,
        devtools_port: devtools_port,
        initial_window_size: initial_window_size,
        user_agent: opt_match.opt_str("u"),
        dump_flow_tree: opt_match.opt_present("dump-flow-tree"),
        validate_display_list_geometry: opt_match.opt_present("validate-display-list-geometry"),
    };

    set_opts(opts);
    true
}

static mut EXPERIMENTAL_ENABLED: bool = false;

pub fn set_experimental_enabled(new_value: bool) {
    unsafe {
        EXPERIMENTAL_ENABLED = new_value;
    }
}

pub fn experimental_enabled() -> bool {
    unsafe {
        EXPERIMENTAL_ENABLED
    }
}

// Make Opts available globally. This saves having to clone and pass
// opts everywhere it is used, which gets particularly cumbersome
// when passing through the DOM structures.
static mut OPTIONS: *mut Opts = 0 as *mut Opts;

pub fn set_opts(opts: Opts) {
    unsafe {
        let box_opts = box opts;
        OPTIONS = mem::transmute(box_opts);
    }
}

#[inline]
pub fn get<'a>() -> &'a Opts {
    unsafe {
        assert!(OPTIONS != ptr::null_mut());
        mem::transmute(OPTIONS)
    }
}
<|endoftext|>