<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/. */

// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use canvas_traits::CanvasMsg;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::WebGLFramebufferBinding;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::js::{HeapGCValue, JS, Root};
use dom::bindings::reflector::reflect_dom_object;
use dom::globalscope::GlobalScope;
use dom::webglobject::WebGLObject;
use dom::webglrenderbuffer::WebGLRenderbuffer;
use dom::webgltexture::WebGLTexture;
use ipc_channel::ipc::{self, IpcSender};
use std::cell::Cell;
use webrender_traits::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId, WebGLResult, WebGLError};

#[must_root]
#[derive(JSTraceable, Clone, HeapSizeOf)]
enum WebGLFramebufferAttachment {
    Renderbuffer(JS<WebGLRenderbuffer>),
    Texture(JS<WebGLTexture>),
}

impl HeapGCValue for WebGLFramebufferAttachment {}

#[dom_struct]
pub struct WebGLFramebuffer {
    webgl_object: WebGLObject,
    id: WebGLFramebufferId,
    /// target can only be gl::FRAMEBUFFER at the moment
    target: Cell<Option<u32>>,
    is_deleted: Cell<bool>,
    status: Cell<u32>,
    #[ignore_heap_size_of = "Defined in ipc-channel"]
    renderer: IpcSender<CanvasMsg>,

    // The attachment points for textures and renderbuffers on this
    // FBO.
    color: DOMRefCell<Option<WebGLFramebufferAttachment>>,
    depth: DOMRefCell<Option<WebGLFramebufferAttachment>>,
    stencil: DOMRefCell<Option<WebGLFramebufferAttachment>>,
    depthstencil: DOMRefCell<Option<WebGLFramebufferAttachment>>,
}

impl WebGLFramebuffer {
    fn new_inherited(renderer: IpcSender<CanvasMsg>,
                     id: WebGLFramebufferId)
                     -> WebGLFramebuffer {
        WebGLFramebuffer {
            webgl_object: WebGLObject::new_inherited(),
            id: id,
            target: Cell::new(None),
            is_deleted: Cell::new(false),
            renderer: renderer,
            status: Cell::new(constants::FRAMEBUFFER_UNSUPPORTED),
            color: DOMRefCell::new(None),
            depth: DOMRefCell::new(None),
            stencil: DOMRefCell::new(None),
            depthstencil: DOMRefCell::new(None),
        }
    }

    pub fn maybe_new(global: &GlobalScope, renderer: IpcSender<CanvasMsg>)
                     -> Option<Root<WebGLFramebuffer>> {
        let (sender, receiver) = ipc::channel().unwrap();
        renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateFramebuffer(sender))).unwrap();

        let result = receiver.recv().unwrap();
        result.map(|fb_id| WebGLFramebuffer::new(global, renderer, fb_id))
    }

    pub fn new(global: &GlobalScope,
               renderer: IpcSender<CanvasMsg>,
               id: WebGLFramebufferId)
               -> Root<WebGLFramebuffer> {
        reflect_dom_object(box WebGLFramebuffer::new_inherited(renderer, id),
                           global,
                           WebGLFramebufferBinding::Wrap)
    }
}

impl WebGLFramebuffer {
    pub fn id(&self) -> WebGLFramebufferId {
        self.id
    }

    pub fn bind(&self, target: u32) {
        // Update the framebuffer status on binding.  It may have
        // changed if its attachments were resized or deleted while
        // we've been unbound.
        self.update_status();

        self.target.set(Some(target));
        let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id));
        self.renderer.send(CanvasMsg::WebGL(cmd)).unwrap();
    }

    pub fn delete(&self) {
        if !self.is_deleted.get() {
            self.is_deleted.set(true);
            let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteFramebuffer(self.id)));
        }
    }

    pub fn is_deleted(&self) -> bool {
        self.is_deleted.get()
    }

    fn update_status(&self) {
        let has_c = self.color.borrow().is_some();
        let has_z = self.depth.borrow().is_some();
        let has_s = self.stencil.borrow().is_some();
        let has_zs = self.depthstencil.borrow().is_some();

        // From the WebGL spec, 6.6 ("Framebuffer Object Attachments"):
        //
        //    "In the WebGL API, it is an error to concurrently attach
        //     renderbuffers to the following combinations of
        //     attachment points:
        //
        //     DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
        //     STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
        //     DEPTH_ATTACHMENT + STENCIL_ATTACHMENT
        //
        //     If any of the constraints above are violated, then:
        //
        //     checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED."
        if (has_zs && (has_z || has_s)) ||
            (has_z && has_s) {
            self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
            return;
        }

        if has_c || has_z || has_zs || has_s {
            self.status.set(constants::FRAMEBUFFER_COMPLETE);
        } else {
            self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
        }
    }

    pub fn check_status(&self) -> u32 {
        return self.status.get();
    }

    pub fn renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()> {
        let binding = match attachment {
            constants::COLOR_ATTACHMENT0 => &self.color,
            constants::DEPTH_ATTACHMENT => &self.depth,
            constants::STENCIL_ATTACHMENT => &self.stencil,
            constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
            _ => return Err(WebGLError::InvalidEnum),
        };

        let rb_id = match rb {
            Some(rb) => {
                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Renderbuffer(JS::from_ref(rb)));
                Some(rb.id())
            }

            _ => {
                *binding.borrow_mut() = None;
                None
            }
        };

        self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferRenderbuffer(constants::FRAMEBUFFER,
                                                                                attachment,
                                                                                constants::RENDERBUFFER,
                                                                                rb_id))).unwrap();

        self.update_status();
        Ok(())
    }

    pub fn texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>,
                     level: i32) -> WebGLResult<()> {
        let binding = match attachment {
            constants::COLOR_ATTACHMENT0 => &self.color,
            constants::DEPTH_ATTACHMENT => &self.depth,
            constants::STENCIL_ATTACHMENT => &self.stencil,
            constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
            _ => return Err(WebGLError::InvalidEnum),
        };

        let tex_id = match texture {
            // Note, from the GLES 2.0.25 spec, page 113:
            //      "If texture is zero, then textarget and level are ignored."
            Some(texture) => {
                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture(JS::from_ref(texture)));

                // From the GLES 2.0.25 spec, page 113:
                //
                //     "level specifies the mipmap level of the texture image
                //      to be attached to the framebuffer and must be
                //      0. Otherwise, INVALID_VALUE is generated."
                if level != 0 {
                    return Err(WebGLError::InvalidValue);
                }

                //     "If texture is not zero, then texture must either
                //      name an existing texture object with an target of
                //      textarget, or texture must name an existing cube
                //      map texture and textarget must be one of:
                //      TEXTURE_CUBE_MAP_POSITIVE_X,
                //      TEXTURE_CUBE_MAP_POSITIVE_Y,
                //      TEXTURE_CUBE_MAP_POSITIVE_Z,
                //      TEXTURE_CUBE_MAP_NEGATIVE_X,
                //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
                //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
                //      INVALID_OPERATION is generated."
                let is_cube = match textarget {
                    constants::TEXTURE_2D => false,

                    constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
                    constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
                    constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
                    constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
                    constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
                    constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,

                    _ => return Err(WebGLError::InvalidEnum),
                };

                match texture.target() {
                    Some(constants::TEXTURE_CUBE_MAP) if is_cube => {}
                    Some(_) if !is_cube => {}
                    _ => return Err(WebGLError::InvalidOperation),
                }

                Some(texture.id())
            }

            _ => {
                *binding.borrow_mut() = None;
                self.update_status();
                None
            }
        };

        self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferTexture2D(constants::FRAMEBUFFER,
                                                                               attachment,
                                                                               textarget,
                                                                               tex_id,
                                                                               level))).unwrap();

        self.update_status();
        Ok(())
    }

    pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) {
        let attachments = [&self.color,
                           &self.depth,
                           &self.stencil,
                           &self.depthstencil];

        for attachment in &attachments {
            let matched = {
                match *attachment.borrow() {
                    Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb))
                        if rb.id() == att_rb.id() => true,
                    _ => false,
                }
            };

            if matched {
                *attachment.borrow_mut() = None;
                self.update_status();
            }
        }
    }

    pub fn detach_texture(&self, texture: &WebGLTexture) {
        let attachments = [&self.color,
                           &self.depth,
                           &self.stencil,
                           &self.depthstencil];

        for attachment in &attachments {
            let matched = {
                match *attachment.borrow() {
                    Some(WebGLFramebufferAttachment::Texture(ref att_texture))
                        if texture.id() == att_texture.id() => true,
                    _ => false,
                }
            };

            if matched {
                *attachment.borrow_mut() = None;
            }
        }
    }

    pub fn target(&self) -> Option<u32> {
        self.target.get()
    }
}

impl Drop for WebGLFramebuffer {
    fn drop(&mut self) {
        self.delete();
    }
}
<commit_msg>webgl: Track the level with texture attachments.<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/. */

// https://www.khronos.org/registry/webgl/specs/latest/1.0/webgl.idl
use canvas_traits::CanvasMsg;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::WebGLFramebufferBinding;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use dom::bindings::js::{HeapGCValue, JS, Root};
use dom::bindings::reflector::reflect_dom_object;
use dom::globalscope::GlobalScope;
use dom::webglobject::WebGLObject;
use dom::webglrenderbuffer::WebGLRenderbuffer;
use dom::webgltexture::WebGLTexture;
use ipc_channel::ipc::{self, IpcSender};
use std::cell::Cell;
use webrender_traits::{WebGLCommand, WebGLFramebufferBindingRequest, WebGLFramebufferId, WebGLResult, WebGLError};

#[must_root]
#[derive(JSTraceable, Clone, HeapSizeOf)]
enum WebGLFramebufferAttachment {
    Renderbuffer(JS<WebGLRenderbuffer>),
    Texture { texture: JS<WebGLTexture>, level: i32 },
}

impl HeapGCValue for WebGLFramebufferAttachment {}

#[dom_struct]
pub struct WebGLFramebuffer {
    webgl_object: WebGLObject,
    id: WebGLFramebufferId,
    /// target can only be gl::FRAMEBUFFER at the moment
    target: Cell<Option<u32>>,
    is_deleted: Cell<bool>,
    status: Cell<u32>,
    #[ignore_heap_size_of = "Defined in ipc-channel"]
    renderer: IpcSender<CanvasMsg>,

    // The attachment points for textures and renderbuffers on this
    // FBO.
    color: DOMRefCell<Option<WebGLFramebufferAttachment>>,
    depth: DOMRefCell<Option<WebGLFramebufferAttachment>>,
    stencil: DOMRefCell<Option<WebGLFramebufferAttachment>>,
    depthstencil: DOMRefCell<Option<WebGLFramebufferAttachment>>,
}

impl WebGLFramebuffer {
    fn new_inherited(renderer: IpcSender<CanvasMsg>,
                     id: WebGLFramebufferId)
                     -> WebGLFramebuffer {
        WebGLFramebuffer {
            webgl_object: WebGLObject::new_inherited(),
            id: id,
            target: Cell::new(None),
            is_deleted: Cell::new(false),
            renderer: renderer,
            status: Cell::new(constants::FRAMEBUFFER_UNSUPPORTED),
            color: DOMRefCell::new(None),
            depth: DOMRefCell::new(None),
            stencil: DOMRefCell::new(None),
            depthstencil: DOMRefCell::new(None),
        }
    }

    pub fn maybe_new(global: &GlobalScope, renderer: IpcSender<CanvasMsg>)
                     -> Option<Root<WebGLFramebuffer>> {
        let (sender, receiver) = ipc::channel().unwrap();
        renderer.send(CanvasMsg::WebGL(WebGLCommand::CreateFramebuffer(sender))).unwrap();

        let result = receiver.recv().unwrap();
        result.map(|fb_id| WebGLFramebuffer::new(global, renderer, fb_id))
    }

    pub fn new(global: &GlobalScope,
               renderer: IpcSender<CanvasMsg>,
               id: WebGLFramebufferId)
               -> Root<WebGLFramebuffer> {
        reflect_dom_object(box WebGLFramebuffer::new_inherited(renderer, id),
                           global,
                           WebGLFramebufferBinding::Wrap)
    }
}

impl WebGLFramebuffer {
    pub fn id(&self) -> WebGLFramebufferId {
        self.id
    }

    pub fn bind(&self, target: u32) {
        // Update the framebuffer status on binding.  It may have
        // changed if its attachments were resized or deleted while
        // we've been unbound.
        self.update_status();

        self.target.set(Some(target));
        let cmd = WebGLCommand::BindFramebuffer(target, WebGLFramebufferBindingRequest::Explicit(self.id));
        self.renderer.send(CanvasMsg::WebGL(cmd)).unwrap();
    }

    pub fn delete(&self) {
        if !self.is_deleted.get() {
            self.is_deleted.set(true);
            let _ = self.renderer.send(CanvasMsg::WebGL(WebGLCommand::DeleteFramebuffer(self.id)));
        }
    }

    pub fn is_deleted(&self) -> bool {
        self.is_deleted.get()
    }

    fn update_status(&self) {
        let has_c = self.color.borrow().is_some();
        let has_z = self.depth.borrow().is_some();
        let has_s = self.stencil.borrow().is_some();
        let has_zs = self.depthstencil.borrow().is_some();

        // From the WebGL spec, 6.6 ("Framebuffer Object Attachments"):
        //
        //    "In the WebGL API, it is an error to concurrently attach
        //     renderbuffers to the following combinations of
        //     attachment points:
        //
        //     DEPTH_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
        //     STENCIL_ATTACHMENT + DEPTH_STENCIL_ATTACHMENT
        //     DEPTH_ATTACHMENT + STENCIL_ATTACHMENT
        //
        //     If any of the constraints above are violated, then:
        //
        //     checkFramebufferStatus must return FRAMEBUFFER_UNSUPPORTED."
        if (has_zs && (has_z || has_s)) ||
            (has_z && has_s) {
            self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
            return;
        }

        if has_c || has_z || has_zs || has_s {
            self.status.set(constants::FRAMEBUFFER_COMPLETE);
        } else {
            self.status.set(constants::FRAMEBUFFER_UNSUPPORTED);
        }
    }

    pub fn check_status(&self) -> u32 {
        return self.status.get();
    }

    pub fn renderbuffer(&self, attachment: u32, rb: Option<&WebGLRenderbuffer>) -> WebGLResult<()> {
        let binding = match attachment {
            constants::COLOR_ATTACHMENT0 => &self.color,
            constants::DEPTH_ATTACHMENT => &self.depth,
            constants::STENCIL_ATTACHMENT => &self.stencil,
            constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
            _ => return Err(WebGLError::InvalidEnum),
        };

        let rb_id = match rb {
            Some(rb) => {
                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Renderbuffer(JS::from_ref(rb)));
                Some(rb.id())
            }

            _ => {
                *binding.borrow_mut() = None;
                None
            }
        };

        self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferRenderbuffer(constants::FRAMEBUFFER,
                                                                                attachment,
                                                                                constants::RENDERBUFFER,
                                                                                rb_id))).unwrap();

        self.update_status();
        Ok(())
    }

    pub fn texture2d(&self, attachment: u32, textarget: u32, texture: Option<&WebGLTexture>,
                     level: i32) -> WebGLResult<()> {
        let binding = match attachment {
            constants::COLOR_ATTACHMENT0 => &self.color,
            constants::DEPTH_ATTACHMENT => &self.depth,
            constants::STENCIL_ATTACHMENT => &self.stencil,
            constants::DEPTH_STENCIL_ATTACHMENT => &self.depthstencil,
            _ => return Err(WebGLError::InvalidEnum),
        };

        let tex_id = match texture {
            // Note, from the GLES 2.0.25 spec, page 113:
            //      "If texture is zero, then textarget and level are ignored."
            Some(texture) => {
                *binding.borrow_mut() = Some(WebGLFramebufferAttachment::Texture {
                    texture: JS::from_ref(texture),
                    level: level }
                );

                // From the GLES 2.0.25 spec, page 113:
                //
                //     "level specifies the mipmap level of the texture image
                //      to be attached to the framebuffer and must be
                //      0. Otherwise, INVALID_VALUE is generated."
                if level != 0 {
                    return Err(WebGLError::InvalidValue);
                }

                //     "If texture is not zero, then texture must either
                //      name an existing texture object with an target of
                //      textarget, or texture must name an existing cube
                //      map texture and textarget must be one of:
                //      TEXTURE_CUBE_MAP_POSITIVE_X,
                //      TEXTURE_CUBE_MAP_POSITIVE_Y,
                //      TEXTURE_CUBE_MAP_POSITIVE_Z,
                //      TEXTURE_CUBE_MAP_NEGATIVE_X,
                //      TEXTURE_CUBE_MAP_NEGATIVE_Y, or
                //      TEXTURE_CUBE_MAP_NEGATIVE_Z. Otherwise,
                //      INVALID_OPERATION is generated."
                let is_cube = match textarget {
                    constants::TEXTURE_2D => false,

                    constants::TEXTURE_CUBE_MAP_POSITIVE_X => true,
                    constants::TEXTURE_CUBE_MAP_POSITIVE_Y => true,
                    constants::TEXTURE_CUBE_MAP_POSITIVE_Z => true,
                    constants::TEXTURE_CUBE_MAP_NEGATIVE_X => true,
                    constants::TEXTURE_CUBE_MAP_NEGATIVE_Y => true,
                    constants::TEXTURE_CUBE_MAP_NEGATIVE_Z => true,

                    _ => return Err(WebGLError::InvalidEnum),
                };

                match texture.target() {
                    Some(constants::TEXTURE_CUBE_MAP) if is_cube => {}
                    Some(_) if !is_cube => {}
                    _ => return Err(WebGLError::InvalidOperation),
                }

                Some(texture.id())
            }

            _ => {
                *binding.borrow_mut() = None;
                self.update_status();
                None
            }
        };

        self.renderer.send(CanvasMsg::WebGL(WebGLCommand::FramebufferTexture2D(constants::FRAMEBUFFER,
                                                                               attachment,
                                                                               textarget,
                                                                               tex_id,
                                                                               level))).unwrap();

        self.update_status();
        Ok(())
    }

    pub fn detach_renderbuffer(&self, rb: &WebGLRenderbuffer) {
        let attachments = [&self.color,
                           &self.depth,
                           &self.stencil,
                           &self.depthstencil];

        for attachment in &attachments {
            let matched = {
                match *attachment.borrow() {
                    Some(WebGLFramebufferAttachment::Renderbuffer(ref att_rb))
                        if rb.id() == att_rb.id() => true,
                    _ => false,
                }
            };

            if matched {
                *attachment.borrow_mut() = None;
                self.update_status();
            }
        }
    }

    pub fn detach_texture(&self, texture: &WebGLTexture) {
        let attachments = [&self.color,
                           &self.depth,
                           &self.stencil,
                           &self.depthstencil];

        for attachment in &attachments {
            let matched = {
                match *attachment.borrow() {
                    Some(WebGLFramebufferAttachment::Texture { texture: ref att_texture, .. })
                        if texture.id() == att_texture.id() => true,
                    _ => false,
                }
            };

            if matched {
                *attachment.borrow_mut() = None;
            }
        }
    }

    pub fn target(&self) -> Option<u32> {
        self.target.get()
    }
}

impl Drop for WebGLFramebuffer {
    fn drop(&mut self) {
        self.delete();
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::io::Read;

use hyper::status::StatusCode;
use rustc_serialize::json;
use url::{ParseError, Url};
use url::form_urlencoded;

use options::{ComparisonConditions, DeleteOptions, SetOptions};
use error::Error;
use http;
use query_pairs::UrlWithQueryPairs;
use response::{EtcdResult, LeaderStats};

/// API client for etcd.
#[derive(Debug)]
pub struct Client {
    root_url: String,
}

impl Client {
    /// Constructs a new client.
    pub fn new(root_url: &str) -> Result<Client, ParseError> {
        let url = try!(Url::parse(root_url));
        let client = Client {
            root_url: format!("{}", url.serialize()),
        };

        Ok(client)
    }

    /// Constructs a client that will connect to http://127.0.0.1:2379/.
    pub fn default() -> Client {
        Client {
            root_url: "http://127.0.0.1:2379/".to_string(),
        }
    }

    /// Deletes a key only if the given current value and/or current modified index match.
    ///
    /// # Failures
    ///
    /// Fails if the conditions didn't match or if no conditions were given.
    pub fn compare_and_delete(
        &self,
        key: &str,
        current_value: Option<&str>,
        current_modified_index: Option<u64>
    ) -> EtcdResult {
        self.raw_delete(
            key,
            DeleteOptions {
                conditions: Some(ComparisonConditions {
                    value: current_value,
                    modified_index: current_modified_index,
                }),
                ..Default::default()
            }
        )
    }

    /// Updates the value of a key only if the given current value and/or current modified index
    /// match.
    ///
    /// # Failures
    ///
    /// Fails if the conditions didn't match or if no conditions were given.
    pub fn compare_and_swap(
        &self,
        key: &str,
        value: &str,
        ttl: Option<u64>,
        current_value: Option<&str>,
        current_modified_index: Option<u64>,
    ) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                conditions: Some(ComparisonConditions {
                    value: current_value,
                    modified_index: current_modified_index,
                }),
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Creates a new file at the given key with the given value and time to live in seconds.
    ///
    /// # Failures
    ///
    /// Fails if the key already exists.
    pub fn create(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                prev_exist: Some(false),
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Creates a new empty directory at the given key with the given time to live in seconds.
    ///
    /// # Failures
    ///
    /// Fails if the key already exists.
    pub fn create_dir(&self, key: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                dir: Some(true),
                prev_exist: Some(false),
                ttl: ttl,
                ..Default::default()
            },
        )
    }

    /// Creates a new file in the given directory with the given value and time to live in seconds
    /// with a key name guaranteed to be greater than all existing keys in the directory.
    ///
    /// # Failures
    ///
    /// Fails if the key already exists and is not a directory.
    pub fn create_in_order(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                create_in_order: true,
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Deletes a file or directory at the given key. If `recursive` is `true` and the key is a
    /// directory, the directory and all child files and directories will be deleted.
    ///
    /// # Failures
    ///
    /// Fails if the key is a directory and `recursive` is `false`.
    pub fn delete(&self, key: &str, recursive: bool) -> EtcdResult {
        self.raw_delete(
            key,
            DeleteOptions {
                recursive: Some(recursive),
                ..Default::default()
            }
        )
    }

    /// Deletes an empty directory or a file at the given key.
    ///
    /// # Failures
    ///
    /// Fails if the directory is not empty.
    pub fn delete_dir(&self, key: &str) -> EtcdResult {
        self.raw_delete(
            key,
            DeleteOptions {
                dir: Some(true),
                ..Default::default()
            }
        )
    }

    /// Gets the value of a key. If the key is a directory, `sort` will determine whether the
    /// contents of the directory are returned in a sorted order. If the key is a directory and
    /// `recursive` is `true`, the contents of child directories will be returned as well.
    pub fn get(&self, key: &str, sort: bool, recursive: bool) -> EtcdResult {
        let mut query_pairs = HashMap::new();

        query_pairs.insert("sorted", format!("{}", sort));
        query_pairs.insert("recursive", format!("{}", recursive));

        let url = UrlWithQueryPairs {
            pairs: &query_pairs,
            url: self.build_url(key),
        }.parse();

        let mut response = try!(http::get(format!("{}", url)));
        let mut response_body = String::new();
        response.read_to_string(&mut response_body).unwrap();

        match response.status {
            StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap())),
        }
    }

    /// Sets the key to the given value with the given time to live in seconds. Any previous value
    /// and TTL will be replaced.
    ///
    /// # Failures
    ///
    /// Fails if the key is a directory.
    pub fn set(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Sets the key to an empty directory with the given time to live in seconds. An existing file
    /// will be replaced, but an existing directory will not.
    ///
    /// # Failures
    ///
    /// Fails if the key is an existing directory.
    pub fn set_dir(&self, key: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                dir: Some(true),
                ttl: ttl,
                ..Default::default()
            },
        )
    }

    /// Updates the given key to the given value and time to live in seconds.
    ///
    /// # Failures
    ///
    /// Fails if the key does not exist.
    pub fn update(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                prev_exist: Some(true),
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Updates the given key to a directory with the given time to live in seconds. If the
    /// directory already existed, only the TTL is updated. If the key was a file, its value is
    /// removed and its TTL is updated.
    ///
    /// # Failures
    ///
    /// Fails if the key does not exist.
    pub fn update_dir(&self, key: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                dir: Some(true),
                prev_exist: Some(true),
                ttl: ttl,
                ..Default::default()
            },
        )
    }

    /// Returns statistics on the leader member of a cluster.
    ///
    /// # Failures
    ///
    /// Fails if JSON decoding fails, which suggests a bug in our schema.
    pub fn leader_stats(&self) -> Result<LeaderStats, Error> {
        let url = format!("{}v2/stats/leader", self.root_url);
        let mut response = try!(http::get(url));
        let mut response_body = String::new();
        try!(response.read_to_string(&mut response_body));

        match response.status {
            StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap()))
        }
    }

    // private

    /// Constructs the full URL for an API call.
    fn build_url(&self, path: &str) -> String {
        format!("{}v2/keys{}", self.root_url, path)
    }

    /// Handles all delete operations.
    fn raw_delete(
        &self,
        key: &str,
        options: DeleteOptions,
    ) -> EtcdResult {
        let mut query_pairs = HashMap::new();

        if options.recursive.is_some() {
            query_pairs.insert("recursive", format!("{}", options.recursive.unwrap()));
        }

        if options.dir.is_some() {
            query_pairs.insert("dir", format!("{}", options.dir.unwrap()));
        }

        if options.conditions.is_some() {
            let conditions = options.conditions.unwrap();

            if conditions.is_empty() {
                return Err(
                    Error::InvalidConditions("Current value or modified index is required.")
                );
            }

            if conditions.modified_index.is_some() {
              query_pairs.insert("prevIndex", format!("{}", conditions.modified_index.unwrap()));
            }

            if conditions.value.is_some() {
                query_pairs.insert("prevValue", conditions.value.unwrap().to_string());
            }
        }

        let url = UrlWithQueryPairs {
            pairs: &query_pairs,
            url: self.build_url(key),
        }.parse();

        let mut response = try!(http::delete(format!("{}", url)));
        let mut response_body = String::new();
        response.read_to_string(&mut response_body).unwrap();

        match response.status {
            StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap())),
        }
    }

    /// Handles all set operations.
    fn raw_set(
        &self,
        key: &str,
        options: SetOptions,
    ) -> EtcdResult {
        let url = self.build_url(key);
        let mut http_options = vec![];

        if options.value.is_some() {
            http_options.push(("value".to_string(), options.value.unwrap().to_string()));
        }

        if options.ttl.is_some() {
            http_options.push(("ttl".to_string(), format!("{}", options.ttl.unwrap())));
        }

        if options.dir.is_some() {
            http_options.push(("dir".to_string(), format!("{}", options.dir.unwrap())));
        }

        if options.prev_exist.is_some() {
            http_options.push(
                ("prevExist".to_string(), format!("{}", options.prev_exist.unwrap()))
            );
        }

        if options.conditions.is_some() {
            let conditions = options.conditions.unwrap();

            if conditions.is_empty() {
                return Err(
                    Error::InvalidConditions("Current value or modified index is required.")
                );
            }

            if conditions.modified_index.is_some() {
                http_options.push(
                    ("prevIndex".to_string(), format!("{}", conditions.modified_index.unwrap()))
                );
            }

            if conditions.value.is_some() {
                http_options.push(("prevValue".to_string(), conditions.value.unwrap().to_string()));
            }
        }

        let body = form_urlencoded::serialize(&http_options);

        let mut response = if options.create_in_order {
            try!(http::post(url, body))
        } else {
            try!(http::put(url, body))
        };

        let mut response_body = String::new();
        response.read_to_string(&mut response_body).unwrap();

        match response.status {
            StatusCode::Created | StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap())),
        }
    }
}
<commit_msg>Use "key-value pair" instead of "file" in docs.<commit_after>use std::collections::HashMap;
use std::io::Read;

use hyper::status::StatusCode;
use rustc_serialize::json;
use url::{ParseError, Url};
use url::form_urlencoded;

use options::{ComparisonConditions, DeleteOptions, SetOptions};
use error::Error;
use http;
use query_pairs::UrlWithQueryPairs;
use response::{EtcdResult, LeaderStats};

/// API client for etcd.
#[derive(Debug)]
pub struct Client {
    root_url: String,
}

impl Client {
    /// Constructs a new client.
    pub fn new(root_url: &str) -> Result<Client, ParseError> {
        let url = try!(Url::parse(root_url));
        let client = Client {
            root_url: format!("{}", url.serialize()),
        };

        Ok(client)
    }

    /// Constructs a client that will connect to http://127.0.0.1:2379/.
    pub fn default() -> Client {
        Client {
            root_url: "http://127.0.0.1:2379/".to_string(),
        }
    }

    /// Deletes a key only if the given current value and/or current modified index match.
    ///
    /// # Failures
    ///
    /// Fails if the conditions didn't match or if no conditions were given.
    pub fn compare_and_delete(
        &self,
        key: &str,
        current_value: Option<&str>,
        current_modified_index: Option<u64>
    ) -> EtcdResult {
        self.raw_delete(
            key,
            DeleteOptions {
                conditions: Some(ComparisonConditions {
                    value: current_value,
                    modified_index: current_modified_index,
                }),
                ..Default::default()
            }
        )
    }

    /// Updates the value of a key only if the given current value and/or current modified index
    /// match.
    ///
    /// # Failures
    ///
    /// Fails if the conditions didn't match or if no conditions were given.
    pub fn compare_and_swap(
        &self,
        key: &str,
        value: &str,
        ttl: Option<u64>,
        current_value: Option<&str>,
        current_modified_index: Option<u64>,
    ) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                conditions: Some(ComparisonConditions {
                    value: current_value,
                    modified_index: current_modified_index,
                }),
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Creates a new key-value pair with any given time to live in seconds.
    ///
    /// # Failures
    ///
    /// Fails if the key already exists.
    pub fn create(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                prev_exist: Some(false),
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Creates a new empty directory at the given key with the given time to live in seconds.
    ///
    /// # Failures
    ///
    /// Fails if the key already exists.
    pub fn create_dir(&self, key: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                dir: Some(true),
                prev_exist: Some(false),
                ttl: ttl,
                ..Default::default()
            },
        )
    }

    /// Creates a new key-value pair in the given directory with any given time to live in seconds
    /// and a key name guaranteed to be greater than all existing keys in the directory.
    ///
    /// # Failures
    ///
    /// Fails if the key already exists and is not a directory.
    pub fn create_in_order(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                create_in_order: true,
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Deletes a key-value pair or directory. If `recursive` is `true` and the key is a
    /// directory, the directory and all child key-value pairs and directories will be deleted.
    ///
    /// # Failures
    ///
    /// Fails if the key is a directory and `recursive` is `false`.
    pub fn delete(&self, key: &str, recursive: bool) -> EtcdResult {
        self.raw_delete(
            key,
            DeleteOptions {
                recursive: Some(recursive),
                ..Default::default()
            }
        )
    }

    /// Deletes an empty directory or a key-value pair at the given key.
    ///
    /// # Failures
    ///
    /// Fails if the directory is not empty.
    pub fn delete_dir(&self, key: &str) -> EtcdResult {
        self.raw_delete(
            key,
            DeleteOptions {
                dir: Some(true),
                ..Default::default()
            }
        )
    }

    /// Gets the value of a key. If the key is a directory, `sort` will determine whether the
    /// contents of the directory are returned in a sorted order. If the key is a directory and
    /// `recursive` is `true`, the contents of child directories will be returned as well.
    pub fn get(&self, key: &str, sort: bool, recursive: bool) -> EtcdResult {
        let mut query_pairs = HashMap::new();

        query_pairs.insert("sorted", format!("{}", sort));
        query_pairs.insert("recursive", format!("{}", recursive));

        let url = UrlWithQueryPairs {
            pairs: &query_pairs,
            url: self.build_url(key),
        }.parse();

        let mut response = try!(http::get(format!("{}", url)));
        let mut response_body = String::new();
        response.read_to_string(&mut response_body).unwrap();

        match response.status {
            StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap())),
        }
    }

    /// Sets the key to the given value with the given time to live in seconds. Any previous value
    /// and TTL will be replaced.
    ///
    /// # Failures
    ///
    /// Fails if the key is a directory.
    pub fn set(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Sets the key to an empty directory with the given time to live in seconds. An existing
    /// key-value will be replaced, but an existing directory will not.
    ///
    /// # Failures
    ///
    /// Fails if the key is an existing directory.
    pub fn set_dir(&self, key: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                dir: Some(true),
                ttl: ttl,
                ..Default::default()
            },
        )
    }

    /// Updates the given key to the given value and time to live in seconds.
    ///
    /// # Failures
    ///
    /// Fails if the key does not exist.
    pub fn update(&self, key: &str, value: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                prev_exist: Some(true),
                ttl: ttl,
                value: Some(value),
                ..Default::default()
            },
        )
    }

    /// Updates the given key to a directory with the given time to live in seconds. If the
    /// directory already existed, only the TTL is updated. If the key was a key-value pair, its
    /// value is removed and its TTL is updated.
    ///
    /// # Failures
    ///
    /// Fails if the key does not exist.
    pub fn update_dir(&self, key: &str, ttl: Option<u64>) -> EtcdResult {
        self.raw_set(
            key,
            SetOptions {
                dir: Some(true),
                prev_exist: Some(true),
                ttl: ttl,
                ..Default::default()
            },
        )
    }

    /// Returns statistics on the leader member of a cluster.
    ///
    /// # Failures
    ///
    /// Fails if JSON decoding fails, which suggests a bug in our schema.
    pub fn leader_stats(&self) -> Result<LeaderStats, Error> {
        let url = format!("{}v2/stats/leader", self.root_url);
        let mut response = try!(http::get(url));
        let mut response_body = String::new();
        try!(response.read_to_string(&mut response_body));

        match response.status {
            StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap()))
        }
    }

    // private

    /// Constructs the full URL for an API call.
    fn build_url(&self, path: &str) -> String {
        format!("{}v2/keys{}", self.root_url, path)
    }

    /// Handles all delete operations.
    fn raw_delete(
        &self,
        key: &str,
        options: DeleteOptions,
    ) -> EtcdResult {
        let mut query_pairs = HashMap::new();

        if options.recursive.is_some() {
            query_pairs.insert("recursive", format!("{}", options.recursive.unwrap()));
        }

        if options.dir.is_some() {
            query_pairs.insert("dir", format!("{}", options.dir.unwrap()));
        }

        if options.conditions.is_some() {
            let conditions = options.conditions.unwrap();

            if conditions.is_empty() {
                return Err(
                    Error::InvalidConditions("Current value or modified index is required.")
                );
            }

            if conditions.modified_index.is_some() {
              query_pairs.insert("prevIndex", format!("{}", conditions.modified_index.unwrap()));
            }

            if conditions.value.is_some() {
                query_pairs.insert("prevValue", conditions.value.unwrap().to_string());
            }
        }

        let url = UrlWithQueryPairs {
            pairs: &query_pairs,
            url: self.build_url(key),
        }.parse();

        let mut response = try!(http::delete(format!("{}", url)));
        let mut response_body = String::new();
        response.read_to_string(&mut response_body).unwrap();

        match response.status {
            StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap())),
        }
    }

    /// Handles all set operations.
    fn raw_set(
        &self,
        key: &str,
        options: SetOptions,
    ) -> EtcdResult {
        let url = self.build_url(key);
        let mut http_options = vec![];

        if options.value.is_some() {
            http_options.push(("value".to_string(), options.value.unwrap().to_string()));
        }

        if options.ttl.is_some() {
            http_options.push(("ttl".to_string(), format!("{}", options.ttl.unwrap())));
        }

        if options.dir.is_some() {
            http_options.push(("dir".to_string(), format!("{}", options.dir.unwrap())));
        }

        if options.prev_exist.is_some() {
            http_options.push(
                ("prevExist".to_string(), format!("{}", options.prev_exist.unwrap()))
            );
        }

        if options.conditions.is_some() {
            let conditions = options.conditions.unwrap();

            if conditions.is_empty() {
                return Err(
                    Error::InvalidConditions("Current value or modified index is required.")
                );
            }

            if conditions.modified_index.is_some() {
                http_options.push(
                    ("prevIndex".to_string(), format!("{}", conditions.modified_index.unwrap()))
                );
            }

            if conditions.value.is_some() {
                http_options.push(("prevValue".to_string(), conditions.value.unwrap().to_string()));
            }
        }

        let body = form_urlencoded::serialize(&http_options);

        let mut response = if options.create_in_order {
            try!(http::post(url, body))
        } else {
            try!(http::put(url, body))
        };

        let mut response_body = String::new();
        response.read_to_string(&mut response_body).unwrap();

        match response.status {
            StatusCode::Created | StatusCode::Ok => Ok(json::decode(&response_body).unwrap()),
            _ => Err(Error::Etcd(json::decode(&response_body).unwrap())),
        }
    }
}
<|endoftext|>


<commit_before>use std::net::{UdpSocket, SocketAddr};
use std::io::Error;
use std::str::FromStr;
// use std::time::Duration;

/// Client socket for statsd servers.
///
/// After creating a metric you can use `Client`
/// to send metrics to the configured statsd server
///
/// # Example
///
/// Creating a client and sending metrics is easy.
///
/// ```ignore
/// use statsd::client::Client;
///
/// let client = Client::new("127.0.0.1:8125", "myapp.");
/// client.incr("some.metric.completed");
/// ```
pub struct Client {
    socket: UdpSocket,
    server_addr: SocketAddr,
    prefix: String
}

impl Client {
    /// Construct a new statsd client given an host/port & prefix
    pub fn new(host: &str, prefix: &str) -> Result<Client, Error> {
        let addr = SocketAddr::from_str(host).ok().unwrap();
        let mut socket = try!(UdpSocket::bind(addr));
        Ok(Client {socket: socket, prefix: prefix.to_string(), server_addr: addr})
    }

    /// Increment a metric by 1
    ///
    /// ```ignore
    /// # Increment a given metric by 1.
    /// client.incr("metric.completed");
    /// ```
    ///
    /// This modifies a counter with an effective sampling
    /// rate of 1.0.
    pub fn incr(&mut self, metric: &str) {
        self.count(metric, 1.0);
    }

    /// Decrement a metric by -1
    ///
    /// ```ignore
    /// # Decrement a given metric by 1
    /// client.decr("metric.completed");
    /// ```
    ///
    /// This modifies a counter with an effective sampling
    /// rate of 1.0.
    pub fn decr(&mut self, metric: &str) {
        self.count(metric, -1.0);
    }

    /// Modify a counter by `value`.
    ///
    /// Will increment or decrement a counter by `value` with
    /// a sampling rate of 1.0.
    ///
    /// ```ignore
    /// # Increment by 12
    /// client.count("metric.completed", 12);
    /// ```
    pub fn count(&mut self, metric: &str, value: f64) {
        let data = format!("{}.{}:{}|c", self.prefix, metric, value);
        self.send(data);
    }

    /// Modify a counter by `value` only x% of the time.
    ///
    /// Will increment or decrement a counter by `value` with
    /// a custom sampling rate.
    ///
    ///
    /// ```ignore
    /// # Increment by 4 50% of the time.
    /// client.sampled_count("metric.completed", 4, 0.5);
    /// ```
    pub fn sampled_count(&mut self, metric: &str, value: f64, rate: f64) {
        /* TODO figure out how to generate random numbers properly.
        if f64::rand() < rate {
            return
        }
        let data = format!("{}.{}:{}|c", self.prefix, metric, value);
        self.send(data);
        */
    }

    /// Set a gauge value.
    ///
    /// ```ignore
    /// # set a gauge to 9001
    /// client.gauge("power_level.observed", 9001);
    /// ```
    pub fn gauge(&mut self, metric: &str, value: f64) {
        let data = format!("{}.{}:{}|g", self.prefix, metric, value);
        self.send(data);
    }

    /// Send a timer value.
    ///
    /// The value is expected to be in ms.
    ///
    /// ```ignore
    /// # pass a duration value
    /// client.timer("response.duration", 10.123);
    /// ```
    pub fn timer(&mut self, metric: &str, value: f64) {
        let data = format!("{}.{}:{}|ms", self.prefix, metric, value);
        self.send(data);
    }

    /*
     TODO this relies on unstable API's - figure out how to disable the warnings.
    /// Time a block of code.
    ///
    /// The passed closure will be timed and executed. The block's
    /// duration will be sent as a metric.
    ///
    /// ```ignore
    /// # pass a duration value
    /// client.time("response.duration", || {
    ///   # Your code here.
    /// });
    /// ```
    pub fn time<F>(&mut self, metric: &str, callable: F) where F : Fn() {
        let duration = {
            Duration::span(callable)
        };
        let data = format!("{}.{}:{}|ms", self.prefix, metric, duration.num_seconds());
        self.send(data);
    }
    */

    /// Send data along the UDP socket.
    fn send(&mut self, data: String) {
        let _ = self.socket.send_to(data.as_bytes(), self.server_addr);
    }
}
<commit_msg>Client works and sends metrics.<commit_after>use std::net::{UdpSocket, SocketAddr};
use std::io::Error;
use std::str::FromStr;
// use std::time::Duration;

/// Client socket for statsd servers.
///
/// After creating a metric you can use `Client`
/// to send metrics to the configured statsd server
///
/// # Example
///
/// Creating a client and sending metrics is easy.
///
/// ```ignore
/// use statsd::client::Client;
///
/// let client = Client::new("127.0.0.1:8125", "myapp.");
/// client.incr("some.metric.completed");
/// ```
pub struct Client {
    socket: UdpSocket,
    server_address: SocketAddr,
    prefix: String
}

impl Client {
    /// Construct a new statsd client given an host/port & prefix
    pub fn new(host: &str, prefix: &str) -> Result<Client, Error> {
        // Bind to a generic port as we'll only be writing on this
        // socket.
        let client_address = SocketAddr::from_str("0.0.0.0:0").unwrap();
        let mut socket = try!(UdpSocket::bind(client_address));

        let server_address = SocketAddr::from_str(host).ok().unwrap();
        Ok(Client {
            socket: socket,
            prefix: prefix.to_string(),
            server_address: server_address
        })
    }

    /// Increment a metric by 1
    ///
    /// ```ignore
    /// # Increment a given metric by 1.
    /// client.incr("metric.completed");
    /// ```
    ///
    /// This modifies a counter with an effective sampling
    /// rate of 1.0.
    pub fn incr(&mut self, metric: &str) {
        self.count(metric, 1.0);
    }

    /// Decrement a metric by -1
    ///
    /// ```ignore
    /// # Decrement a given metric by 1
    /// client.decr("metric.completed");
    /// ```
    ///
    /// This modifies a counter with an effective sampling
    /// rate of 1.0.
    pub fn decr(&mut self, metric: &str) {
        self.count(metric, -1.0);
    }

    /// Modify a counter by `value`.
    ///
    /// Will increment or decrement a counter by `value` with
    /// a sampling rate of 1.0.
    ///
    /// ```ignore
    /// # Increment by 12
    /// client.count("metric.completed", 12);
    /// ```
    pub fn count(&mut self, metric: &str, value: f64) {
        let data = format!("{}.{}:{}|c", self.prefix, metric, value);
        self.send(data);
    }

    /// Modify a counter by `value` only x% of the time.
    ///
    /// Will increment or decrement a counter by `value` with
    /// a custom sampling rate.
    ///
    ///
    /// ```ignore
    /// # Increment by 4 50% of the time.
    /// client.sampled_count("metric.completed", 4, 0.5);
    /// ```
    pub fn sampled_count(&mut self, metric: &str, value: f64, rate: f64) {
        /* TODO figure out how to generate random numbers properly.
        if f64::rand() < rate {
            return
        }
        let data = format!("{}.{}:{}|c", self.prefix, metric, value);
        self.send(data);
        */
    }

    /// Set a gauge value.
    ///
    /// ```ignore
    /// # set a gauge to 9001
    /// client.gauge("power_level.observed", 9001);
    /// ```
    pub fn gauge(&mut self, metric: &str, value: f64) {
        let data = format!("{}.{}:{}|g", self.prefix, metric, value);
        self.send(data);
    }

    /// Send a timer value.
    ///
    /// The value is expected to be in ms.
    ///
    /// ```ignore
    /// # pass a duration value
    /// client.timer("response.duration", 10.123);
    /// ```
    pub fn timer(&mut self, metric: &str, value: f64) {
        let data = format!("{}.{}:{}|ms", self.prefix, metric, value);
        self.send(data);
    }

    /*
     TODO this relies on unstable API's - figure out how to disable the warnings.
    /// Time a block of code.
    ///
    /// The passed closure will be timed and executed. The block's
    /// duration will be sent as a metric.
    ///
    /// ```ignore
    /// # pass a duration value
    /// client.time("response.duration", || {
    ///   # Your code here.
    /// });
    /// ```
    pub fn time<F>(&mut self, metric: &str, callable: F) where F : Fn() {
        let duration = {
            Duration::span(callable)
        };
        let data = format!("{}.{}:{}|ms", self.prefix, metric, duration.num_seconds());
        self.send(data);
    }
    */

    /// Send data along the UDP socket.
    fn send(&mut self, data: String) {
        let _ = self.socket.send_to(data.as_bytes(), self.server_address);
    }
}
<|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 https://mozilla.org/MPL/2.0/. */

use crate::replaced::IntrinsicSizes;
use euclid::{Size2D, Vector2D};
use style::computed_values::background_clip::single_value::T as Clip;
use style::computed_values::background_origin::single_value::T as Origin;
use style::values::computed::background::BackgroundSize as Size;
use style::values::computed::{Length, LengthPercentage};
use style::values::specified::background::BackgroundRepeat as RepeatXY;
use style::values::specified::background::BackgroundRepeatKeyword as Repeat;
use webrender_api::{self as wr, units};

pub(super) struct Layer {
    pub common: wr::CommonItemProperties,
    pub bounds: units::LayoutRect,
    pub tile_size: units::LayoutSize,
    pub tile_spacing: units::LayoutSize,
    pub repeat: bool,
}

struct Layout1DResult {
    repeat: bool,
    bounds_origin: f32,
    bounds_size: f32,
}

fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
    &values[layer_index % values.len()]
}

pub(super) fn layout_layer(
    fragment_builder: &mut super::BuilderForBoxFragment,
    builder: &mut super::DisplayListBuilder,
    layer_index: usize,
    intrinsic: IntrinsicSizes,
) -> Option<Layer> {
    let b = fragment_builder.fragment.style.get_background();

    let clipping_area = match get_cyclic(&b.background_clip.0, layer_index) {
        Clip::ContentBox => fragment_builder.content_rect(),
        Clip::PaddingBox => fragment_builder.padding_rect(),
        Clip::BorderBox => &fragment_builder.border_rect,
    };
    let positioning_area = match get_cyclic(&b.background_origin.0, layer_index) {
        Origin::ContentBox => fragment_builder.content_rect(),
        Origin::PaddingBox => fragment_builder.padding_rect(),
        Origin::BorderBox => &fragment_builder.border_rect,
    };

    // https://drafts.csswg.org/css-backgrounds/#background-size
    enum ContainOrCover {
        Contain,
        Cover,
    }
    let size_contain_or_cover = |background_size| {
        let mut tile_size = positioning_area.size;
        if let Some(intrinsic_ratio) = intrinsic.ratio {
            let positioning_ratio = positioning_area.size.width / positioning_area.size.height;
            // Whether the tile width (as opposed to height)
            // is scaled to that of the positioning area
            let fit_width = match background_size {
                ContainOrCover::Contain => positioning_ratio <= intrinsic_ratio,
                ContainOrCover::Cover => positioning_ratio > intrinsic_ratio,
            };
            // The other dimension needs to be adjusted
            if fit_width {
                tile_size.height = tile_size.width / intrinsic_ratio
            } else {
                tile_size.width = tile_size.height * intrinsic_ratio
            }
        }
        tile_size
    };
    let mut tile_size = match get_cyclic(&b.background_size.0, layer_index) {
        Size::Contain => size_contain_or_cover(ContainOrCover::Contain),
        Size::Cover => size_contain_or_cover(ContainOrCover::Cover),
        Size::ExplicitSize { width, height } => {
            let mut width = width.non_auto().map(|lp| {
                lp.0.percentage_relative_to(Length::new(positioning_area.size.width))
            });
            let mut height = height.non_auto().map(|lp| {
                lp.0.percentage_relative_to(Length::new(positioning_area.size.height))
            });

            if width.is_none() && height.is_none() {
                // Both computed values are 'auto':
                // use intrinsic sizes, treating missing width or height as 'auto'
                width = intrinsic.width;
                height = intrinsic.height;
            }

            match (width, height) {
                (Some(w), Some(h)) => units::LayoutSize::new(w.px(), h.px()),
                (Some(w), None) => {
                    let h = if let Some(intrinsic_ratio) = intrinsic.ratio {
                        w / intrinsic_ratio
                    } else if let Some(intrinsic_height) = intrinsic.height {
                        intrinsic_height
                    } else {
                        // Treated as 100%
                        Length::new(positioning_area.size.height)
                    };
                    units::LayoutSize::new(w.px(), h.px())
                },
                (None, Some(h)) => {
                    let w = if let Some(intrinsic_ratio) = intrinsic.ratio {
                        h * intrinsic_ratio
                    } else if let Some(intrinsic_width) = intrinsic.width {
                        intrinsic_width
                    } else {
                        // Treated as 100%
                        Length::new(positioning_area.size.width)
                    };
                    units::LayoutSize::new(w.px(), h.px())
                },
                // Both comptued values were 'auto', and neither intrinsic size is present
                (None, None) => size_contain_or_cover(ContainOrCover::Contain),
            }
        },
    };

    if tile_size.width == 0.0 || tile_size.height == 0.0 {
        return None;
    }

    let mut tile_spacing = units::LayoutSize::zero();
    let RepeatXY(repeat_x, repeat_y) = *get_cyclic(&b.background_repeat.0, layer_index);
    let result_x = layout_1d(
        &mut tile_size.width,
        &mut tile_spacing.width,
        repeat_x,
        get_cyclic(&b.background_position_x.0, layer_index),
        clipping_area.origin.x - positioning_area.origin.x,
        clipping_area.size.width,
        positioning_area.size.width,
    );
    let result_y = layout_1d(
        &mut tile_size.height,
        &mut tile_spacing.height,
        repeat_y,
        get_cyclic(&b.background_position_y.0, layer_index),
        clipping_area.origin.y - positioning_area.origin.y,
        clipping_area.size.height,
        positioning_area.size.height,
    );
    let bounds = units::LayoutRect::new(
        positioning_area.origin + Vector2D::new(result_x.bounds_origin, result_y.bounds_origin),
        Size2D::new(result_x.bounds_size, result_y.bounds_size),
    );

    // The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
    let mut common = builder.common_properties(*clipping_area);
    fragment_builder.with_border_edge_clip(builder, &mut common);

    Some(Layer {
        common,
        bounds,
        tile_size,
        tile_spacing,
        repeat: result_x.repeat || result_y.repeat,
    })
}

/// Abstract over the horizontal or vertical dimension
/// Coordinates (0, 0) for the purpose of this function are the positioning area’s origin.
fn layout_1d(
    tile_size: &mut f32,
    tile_spacing: &mut f32,
    mut repeat: Repeat,
    position: &LengthPercentage,
    clipping_area_origin: f32,
    clipping_area_size: f32,
    positioning_area_size: f32,
) -> Layout1DResult {
    // https://drafts.csswg.org/css-backgrounds/#background-repeat
    if let Repeat::Round = repeat {
        *tile_size = positioning_area_size / (positioning_area_size / *tile_size).round();
    }
    // https://drafts.csswg.org/css-backgrounds/#background-position
    let mut position = position
        .percentage_relative_to(Length::new(positioning_area_size - *tile_size))
        .px();
    // https://drafts.csswg.org/css-backgrounds/#background-repeat
    if let Repeat::Space = repeat {
        // The most entire tiles we can fit
        let tile_count = (positioning_area_size / *tile_size).floor();
        if tile_count >= 2.0 {
            position = 0.0;
            // Make the outsides of the first and last of that many tiles
            // touch the edges of the positioning area:
            let total_space = positioning_area_size - *tile_size * tile_count;
            let spaces_count = tile_count - 1.0;
            *tile_spacing = total_space / spaces_count;
        } else {
            repeat = Repeat::NoRepeat
        }
    }
    match repeat {
        Repeat::Repeat | Repeat::Round | Repeat::Space => {
            // WebRender’s `RepeatingImageDisplayItem` contains a `bounds` rectangle and:
            //
            // * The tiling is clipped to the intersection of `clip_rect` and `bounds`
            // * The origin (top-left corner) of `bounds` is the position
            //   of the “first” (top-left-most) tile.
            //
            // In the general case that first tile is not the one that is positioned by
            // `background-position`.
            // We want it to be the top-left-most tile that intersects with `clip_rect`.
            // We find it by offsetting by a whole number of strides,
            // then compute `bounds` such that:
            //
            // * Its bottom-right is the bottom-right of `clip_rect`
            // * Its top-left is the top-left of first tile.
            let tile_stride = *tile_size + *tile_spacing;
            let offset = position - clipping_area_origin;
            let bounds_origin = position - tile_stride * (offset / tile_stride).ceil();
            let bounds_size = clipping_area_size - bounds_origin - clipping_area_origin;
            Layout1DResult {
                repeat: true,
                bounds_origin,
                bounds_size,
            }
        },
        Repeat::NoRepeat => {
            // `RepeatingImageDisplayItem` always repeats in both dimension.
            // When we want only one of the dimensions to repeat,
            // we use the `bounds` rectangle to clip the tiling to one tile
            // in that dimension.
            Layout1DResult {
                repeat: false,
                bounds_origin: position,
                bounds_size: *tile_size,
            }
        },
    }
}
<commit_msg>Rename `clipping_area` to `painting_area`<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 https://mozilla.org/MPL/2.0/. */

use crate::replaced::IntrinsicSizes;
use euclid::{Size2D, Vector2D};
use style::computed_values::background_clip::single_value::T as Clip;
use style::computed_values::background_origin::single_value::T as Origin;
use style::values::computed::background::BackgroundSize as Size;
use style::values::computed::{Length, LengthPercentage};
use style::values::specified::background::BackgroundRepeat as RepeatXY;
use style::values::specified::background::BackgroundRepeatKeyword as Repeat;
use webrender_api::{self as wr, units};

pub(super) struct Layer {
    pub common: wr::CommonItemProperties,
    pub bounds: units::LayoutRect,
    pub tile_size: units::LayoutSize,
    pub tile_spacing: units::LayoutSize,
    pub repeat: bool,
}

struct Layout1DResult {
    repeat: bool,
    bounds_origin: f32,
    bounds_size: f32,
}

fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
    &values[layer_index % values.len()]
}

pub(super) fn layout_layer(
    fragment_builder: &mut super::BuilderForBoxFragment,
    builder: &mut super::DisplayListBuilder,
    layer_index: usize,
    intrinsic: IntrinsicSizes,
) -> Option<Layer> {
    let b = fragment_builder.fragment.style.get_background();

    let painting_area = match get_cyclic(&b.background_clip.0, layer_index) {
        Clip::ContentBox => fragment_builder.content_rect(),
        Clip::PaddingBox => fragment_builder.padding_rect(),
        Clip::BorderBox => &fragment_builder.border_rect,
    };
    let positioning_area = match get_cyclic(&b.background_origin.0, layer_index) {
        Origin::ContentBox => fragment_builder.content_rect(),
        Origin::PaddingBox => fragment_builder.padding_rect(),
        Origin::BorderBox => &fragment_builder.border_rect,
    };

    // https://drafts.csswg.org/css-backgrounds/#background-size
    enum ContainOrCover {
        Contain,
        Cover,
    }
    let size_contain_or_cover = |background_size| {
        let mut tile_size = positioning_area.size;
        if let Some(intrinsic_ratio) = intrinsic.ratio {
            let positioning_ratio = positioning_area.size.width / positioning_area.size.height;
            // Whether the tile width (as opposed to height)
            // is scaled to that of the positioning area
            let fit_width = match background_size {
                ContainOrCover::Contain => positioning_ratio <= intrinsic_ratio,
                ContainOrCover::Cover => positioning_ratio > intrinsic_ratio,
            };
            // The other dimension needs to be adjusted
            if fit_width {
                tile_size.height = tile_size.width / intrinsic_ratio
            } else {
                tile_size.width = tile_size.height * intrinsic_ratio
            }
        }
        tile_size
    };
    let mut tile_size = match get_cyclic(&b.background_size.0, layer_index) {
        Size::Contain => size_contain_or_cover(ContainOrCover::Contain),
        Size::Cover => size_contain_or_cover(ContainOrCover::Cover),
        Size::ExplicitSize { width, height } => {
            let mut width = width.non_auto().map(|lp| {
                lp.0.percentage_relative_to(Length::new(positioning_area.size.width))
            });
            let mut height = height.non_auto().map(|lp| {
                lp.0.percentage_relative_to(Length::new(positioning_area.size.height))
            });

            if width.is_none() && height.is_none() {
                // Both computed values are 'auto':
                // use intrinsic sizes, treating missing width or height as 'auto'
                width = intrinsic.width;
                height = intrinsic.height;
            }

            match (width, height) {
                (Some(w), Some(h)) => units::LayoutSize::new(w.px(), h.px()),
                (Some(w), None) => {
                    let h = if let Some(intrinsic_ratio) = intrinsic.ratio {
                        w / intrinsic_ratio
                    } else if let Some(intrinsic_height) = intrinsic.height {
                        intrinsic_height
                    } else {
                        // Treated as 100%
                        Length::new(positioning_area.size.height)
                    };
                    units::LayoutSize::new(w.px(), h.px())
                },
                (None, Some(h)) => {
                    let w = if let Some(intrinsic_ratio) = intrinsic.ratio {
                        h * intrinsic_ratio
                    } else if let Some(intrinsic_width) = intrinsic.width {
                        intrinsic_width
                    } else {
                        // Treated as 100%
                        Length::new(positioning_area.size.width)
                    };
                    units::LayoutSize::new(w.px(), h.px())
                },
                // Both comptued values were 'auto', and neither intrinsic size is present
                (None, None) => size_contain_or_cover(ContainOrCover::Contain),
            }
        },
    };

    if tile_size.width == 0.0 || tile_size.height == 0.0 {
        return None;
    }

    let mut tile_spacing = units::LayoutSize::zero();
    let RepeatXY(repeat_x, repeat_y) = *get_cyclic(&b.background_repeat.0, layer_index);
    let result_x = layout_1d(
        &mut tile_size.width,
        &mut tile_spacing.width,
        repeat_x,
        get_cyclic(&b.background_position_x.0, layer_index),
        painting_area.origin.x - positioning_area.origin.x,
        painting_area.size.width,
        positioning_area.size.width,
    );
    let result_y = layout_1d(
        &mut tile_size.height,
        &mut tile_spacing.height,
        repeat_y,
        get_cyclic(&b.background_position_y.0, layer_index),
        painting_area.origin.y - positioning_area.origin.y,
        painting_area.size.height,
        positioning_area.size.height,
    );
    let bounds = units::LayoutRect::new(
        positioning_area.origin + Vector2D::new(result_x.bounds_origin, result_y.bounds_origin),
        Size2D::new(result_x.bounds_size, result_y.bounds_size),
    );

    // The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
    let mut common = builder.common_properties(*painting_area);
    fragment_builder.with_border_edge_clip(builder, &mut common);

    Some(Layer {
        common,
        bounds,
        tile_size,
        tile_spacing,
        repeat: result_x.repeat || result_y.repeat,
    })
}

/// Abstract over the horizontal or vertical dimension
/// Coordinates (0, 0) for the purpose of this function are the positioning area’s origin.
fn layout_1d(
    tile_size: &mut f32,
    tile_spacing: &mut f32,
    mut repeat: Repeat,
    position: &LengthPercentage,
    painting_area_origin: f32,
    painting_area_size: f32,
    positioning_area_size: f32,
) -> Layout1DResult {
    // https://drafts.csswg.org/css-backgrounds/#background-repeat
    if let Repeat::Round = repeat {
        *tile_size = positioning_area_size / (positioning_area_size / *tile_size).round();
    }
    // https://drafts.csswg.org/css-backgrounds/#background-position
    let mut position = position
        .percentage_relative_to(Length::new(positioning_area_size - *tile_size))
        .px();
    // https://drafts.csswg.org/css-backgrounds/#background-repeat
    if let Repeat::Space = repeat {
        // The most entire tiles we can fit
        let tile_count = (positioning_area_size / *tile_size).floor();
        if tile_count >= 2.0 {
            position = 0.0;
            // Make the outsides of the first and last of that many tiles
            // touch the edges of the positioning area:
            let total_space = positioning_area_size - *tile_size * tile_count;
            let spaces_count = tile_count - 1.0;
            *tile_spacing = total_space / spaces_count;
        } else {
            repeat = Repeat::NoRepeat
        }
    }
    match repeat {
        Repeat::Repeat | Repeat::Round | Repeat::Space => {
            // WebRender’s `RepeatingImageDisplayItem` contains a `bounds` rectangle and:
            //
            // * The tiling is clipped to the intersection of `clip_rect` and `bounds`
            // * The origin (top-left corner) of `bounds` is the position
            //   of the “first” (top-left-most) tile.
            //
            // In the general case that first tile is not the one that is positioned by
            // `background-position`.
            // We want it to be the top-left-most tile that intersects with `clip_rect`.
            // We find it by offsetting by a whole number of strides,
            // then compute `bounds` such that:
            //
            // * Its bottom-right is the bottom-right of `clip_rect`
            // * Its top-left is the top-left of first tile.
            let tile_stride = *tile_size + *tile_spacing;
            let offset = position - painting_area_origin;
            let bounds_origin = position - tile_stride * (offset / tile_stride).ceil();
            let bounds_size = painting_area_size - bounds_origin - painting_area_origin;
            Layout1DResult {
                repeat: true,
                bounds_origin,
                bounds_size,
            }
        },
        Repeat::NoRepeat => {
            // `RepeatingImageDisplayItem` always repeats in both dimension.
            // When we want only one of the dimensions to repeat,
            // we use the `bounds` rectangle to clip the tiling to one tile
            // in that dimension.
            Layout1DResult {
                repeat: false,
                bounds_origin: position,
                bounds_size: *tile_size,
            }
        },
    }
}
<|endoftext|>


<commit_before>use std::path::{PathBuf, Path};
use std::env;
use std::time::Duration;
use std::str::FromStr;
use clap::ArgMatches;
use coveralls_api::CiService;
use regex::Regex;

arg_enum!{
    /// Enum to represent possible output formats.
    #[derive(Debug)]
    pub enum OutputFile {
        Json,
        Toml,
        Stdout,
        Xml
    }
}

struct Ci(CiService);

impl FromStr for Ci {
    type Err = ();
    /// This will never error so no need to implement the error type.
    fn from_str(s: &str) -> Result<Ci, ()> {
        match s {
            "travis-ci" => Ok(Ci(CiService::Travis)),
            "travis-pro" => Ok(Ci(CiService::TravisPro)),
            "circle-ci" => Ok(Ci(CiService::Circle)),
            "semaphore" => Ok(Ci(CiService::Semaphore)),
            "jenkins" => Ok(Ci(CiService::Jenkins)),
            "codeship" => Ok(Ci(CiService::Codeship)),
            other => Ok(Ci(CiService::Other(other.to_string()))),
        }
    }
}

/// Specifies the current configuration tarpaulin is using.
#[derive(Debug, Default)]
pub struct Config {
    /// Path to the projects cargo manifest
    pub manifest: PathBuf,
    /// Flag to also run tests with the ignored attribute
    pub run_ignored: bool,
    /// Flag to ignore test functions in coverage statistics
    pub ignore_tests: bool,
    /// Flag to skip the clean step when preparing the target project
    pub skip_clean: bool,
    /// Verbose flag for printing information to the user
    pub verbose: bool,
    /// Flag to disable counting line hits in line coverage mode
    pub no_count: bool,
    /// Flag specifying to run line coverage (default)
    pub line_coverage: bool,
    /// Flag specifying to run branch coverage
    pub branch_coverage: bool,
    /// Output files to generate
    pub generate: Vec<OutputFile>,
    /// Key relating to coveralls service or repo
    pub coveralls: Option<String>,
    /// Enum representing CI tool used.
    pub ci_tool: Option<CiService>,
    /// Forward unexpected signals back to the tracee. Used for tests which
    /// rely on signals to work.
    pub forward_signals: bool,
    /// Include all available features in target build
    pub all_features: bool,
    /// Features to include in the target project build
    pub features: Vec<String>,
    /// Build all packages in the workspace
    pub all: bool,
    /// Packages to include when building the target project
    pub packages: Vec<String>,
    /// Packages to exclude from testing
    pub exclude: Vec<String>,
    /// Files to exclude from testing
    excluded_files: Vec<Regex>,
    /// Varargs to be forwarded to the test executables.
    pub varargs: Vec<String>,
    /// Duration to wait before a timeout occurs
    pub test_timeout: Duration,
}

impl Config {
    fn get_list_from_args(args: &ArgMatches, key: &str) -> Vec<String> {
        match args.values_of_lossy(key) {
            Some(v) => v,
            None => vec![],
        }
    }

    /// Create configuration from clap ArgMatches.
    pub fn from_args(args: &ArgMatches) -> Config {
        let mut line = args.is_present("line");
        let mut branch = args.is_present("branch");
        let verbose = args.is_present("verbose");
        let ignored = args.is_present("ignored");
        let forward = args.is_present("forward");
        let skip_clean = args.is_present("skip-clean");
        let no_count = args.is_present("no-count");
        let ignore_tests = args.is_present("ignore-tests");
        let all_features = args.is_present("all-features");
        // If no coverage selected do everything!
        if !branch && !line {
            branch = true;
            line = true;
        }
        let mut root = env::current_dir().unwrap();
        if let Some(path) = args.value_of("root") {
            root.push(path);
        };
        root.push("Cargo.toml");
        if let Ok(cpath) = root.canonicalize() {
            root = cpath;
        }
        let ci_tool = match value_t!(args, "ciserver", Ci) {
            Ok(ci) => Some(ci.0),
            Err(_) => None,
        };
        let coveralls = if let Some(cio) = args.value_of("coveralls") {
            Some(cio.to_string())
        } else {
            None
        };
        let out:Vec<OutputFile> = values_t!(args.values_of("out"), OutputFile)
            .unwrap_or_default();
        let features: Vec<String> = Config::get_list_from_args(args, "features");
        let all = args.is_present("all");
        let packages: Vec<String> = Config::get_list_from_args(args, "packages");
        let exclude: Vec<String> = Config::get_list_from_args(args, "exclude");
        let varargs: Vec<String> = Config::get_list_from_args(args, "args");
        let mut ex_files:Vec<Regex> = vec![];
        for temp_str in &Config::get_list_from_args(args, "exclude-files") {
            let s =  &temp_str.replace(".", r"\.").replace("*", ".*");
            if let Ok(re) = Regex::new(s) {
                ex_files.push(re);
            } else if verbose {
                println!("Error in wildcard expression: {}", temp_str);
            }
        }

        let timeout = if args.is_present("timeout") {
            match value_t!(args.value_of("timeout"), u64) {
                Ok(s) => s,
                Err(_) => {
                    println!("Invalid value for timeout. Setting to 1 minute");
                    60u64
                }
            }
        } else {
            60u64
        };
        Config{
            manifest: root,
            run_ignored: ignored,
            ignore_tests: ignore_tests,
            verbose: verbose,
            no_count: no_count,
            line_coverage: line,
            skip_clean: skip_clean,
            branch_coverage: branch,
            generate: out,
            coveralls: coveralls,
            ci_tool: ci_tool,
            forward_signals: forward,
            all_features: all_features,
            features: features,
            all: all,
            packages: packages,
            exclude: exclude,
            excluded_files: ex_files,
            varargs: varargs,
            test_timeout: Duration::from_secs(timeout),
        }
    }

    /// Determine whether to send data to coveralls
    pub fn is_coveralls(&self) -> bool {
        self.coveralls.is_some()
    }

    pub fn exclude_path(&self, path: &Path) -> bool {
        let path = self.strip_project_path(path);
        self.excluded_files.iter()
                           .any(|x| x.is_match(path.to_str().unwrap_or("")))
    }

    /// Strips the directory the project manifest is in from the path. Provides a
    /// nicer path for printing to the user.
    pub fn strip_project_path<'a>(&'a self, path: &'a Path) -> PathBuf {
        if let Some(root) = self.manifest.parent() {
            path_relative_from(path, root).unwrap_or(path.to_path_buf())
        } else {
            path.to_path_buf()
        }
    }
}

/// Gets the relative path from one directory to another, if it exists.
/// Credit to brson from this commit from 2015
/// https://github.com/rust-lang/rust/pull/23283/files
pub(crate) fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
    use std::path::Component;

    if path.is_absolute() != base.is_absolute() {
        if path.is_absolute() {
            Some(path.to_path_buf())
        } else {
            None
        }
    } else {
        let mut ita = path.components();
        let mut itb = base.components();
        let mut comps: Vec<Component> = vec![];
        loop {
            match (ita.next(), itb.next()) {
                (None, None) => break,
                (Some(a), None) => {
                    comps.push(a);
                    comps.extend(ita.by_ref());
                    break;
                }
                (None, _) => comps.push(Component::ParentDir),
                (Some(a), Some(b)) if comps.is_empty() && a == b => (),
                (Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
                (Some(_), Some(b)) if b == Component::ParentDir => return None,
                (Some(a), Some(_)) => {
                    comps.push(Component::ParentDir);
                    for _ in itb {
                        comps.push(Component::ParentDir);
                    }
                    comps.push(a);
                    comps.extend(ita.by_ref());
                    break;
                }
            }
        }
        Some(comps.iter().map(|c| c.as_os_str()).collect())
    }
}

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

    #[test]
    fn exclude_paths() {
        let matches = App::new("tarpaulin")
            .args_from_usage("--exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'")
            .get_matches_from_safe(vec!["tarpaulin", "--exclude-files", "*module*"])
            .unwrap();
        let conf = Config::from_args(&matches);
        assert!(conf.exclude_path(Path::new("src/module/file.rs")));
        assert!(!conf.exclude_path(Path::new("src/mod.rs")));
        assert!(!conf.exclude_path(Path::new("unrelated.rs")));
        assert!(conf.exclude_path(Path::new("module.rs")));
    }

    #[test]
    fn no_exclusions() {
        let matches = App::new("tarpaulin")
            .args_from_usage("--exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'")
            .get_matches_from_safe(vec!["tarpaulin"])
            .unwrap();
        let conf = Config::from_args(&matches);
        assert!(!conf.exclude_path(Path::new("src/module/file.rs")));
        assert!(!conf.exclude_path(Path::new("src/mod.rs")));
        assert!(!conf.exclude_path(Path::new("unrelated.rs")));
        assert!(!conf.exclude_path(Path::new("module.rs")));
    }

    #[test]
    fn exclude_exact_file() {
        let matches = App::new("tarpaulin")
            .args_from_usage("--exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'")
            .get_matches_from_safe(vec!["tarpaulin", "--exclude-files", "*/lib.rs"])
            .unwrap();
        let conf = Config::from_args(&matches);
        assert!(conf.exclude_path(Path::new("src/lib.rs")));
        assert!(!conf.exclude_path(Path::new("src/mod.rs")));
        assert!(!conf.exclude_path(Path::new("src/notlib.rs")));
        assert!(!conf.exclude_path(Path::new("lib.rs")));
    }

    #[test]
    fn relative_path_test() {
        let path_a = Path::new("/this/should/form/a/rel/path/");
        let path_b = Path::new("/this/should/form/b/rel/path/");

        let rel_path = path_relative_from(path_b, path_a);
        assert!(rel_path.is_some());
        assert_eq!(rel_path.unwrap().to_str().unwrap(), "../../../b/rel/path", "Wrong relative path");

        let path_a = Path::new("/this/should/not/form/a/rel/path/");
        let path_b = Path::new("./this/should/not/form/a/rel/path/");

        let rel_path = path_relative_from(path_b, path_a);
        assert_eq!(rel_path, None, "Did not expect relative path");

        let path_a = Path::new("./this/should/form/a/rel/path/");
        let path_b = Path::new("./this/should/form/b/rel/path/");

        let rel_path = path_relative_from(path_b, path_a);
        assert!(rel_path.is_some());
        assert_eq!(rel_path.unwrap().to_str().unwrap(), "../../../b/rel/path", "Wrong relative path");
    }
}
<commit_msg>Resolve merge conflict.<commit_after>use std::path::{PathBuf, Path};
use std::env;
use std::time::Duration;
use std::str::FromStr;
use clap::ArgMatches;
use coveralls_api::CiService;
use regex::Regex;

arg_enum!{
    /// Enum to represent possible output formats.
    #[derive(Debug)]
    pub enum OutputFile {
        Json,
        Toml,
        Stdout,
        Xml
    }
}

struct Ci(CiService);

impl FromStr for Ci {
    type Err = ();
    /// This will never error so no need to implement the error type.
    fn from_str(s: &str) -> Result<Ci, ()> {
        match s {
            "travis-ci" => Ok(Ci(CiService::Travis)),
            "travis-pro" => Ok(Ci(CiService::TravisPro)),
            "circle-ci" => Ok(Ci(CiService::Circle)),
            "semaphore" => Ok(Ci(CiService::Semaphore)),
            "jenkins" => Ok(Ci(CiService::Jenkins)),
            "codeship" => Ok(Ci(CiService::Codeship)),
            other => Ok(Ci(CiService::Other(other.to_string()))),
        }
    }
}

/// Specifies the current configuration tarpaulin is using.
#[derive(Debug, Default)]
pub struct Config {
    /// Path to the projects cargo manifest
    pub manifest: PathBuf,
    /// Flag to also run tests with the ignored attribute
    pub run_ignored: bool,
    /// Flag to ignore test functions in coverage statistics
    pub ignore_tests: bool,
    /// Flag to skip the clean step when preparing the target project
    pub skip_clean: bool,
    /// Verbose flag for printing information to the user
    pub verbose: bool,
    /// Flag to disable counting line hits in line coverage mode
    pub no_count: bool,
    /// Flag specifying to run line coverage (default)
    pub line_coverage: bool,
    /// Flag specifying to run branch coverage
    pub branch_coverage: bool,
    /// Output files to generate
    pub generate: Vec<OutputFile>,
    /// Key relating to coveralls service or repo
    pub coveralls: Option<String>,
    /// Enum representing CI tool used.
    pub ci_tool: Option<CiService>,
    /// Forward unexpected signals back to the tracee. Used for tests which
    /// rely on signals to work.
    pub forward_signals: bool,
    /// Include all available features in target build
    pub all_features: bool,
    /// Features to include in the target project build
    pub features: Vec<String>,
    /// Build all packages in the workspace
    pub all: bool,
    /// Packages to include when building the target project
    pub packages: Vec<String>,
    /// Packages to exclude from testing
    pub exclude: Vec<String>,
    /// Files to exclude from testing
    excluded_files: Vec<Regex>,
    /// Varargs to be forwarded to the test executables.
    pub varargs: Vec<String>,
    /// Duration to wait before a timeout occurs
    pub test_timeout: Duration,
}

impl Config {
    fn get_list_from_args(args: &ArgMatches, key: &str) -> Vec<String> {
        match args.values_of_lossy(key) {
            Some(v) => v,
            None => vec![],
        }
    }

    /// Create configuration from clap ArgMatches.
    pub fn from_args(args: &ArgMatches) -> Config {
        let mut line = args.is_present("line");
        let mut branch = args.is_present("branch");
        let verbose = args.is_present("verbose");
        let ignored = args.is_present("ignored");
        let forward = args.is_present("forward");
        let skip_clean = args.is_present("skip-clean");
        let no_count = args.is_present("no-count");
        let ignore_tests = args.is_present("ignore-tests");
        let all_features = args.is_present("all-features");
        // If no coverage selected do everything!
        if !branch && !line {
            branch = true;
            line = true;
        }
        let mut root = env::current_dir().unwrap();
        if let Some(path) = args.value_of("root") {
            root.push(path);
        };
        root.push("Cargo.toml");
        if let Ok(cpath) = root.canonicalize() {
            root = cpath;
        }
        let ci_tool = match value_t!(args, "ciserver", Ci) {
            Ok(ci) => Some(ci.0),
            Err(_) => None,
        };
        let coveralls = if let Some(cio) = args.value_of("coveralls") {
            Some(cio.to_string())
        } else {
            None
        };
        let out:Vec<OutputFile> = values_t!(args.values_of("out"), OutputFile)
            .unwrap_or_default();
        let features: Vec<String> = Config::get_list_from_args(args, "features");
        let all = args.is_present("all");
        let packages: Vec<String> = Config::get_list_from_args(args, "packages");
        let exclude: Vec<String> = Config::get_list_from_args(args, "exclude");
        let varargs: Vec<String> = Config::get_list_from_args(args, "args");
        let mut ex_files:Vec<Regex> = vec![];
        for temp_str in &Config::get_list_from_args(args, "exclude-files") {
            let s =  &temp_str.replace(".", r"\.").replace("*", ".*");
            if let Ok(re) = Regex::new(s) {
                ex_files.push(re);
            } else if verbose {
                println!("Error in wildcard expression: {}", temp_str);
            }
        }

        let timeout = if args.is_present("timeout") {
            match value_t!(args.value_of("timeout"), u64) {
                Ok(s) => s,
                Err(_) => {
                    println!("Invalid value for timeout. Setting to 1 minute");
                    60u64
                }
            }
        } else {
            60u64
        };
        Config{
            manifest: root,
            run_ignored: ignored,
            ignore_tests: ignore_tests,
            verbose: verbose,
            no_count: no_count,
            line_coverage: line,
            skip_clean: skip_clean,
            branch_coverage: branch,
            generate: out,
            coveralls: coveralls,
            ci_tool: ci_tool,
            forward_signals: forward,
            all_features: all_features,
            features: features,
            all: all,
            packages: packages,
            exclude: exclude,
            excluded_files: ex_files,
            varargs: varargs,
            test_timeout: Duration::from_secs(timeout),
        }
    }

    /// Determine whether to send data to coveralls
    pub fn is_coveralls(&self) -> bool {
        self.coveralls.is_some()
    }

    pub fn exclude_path(&self, path: &Path) -> bool {
        let path = self.strip_project_path(path);
        self.excluded_files.iter()
                           .any(|x| x.is_match(path.to_str().unwrap_or("")))
    }

    /// Strips the directory the project manifest is in from the path. Provides a
    /// nicer path for printing to the user.
    pub fn strip_project_path<'a>(&'a self, path: &'a Path) -> PathBuf {
        if let Some(root) = self.manifest.parent() {
            path_relative_from(path, root).unwrap_or(path.to_path_buf())
        } else {
            path.to_path_buf()
        }
    }
}

/// Gets the relative path from one directory to another, if it exists.
/// Credit to brson from this commit from 2015
/// https://github.com/rust-lang/rust/pull/23283/files
pub(crate) fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
    use std::path::Component;

    if path.is_absolute() != base.is_absolute() {
        if path.is_absolute() {
            Some(path.to_path_buf())
        } else {
            None
        }
    } else {
        let mut ita = path.components();
        let mut itb = base.components();
        let mut comps: Vec<Component> = vec![];
        loop {
            match (ita.next(), itb.next()) {
                (None, None) => break,
                (Some(a), None) => {
                    comps.push(a);
                    comps.extend(ita.by_ref());
                    break;
                }
                (None, _) => comps.push(Component::ParentDir),
                (Some(a), Some(b)) if comps.is_empty() && a == b => (),
                (Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
                (Some(_), Some(b)) if b == Component::ParentDir => return None,
                (Some(a), Some(_)) => {
                    comps.push(Component::ParentDir);
                    for _ in itb {
                        comps.push(Component::ParentDir);
                    }
                    comps.push(a);
                    comps.extend(ita.by_ref());
                    break;
                }
            }
        }
        Some(comps.iter().map(|c| c.as_os_str()).collect())
    }
}

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

    #[test]
    fn exclude_paths() {
        let matches = App::new("tarpaulin")
            .args_from_usage("--exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'")
            .get_matches_from_safe(vec!["tarpaulin", "--exclude-files", "*module*"])
            .unwrap();
        let conf = Config::from_args(&matches);
        assert!(conf.exclude_path(Path::new("src/module/file.rs")));
        assert!(!conf.exclude_path(Path::new("src/mod.rs")));
        assert!(!conf.exclude_path(Path::new("unrelated.rs")));
        assert!(conf.exclude_path(Path::new("module.rs")));
    }

    #[test]
    fn no_exclusions() {
        let matches = App::new("tarpaulin")
            .args_from_usage("--exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'")
            .get_matches_from_safe(vec!["tarpaulin"])
            .unwrap();
        let conf = Config::from_args(&matches);
        assert!(!conf.exclude_path(Path::new("src/module/file.rs")));
        assert!(!conf.exclude_path(Path::new("src/mod.rs")));
        assert!(!conf.exclude_path(Path::new("unrelated.rs")));
        assert!(!conf.exclude_path(Path::new("module.rs")));
    }

    #[test]
    fn exclude_exact_file() {
        let matches = App::new("tarpaulin")
            .args_from_usage("--exclude-files [FILE]... 'Exclude given files from coverage results has * wildcard'")
            .get_matches_from_safe(vec!["tarpaulin", "--exclude-files", "*/lib.rs"])
            .unwrap();
        let conf = Config::from_args(&matches);
        assert!(conf.exclude_path(Path::new("src/lib.rs")));
        assert!(!conf.exclude_path(Path::new("src/mod.rs")));
        assert!(!conf.exclude_path(Path::new("src/notlib.rs")));
        assert!(!conf.exclude_path(Path::new("lib.rs")));
    }

    #[test]
    fn relative_path_test() {
        let path_a = Path::new("/this/should/form/a/rel/path/");
        let path_b = Path::new("/this/should/form/b/rel/path/");

        let rel_path = path_relative_from(path_b, path_a);
        assert!(rel_path.is_some());
        assert_eq!(rel_path.unwrap().to_str().unwrap(), "../../../b/rel/path", "Wrong relative path");

        let path_a = Path::new("/this/should/not/form/a/rel/path/");
        let path_b = Path::new("./this/should/not/form/a/rel/path/");

        let rel_path = path_relative_from(path_b, path_a);
        assert_eq!(rel_path, None, "Did not expect relative path");

        let path_a = Path::new("./this/should/form/a/rel/path/");
        let path_b = Path::new("./this/should/form/b/rel/path/");

        let rel_path = path_relative_from(path_b, path_a);
        assert!(rel_path.is_some());
        assert_eq!(rel_path.unwrap().to_str().unwrap(), "../../../b/rel/path", "Wrong relative path");
    }
}
<|endoftext|>