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

use canvas_traits::{CanvasMsg, FromLayoutMsg, CanvasData};
use dom::attr::Attr;
use dom::attr::AttrValue;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
use dom::bindings::codegen::UnionTypes::CanvasRenderingContext2DOrWebGLRenderingContext;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{HeapGCValue, JS, LayoutJS, Root};
use dom::bindings::num::Finite;
use dom::bindings::reflector::Reflectable;
use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers};
use dom::document::Document;
use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::webglrenderingcontext::{LayoutCanvasWebGLRenderingContextHelpers, WebGLRenderingContext};
use euclid::size::Size2D;
use image::ColorType;
use image::png::PNGEncoder;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::{HandleValue, JSContext};
use offscreen_gl_context::GLContextAttributes;
use rustc_serialize::base64::{STANDARD, ToBase64};
use std::iter::repeat;
use string_cache::Atom;
use util::str::DOMString;

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

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

impl HeapGCValue for CanvasContext {}

#[dom_struct]
pub struct HTMLCanvasElement {
    htmlelement: HTMLElement,
    context: DOMRefCell<Option<CanvasContext>>,
}

impl HTMLCanvasElement {
    fn new_inherited(localName: Atom,
                     prefix: Option<DOMString>,
                     document: &Document) -> HTMLCanvasElement {
        HTMLCanvasElement {
            htmlelement: HTMLElement::new_inherited(localName, prefix, document),
            context: DOMRefCell::new(None),
        }
    }

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

    fn recreate_contexts(&self) {
        let size = self.get_size();
        if let Some(ref context) = *self.context.borrow() {
            match *context {
                CanvasContext::Context2d(ref context) => context.set_bitmap_dimensions(size),
                CanvasContext::WebGL(ref context) => context.recreate(size),
            }
        }
    }

    pub fn get_size(&self) -> Size2D<i32> {
        Size2D::new(self.Width() as i32, self.Height() as i32)
    }

    pub fn origin_is_clean(&self) -> bool {
        match *self.context.borrow() {
            Some(CanvasContext::Context2d(ref context)) => context.origin_is_clean(),
            _ => true,
        }
    }
}

pub struct HTMLCanvasData {
    pub renderer_id: Option<usize>,
    pub ipc_renderer: Option<IpcSender<CanvasMsg>>,
    pub width: u32,
    pub height: u32,
}

pub trait LayoutHTMLCanvasElementHelpers {
    fn data(&self) -> HTMLCanvasData;
}

impl LayoutHTMLCanvasElementHelpers for LayoutJS<HTMLCanvasElement> {
    #[allow(unsafe_code)]
    fn data(&self) -> HTMLCanvasData {
        unsafe {
            let canvas = &*self.unsafe_get();
            let (renderer_id, ipc_renderer) = match canvas.context.borrow_for_layout().as_ref() {
                Some(&CanvasContext::Context2d(ref context)) => {
                    let context = context.to_layout();
                    (Some(context.get_renderer_id()), Some(context.get_ipc_renderer()))
                },
                Some(&CanvasContext::WebGL(ref context)) => {
                    let context = context.to_layout();
                    (Some(context.get_renderer_id()), Some(context.get_ipc_renderer()))
                },
                None => (None, None),
            };

            let width_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &atom!("width"));
            let height_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &atom!("height"));
            HTMLCanvasData {
                renderer_id: renderer_id,
                ipc_renderer: ipc_renderer,
                width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()),
                height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()),
            }
        }
    }
}

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

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

        match *self.context.borrow().as_ref().unwrap() {
            CanvasContext::Context2d(ref context) => Some(Root::from_ref(&*context)),
            _   => None,
        }
    }

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

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

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

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

        if let Some(CanvasContext::WebGL(ref context)) = *self.context.borrow() {
            Some(Root::from_ref(&*context))
        } else {
            None
        }
    }

    pub fn is_valid(&self) -> bool {
        self.Height() != 0 && self.Width() != 0
    }

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

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

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

            match receiver.recv().unwrap() {
                CanvasData::Pixels(pixel_data)
                    => pixel_data.image_data.to_vec(),
                CanvasData::WebGL(_)
                    // TODO(ecoal95): Not sure if WebGL canvas is required for 2d spec,
                    // but I think it's not.
                    => return None,
            }
        } else {
            repeat(0xffu8).take((size.height as usize) * (size.width as usize) * 4).collect()
        };

        Some((data, size))
    }
}

impl HTMLCanvasElementMethods for HTMLCanvasElement {
    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    make_uint_getter!(Width, "width", DEFAULT_WIDTH);

    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    make_uint_setter!(SetWidth, "width", DEFAULT_WIDTH);

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    make_uint_getter!(Height, "height", DEFAULT_HEIGHT);

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    make_uint_setter!(SetHeight, "height", DEFAULT_HEIGHT);

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

    // https://html.spec.whatwg.org/multipage/#dom-canvas-todataurl
    fn ToDataURL(&self,
                 _context: *mut JSContext,
                 _mime_type: Option<DOMString>,
                 _arguments: Vec<HandleValue>) -> Fallible<DOMString> {

        // Step 1.
        if let Some(CanvasContext::Context2d(ref context)) = *self.context.borrow() {
            if !context.origin_is_clean() {
                return Err(Error::Security);
            }
        }

        // Step 2.
        if self.Width() == 0 || self.Height() == 0 {
            return Ok(DOMString::from("data:,"));
        }

        // Step 3.
        let raw_data = match *self.context.borrow() {
            Some(CanvasContext::Context2d(ref context)) => {
                let window = window_from_node(self);
                let image_data = try!(context.GetImageData(Finite::wrap(0f64), Finite::wrap(0f64),
                                                           Finite::wrap(self.Width() as f64),
                                                           Finite::wrap(self.Height() as f64)));
                image_data.get_data_array(&GlobalRef::Window(window.r()))
            }
            None => {
                // Each pixel is fully-transparent black.
                vec![0; (self.Width() * self.Height() * 4) as usize]
            }
            _ => return Err(Error::NotSupported) // WebGL
        };

        // Only handle image/png for now.
        let mime_type = "image/png";

        let mut encoded = Vec::new();
        {
            let encoder: PNGEncoder<&mut Vec<u8>> = PNGEncoder::new(&mut encoded);
            encoder.encode(&raw_data, self.Width(), self.Height(), ColorType::RGBA(8)).unwrap();
        }

        let encoded = encoded.to_base64(STANDARD);
        Ok(DOMString::from(format!("data:{};base64,{}", mime_type, encoded)))
    }
}

impl VirtualMethods for HTMLCanvasElement {
    fn super_type(&self) -> Option<&VirtualMethods> {
        Some(self.upcast::<HTMLElement>() as &VirtualMethods)
    }

    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
        self.super_type().unwrap().attribute_mutated(attr, mutation);
        match attr.local_name() {
            &atom!("width") | &atom!("height") => self.recreate_contexts(),
            _ => (),
        };
    }

    fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
        match name {
            &atom!("width") => AttrValue::from_u32(value, DEFAULT_WIDTH),
            &atom!("height") => AttrValue::from_u32(value, DEFAULT_HEIGHT),
            _ => self.super_type().unwrap().parse_plain_attribute(name, value),
        }
    }
}

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

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

    pub fn request_image_from_cache(window: &Window, url: Url) -> ImageResponse {
        let image_cache = window.image_cache_thread();
        let (response_chan, response_port) = ipc::channel().unwrap();
        image_cache.request_image(url, ImageCacheChan(response_chan), None);
        let result = response_port.recv().unwrap();
        result.image_response
    }
}
<commit_msg>Remove HTMLCanvasData::renderer_id.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use canvas_traits::{CanvasMsg, FromLayoutMsg, CanvasData};
use dom::attr::Attr;
use dom::attr::AttrValue;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding;
use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLContextAttributes;
use dom::bindings::codegen::UnionTypes::CanvasRenderingContext2DOrWebGLRenderingContext;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::global::GlobalRef;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{HeapGCValue, JS, LayoutJS, Root};
use dom::bindings::num::Finite;
use dom::bindings::reflector::Reflectable;
use dom::canvasrenderingcontext2d::{CanvasRenderingContext2D, LayoutCanvasRenderingContext2DHelpers};
use dom::document::Document;
use dom::element::{AttributeMutation, Element, RawLayoutElementHelpers};
use dom::htmlelement::HTMLElement;
use dom::node::{Node, window_from_node};
use dom::virtualmethods::VirtualMethods;
use dom::webglrenderingcontext::{LayoutCanvasWebGLRenderingContextHelpers, WebGLRenderingContext};
use euclid::size::Size2D;
use image::ColorType;
use image::png::PNGEncoder;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::{HandleValue, JSContext};
use offscreen_gl_context::GLContextAttributes;
use rustc_serialize::base64::{STANDARD, ToBase64};
use std::iter::repeat;
use string_cache::Atom;
use util::str::DOMString;

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

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

impl HeapGCValue for CanvasContext {}

#[dom_struct]
pub struct HTMLCanvasElement {
    htmlelement: HTMLElement,
    context: DOMRefCell<Option<CanvasContext>>,
}

impl HTMLCanvasElement {
    fn new_inherited(localName: Atom,
                     prefix: Option<DOMString>,
                     document: &Document) -> HTMLCanvasElement {
        HTMLCanvasElement {
            htmlelement: HTMLElement::new_inherited(localName, prefix, document),
            context: DOMRefCell::new(None),
        }
    }

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

    fn recreate_contexts(&self) {
        let size = self.get_size();
        if let Some(ref context) = *self.context.borrow() {
            match *context {
                CanvasContext::Context2d(ref context) => context.set_bitmap_dimensions(size),
                CanvasContext::WebGL(ref context) => context.recreate(size),
            }
        }
    }

    pub fn get_size(&self) -> Size2D<i32> {
        Size2D::new(self.Width() as i32, self.Height() as i32)
    }

    pub fn origin_is_clean(&self) -> bool {
        match *self.context.borrow() {
            Some(CanvasContext::Context2d(ref context)) => context.origin_is_clean(),
            _ => true,
        }
    }
}

pub struct HTMLCanvasData {
    pub ipc_renderer: Option<IpcSender<CanvasMsg>>,
    pub width: u32,
    pub height: u32,
}

pub trait LayoutHTMLCanvasElementHelpers {
    fn data(&self) -> HTMLCanvasData;
}

impl LayoutHTMLCanvasElementHelpers for LayoutJS<HTMLCanvasElement> {
    #[allow(unsafe_code)]
    fn data(&self) -> HTMLCanvasData {
        unsafe {
            let canvas = &*self.unsafe_get();
            let ipc_renderer = canvas.context.borrow_for_layout().as_ref().map(|context| {
                match *context {
                    CanvasContext::Context2d(ref context) => {
                        context.to_layout().get_ipc_renderer()
                    },
                    CanvasContext::WebGL(ref context) => {
                        context.to_layout().get_ipc_renderer()
                    },
                }
            });

            let width_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &atom!("width"));
            let height_attr = canvas.upcast::<Element>().get_attr_for_layout(&ns!(), &atom!("height"));
            HTMLCanvasData {
                ipc_renderer: ipc_renderer,
                width: width_attr.map_or(DEFAULT_WIDTH, |val| val.as_uint()),
                height: height_attr.map_or(DEFAULT_HEIGHT, |val| val.as_uint()),
            }
        }
    }
}

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

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

        match *self.context.borrow().as_ref().unwrap() {
            CanvasContext::Context2d(ref context) => Some(Root::from_ref(&*context)),
            _   => None,
        }
    }

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

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

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

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

        if let Some(CanvasContext::WebGL(ref context)) = *self.context.borrow() {
            Some(Root::from_ref(&*context))
        } else {
            None
        }
    }

    pub fn is_valid(&self) -> bool {
        self.Height() != 0 && self.Width() != 0
    }

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

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

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

            match receiver.recv().unwrap() {
                CanvasData::Pixels(pixel_data)
                    => pixel_data.image_data.to_vec(),
                CanvasData::WebGL(_)
                    // TODO(ecoal95): Not sure if WebGL canvas is required for 2d spec,
                    // but I think it's not.
                    => return None,
            }
        } else {
            repeat(0xffu8).take((size.height as usize) * (size.width as usize) * 4).collect()
        };

        Some((data, size))
    }
}

impl HTMLCanvasElementMethods for HTMLCanvasElement {
    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    make_uint_getter!(Width, "width", DEFAULT_WIDTH);

    // https://html.spec.whatwg.org/multipage/#dom-canvas-width
    make_uint_setter!(SetWidth, "width", DEFAULT_WIDTH);

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    make_uint_getter!(Height, "height", DEFAULT_HEIGHT);

    // https://html.spec.whatwg.org/multipage/#dom-canvas-height
    make_uint_setter!(SetHeight, "height", DEFAULT_HEIGHT);

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

    // https://html.spec.whatwg.org/multipage/#dom-canvas-todataurl
    fn ToDataURL(&self,
                 _context: *mut JSContext,
                 _mime_type: Option<DOMString>,
                 _arguments: Vec<HandleValue>) -> Fallible<DOMString> {

        // Step 1.
        if let Some(CanvasContext::Context2d(ref context)) = *self.context.borrow() {
            if !context.origin_is_clean() {
                return Err(Error::Security);
            }
        }

        // Step 2.
        if self.Width() == 0 || self.Height() == 0 {
            return Ok(DOMString::from("data:,"));
        }

        // Step 3.
        let raw_data = match *self.context.borrow() {
            Some(CanvasContext::Context2d(ref context)) => {
                let window = window_from_node(self);
                let image_data = try!(context.GetImageData(Finite::wrap(0f64), Finite::wrap(0f64),
                                                           Finite::wrap(self.Width() as f64),
                                                           Finite::wrap(self.Height() as f64)));
                image_data.get_data_array(&GlobalRef::Window(window.r()))
            }
            None => {
                // Each pixel is fully-transparent black.
                vec![0; (self.Width() * self.Height() * 4) as usize]
            }
            _ => return Err(Error::NotSupported) // WebGL
        };

        // Only handle image/png for now.
        let mime_type = "image/png";

        let mut encoded = Vec::new();
        {
            let encoder: PNGEncoder<&mut Vec<u8>> = PNGEncoder::new(&mut encoded);
            encoder.encode(&raw_data, self.Width(), self.Height(), ColorType::RGBA(8)).unwrap();
        }

        let encoded = encoded.to_base64(STANDARD);
        Ok(DOMString::from(format!("data:{};base64,{}", mime_type, encoded)))
    }
}

impl VirtualMethods for HTMLCanvasElement {
    fn super_type(&self) -> Option<&VirtualMethods> {
        Some(self.upcast::<HTMLElement>() as &VirtualMethods)
    }

    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) {
        self.super_type().unwrap().attribute_mutated(attr, mutation);
        match attr.local_name() {
            &atom!("width") | &atom!("height") => self.recreate_contexts(),
            _ => (),
        };
    }

    fn parse_plain_attribute(&self, name: &Atom, value: DOMString) -> AttrValue {
        match name {
            &atom!("width") => AttrValue::from_u32(value, DEFAULT_WIDTH),
            &atom!("height") => AttrValue::from_u32(value, DEFAULT_HEIGHT),
            _ => self.super_type().unwrap().parse_plain_attribute(name, value),
        }
    }
}

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

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

    pub fn request_image_from_cache(window: &Window, url: Url) -> ImageResponse {
        let image_cache = window.image_cache_thread();
        let (response_chan, response_port) = ipc::channel().unwrap();
        image_cache.request_image(url, ImageCacheChan(response_chan), None);
        let result = response_port.recv().unwrap();
        result.image_response
    }
}
<|endoftext|>


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

//! The service worker manager persists the descriptor of any registered service workers.
//! It also stores an active workers map, which holds descriptors of running service workers.
//! If an active service worker timeouts, then it removes the descriptor entry from its
//! active_workers map

use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg};
use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use dom::serviceworkerregistration::longest_prefix_match;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use net_traits::{CustomResponseMediator, CoreResourceMsg};
use script_traits::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders};
use std::collections::HashMap;
use std::sync::mpsc::{channel, Receiver};
use url::Url;
use util::thread::spawn_named;

pub struct ServiceWorkerManager {
    // map of registered service worker descriptors
    registered_workers: HashMap<Url, ScopeThings>,
    // map of active service worker descriptors
    active_workers: HashMap<Url, ScopeThings>,
    // own sender to send messages here
    own_sender: IpcSender<ServiceWorkerMsg>,
    // receiver to receive messages from constellation
    own_port: Receiver<ServiceWorkerMsg>,
    // to receive resource messages
    resource_receiver: Receiver<CustomResponseMediator>
}

impl ServiceWorkerManager {
    fn new(own_sender: IpcSender<ServiceWorkerMsg>,
           from_constellation_receiver: Receiver<ServiceWorkerMsg>,
           resource_port: Receiver<CustomResponseMediator>) -> ServiceWorkerManager {
        ServiceWorkerManager {
            registered_workers: HashMap::new(),
            active_workers: HashMap::new(),
            own_sender: own_sender,
            own_port: from_constellation_receiver,
            resource_receiver: resource_port
        }
    }

    pub fn spawn_manager(sw_senders: SWManagerSenders) {
        let (own_sender, from_constellation_receiver) = ipc::channel().unwrap();
        let (resource_chan, resource_port) = ipc::channel().unwrap();
        let from_constellation = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(from_constellation_receiver);
        let resource_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(resource_port);
        let _ = sw_senders.resource_sender.send(CoreResourceMsg::NetworkMediator(resource_chan));
        let _ = sw_senders.swmanager_sender.send(SWManagerMsg::OwnSender(own_sender.clone()));
        spawn_named("ServiceWorkerManager".to_owned(), move || {
            ServiceWorkerManager::new(own_sender, from_constellation, resource_port).handle_message();
        });
    }

    pub fn prepare_activation(&mut self, load_url: &Url) {
        let mut scope_url = None;
        for scope in self.registered_workers.keys() {
            if longest_prefix_match(&scope, load_url) {
                scope_url = Some(scope.clone());
                break;
            }
        }

        if let Some(ref scope_url) = scope_url {
            if self.active_workers.contains_key(&scope_url) {
                // do not run the same worker if already active.
                warn!("Service worker for {:?} already active", scope_url);
                return;
            }
            let scope_things = self.registered_workers.get(&scope_url);
            if let Some(scope_things) = scope_things {
                let (sender, receiver) = channel();
                let (devtools_sender, devtools_receiver) = ipc::channel().unwrap();
                if let Some(ref chan) = scope_things.devtools_chan {
                    let title = format!("ServiceWorker for {}", scope_things.script_url);
                    let page_info = DevtoolsPageInfo {
                        title: title,
                        url: scope_things.script_url.clone(),
                    };
                    let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal((scope_things.pipeline_id,
                                                                             Some(scope_things.worker_id)),
                                                                             devtools_sender.clone(),
                                                                             page_info));
                };
                ServiceWorkerGlobalScope::run_serviceworker_scope(scope_things.clone(),
                                                                              sender,
                                                                              receiver,
                                                                              devtools_receiver,
                                                                              self.own_sender.clone(),
                                                                              scope_url.clone());
                // We store the activated worker
                self.active_workers.insert(scope_url.clone(), scope_things.clone());
            } else {
                warn!("Unable to activate service worker");
            }
        }
    }

    fn handle_message(&mut self) {
       while self.receive_message() {
        // process message
       }
    }

    fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
        match msg {
            ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope) => {
                if self.registered_workers.contains_key(&scope) {
                    warn!("ScopeThings for {:?} already stored in SW-Manager", scope);
                } else {
                    self.registered_workers.insert(scope, scope_things);
                }
                true
            }
            ServiceWorkerMsg::Timeout(scope) => {
                if self.active_workers.contains_key(&scope) {
                    let _ = self.active_workers.remove(&scope);
                } else {
                    warn!("ScopeThings for {:?} is not active", scope);
                }
                true
            }
            ServiceWorkerMsg::Exit => false
        }
    }

    #[inline]
    fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
        self.prepare_activation(&mediator.load_url);
        // TODO XXXcreativcoder This mediator will need to be send to the appropriate service worker
        // so that it may do the sending of custom responses.
        // For now we just send a None from here itself
        let _ = mediator.response_chan.send(None);
        true
    }

    #[allow(unsafe_code)]
    fn receive_message(&mut self) -> bool {
        enum Message {
            FromResource(CustomResponseMediator),
            FromConstellation(ServiceWorkerMsg)
        }
        let message = {
            let msg_from_constellation = &self.own_port;
            let msg_from_resource = &self.resource_receiver;
            select! {
                msg = msg_from_constellation.recv() =>
                    Message::FromConstellation(msg.expect("Unexpected constellation channel panic in sw-manager")),
                msg = msg_from_resource.recv() =>
                    Message::FromResource(msg.expect("Unexpected resource channel panic in sw-manager"))
            }
        };
        match message {
            Message::FromConstellation(msg) => self.handle_message_from_constellation(msg),
            Message::FromResource(mediator) => self.handle_message_from_resource(mediator)
        }
    }
}
<commit_msg>Remove expect calls in service worker manager thread<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/. */

//! The service worker manager persists the descriptor of any registered service workers.
//! It also stores an active workers map, which holds descriptors of running service workers.
//! If an active service worker timeouts, then it removes the descriptor entry from its
//! active_workers map

use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg};
use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use dom::serviceworkerregistration::longest_prefix_match;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use net_traits::{CustomResponseMediator, CoreResourceMsg};
use script_traits::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders};
use std::collections::HashMap;
use std::sync::mpsc::{channel, Receiver, RecvError};
use url::Url;
use util::thread::spawn_named;

enum Message {
    FromResource(CustomResponseMediator),
    FromConstellation(ServiceWorkerMsg)
}

pub struct ServiceWorkerManager {
    // map of registered service worker descriptors
    registered_workers: HashMap<Url, ScopeThings>,
    // map of active service worker descriptors
    active_workers: HashMap<Url, ScopeThings>,
    // own sender to send messages here
    own_sender: IpcSender<ServiceWorkerMsg>,
    // receiver to receive messages from constellation
    own_port: Receiver<ServiceWorkerMsg>,
    // to receive resource messages
    resource_receiver: Receiver<CustomResponseMediator>
}

impl ServiceWorkerManager {
    fn new(own_sender: IpcSender<ServiceWorkerMsg>,
           from_constellation_receiver: Receiver<ServiceWorkerMsg>,
           resource_port: Receiver<CustomResponseMediator>) -> ServiceWorkerManager {
        ServiceWorkerManager {
            registered_workers: HashMap::new(),
            active_workers: HashMap::new(),
            own_sender: own_sender,
            own_port: from_constellation_receiver,
            resource_receiver: resource_port
        }
    }

    pub fn spawn_manager(sw_senders: SWManagerSenders) {
        let (own_sender, from_constellation_receiver) = ipc::channel().unwrap();
        let (resource_chan, resource_port) = ipc::channel().unwrap();
        let from_constellation = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(from_constellation_receiver);
        let resource_port = ROUTER.route_ipc_receiver_to_new_mpsc_receiver(resource_port);
        let _ = sw_senders.resource_sender.send(CoreResourceMsg::NetworkMediator(resource_chan));
        let _ = sw_senders.swmanager_sender.send(SWManagerMsg::OwnSender(own_sender.clone()));
        spawn_named("ServiceWorkerManager".to_owned(), move || {
            ServiceWorkerManager::new(own_sender, from_constellation, resource_port).handle_message();
        });
    }

    pub fn prepare_activation(&mut self, load_url: &Url) {
        let mut scope_url = None;
        for scope in self.registered_workers.keys() {
            if longest_prefix_match(&scope, load_url) {
                scope_url = Some(scope.clone());
                break;
            }
        }

        if let Some(ref scope_url) = scope_url {
            if self.active_workers.contains_key(&scope_url) {
                // do not run the same worker if already active.
                warn!("Service worker for {:?} already active", scope_url);
                return;
            }
            let scope_things = self.registered_workers.get(&scope_url);
            if let Some(scope_things) = scope_things {
                let (sender, receiver) = channel();
                let (devtools_sender, devtools_receiver) = ipc::channel().unwrap();
                if let Some(ref chan) = scope_things.devtools_chan {
                    let title = format!("ServiceWorker for {}", scope_things.script_url);
                    let page_info = DevtoolsPageInfo {
                        title: title,
                        url: scope_things.script_url.clone(),
                    };
                    let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal((scope_things.pipeline_id,
                                                                             Some(scope_things.worker_id)),
                                                                             devtools_sender.clone(),
                                                                             page_info));
                };
                ServiceWorkerGlobalScope::run_serviceworker_scope(scope_things.clone(),
                                                                              sender,
                                                                              receiver,
                                                                              devtools_receiver,
                                                                              self.own_sender.clone(),
                                                                              scope_url.clone());
                // We store the activated worker
                self.active_workers.insert(scope_url.clone(), scope_things.clone());
            } else {
                warn!("Unable to activate service worker");
            }
        }
    }

    fn handle_message(&mut self) {
        while let Ok(message) = self.receive_message() {
            let should_continue = match message {
                Message::FromConstellation(msg) => {
                    self.handle_message_from_constellation(msg)
                },
                Message::FromResource(msg) => {
                    self.handle_message_from_resource(msg)
                }
            };
            if !should_continue {
                break;
            }
        }
    }

    fn handle_message_from_constellation(&mut self, msg: ServiceWorkerMsg) -> bool {
        match msg {
            ServiceWorkerMsg::RegisterServiceWorker(scope_things, scope) => {
                if self.registered_workers.contains_key(&scope) {
                    warn!("ScopeThings for {:?} already stored in SW-Manager", scope);
                } else {
                    self.registered_workers.insert(scope, scope_things);
                }
                true
            }
            ServiceWorkerMsg::Timeout(scope) => {
                if self.active_workers.contains_key(&scope) {
                    let _ = self.active_workers.remove(&scope);
                } else {
                    warn!("ScopeThings for {:?} is not active", scope);
                }
                true
            }
            ServiceWorkerMsg::Exit => false
        }
    }

    #[inline]
    fn handle_message_from_resource(&mut self, mediator: CustomResponseMediator) -> bool {
        self.prepare_activation(&mediator.load_url);
        // TODO XXXcreativcoder This mediator will need to be send to the appropriate service worker
        // so that it may do the sending of custom responses.
        // For now we just send a None from here itself
        let _ = mediator.response_chan.send(None);
        true
    }

    #[allow(unsafe_code)]
    fn receive_message(&mut self) -> Result<Message, RecvError> {
        let msg_from_constellation = &self.own_port;
        let msg_from_resource = &self.resource_receiver;
        select! {
            msg = msg_from_constellation.recv() => msg.map(Message::FromConstellation),
            msg = msg_from_resource.recv() => msg.map(Message::FromResource)
        }
    }
}
<|endoftext|>


<commit_before>fn main() {
    println!("Hello, world!");
}
<commit_msg>Example of running loop in main and spawned thread and waiting for spawned thread to end<commit_after>use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("{} from spawned thread", i);
        }
    });

    for i in 1..5 {
        println!("{} from main thread", i);
    }

    handle.join();
}
<|endoftext|>


<commit_before>use std::fmt::{Debug, Formatter};
use std::fmt::Result as FMTResult;
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;

use clap::ArgMatches;
use regex::Regex;

mod header;

use module::Module;
use runtime::Runtime;
use storage::file::File;
use storage::parser::Parser;
use storage::json::parser::JsonHeaderParser;
use module::helpers::cli::create_tag_filter;
use module::helpers::cli::create_hash_filter;
use module::helpers::cli::create_text_header_field_grep_filter;
use module::helpers::cli::create_content_grep_filter;
use module::helpers::cli::CliFileFilter;

pub struct Notes<'a> {
    rt: &'a Runtime<'a>,
}

impl<'a> Notes<'a> {

    pub fn new(rt: &'a Runtime<'a>) -> Notes<'a> {
        Notes {
            rt: rt,
        }
    }

    fn command_add(&self, matches: &ArgMatches) -> bool {
        use std::process::exit;
        use ansi_term::Colour::Yellow;
        use self::header::build_header;
        use ui::external::editor::let_user_provide_content;

        let parser = Parser::new(JsonHeaderParser::new(None));
        let name   = matches.value_of("name")
                            .map(String::from)
                            .unwrap_or(String::from(""));
        let tags   = matches.value_of("tags")
                            .and_then(|s| Some(s.split(",").map(String::from).collect()))
                            .unwrap_or(vec![]);

        debug!("Building header with");
        debug!("    name = '{:?}'", name);
        debug!("    tags = '{:?}'", tags);
        let header = build_header(name, tags);

        let content = let_user_provide_content(self.runtime()).unwrap_or(String::from(""));

        let fileid = self.rt.store().new_file_with_content(self, header, content);
        self.rt
            .store()
            .load(self, &parser, &fileid)
            .and_then(|file| {
                info!("{}", Yellow.paint(format!("Created file in memory: {}", fileid)));
                Some(self.rt.store().persist(&parser, file))
            })
            .unwrap_or(false)
    }

    fn command_edit(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};
        use ui::external::editor::edit_content;

        let parser  = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let head_filter = create_text_header_field_grep_filter(matches, "namematch", "NAME", false);
            let text_filter = create_content_grep_filter(matches, "match", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(head_filter)).or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .into_iter()
            .filter(|f| filter.filter_file(f))
            .map(|file| {
                debug!("File loaded, can edit now: {:?}", file);

                let old_content = {
                    let f = file.deref().borrow();
                    f.data().clone()
                };

                debug!("Editing content now...");
                let (new_content, editing_worked) = edit_content(self.runtime(), old_content);
                debug!("... ready with editing");

                if editing_worked {
                    debug!("Editing worked");
                    {
                        let mut f = file.deref().borrow_mut();
                        f.set_data(new_content);
                    }
                    self.runtime().store().persist(&parser, file)
                } else {
                    debug!("Editing didn't work");
                    false
                }
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        info!("{}", Green.paint(format!("Editing succeeded for {} files", worked)));
        info!("{}", Red.paint(format!(  "Editing failed for {} files", failed)));

        return failed == 0;
    }

    fn command_list(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};
        use ui::file::{FilePrinter, TablePrinter};
        use self::header::get_name_from_header;
        use self::header::get_tags_from_header;
        use std::process::exit;
        use module::helpers::cli::CliFileFilter;

        let parser  = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", true);
            let head_filter = create_text_header_field_grep_filter(matches, "match", "NAME", true);
            let text_filter = create_content_grep_filter(matches, "match", true);
            let tags_filter = create_tag_filter(matches, "tags", true);
            hash_filter.or(Box::new(head_filter)).and(Box::new(text_filter)).and(Box::new(tags_filter))
        };

        let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());

        printer.print_files_custom(
            self.rt.store()
                    .load_for_module(self, &parser)
                    .into_iter()
                    .filter(|f| filter.filter_file(f)),
            &|file| {
                let fl      = file.deref().borrow();
                let hdr     = fl.header();
                let name    = get_name_from_header(hdr);
                let tags    = get_tags_from_header(hdr);

                debug!("Custom printer field: name = '{:?}'", name);
                debug!("Custom printer field: tags = '{:?}'", tags);

                vec![name, tags.join(", ")]
            }
        );
        true
    }

    fn command_links(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};
        use module::helpers::content::markdown::MarkdownParser;
        use ui::file::{FilePrinter, TablePrinter};
        use util::is_url;
        use prettytable::Table;
        use prettytable::row::Row;
        use prettytable::cell::Cell;
        use itertools::Itertools;

        debug!("Going to list links in files...");

        let list_intern = matches.is_present("internal");
        let list_extern = matches.is_present("external");
        debug!("list internal links = {}", list_intern);
        debug!("list external links = {}", list_extern);

        let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());
        let titles = row!["#", "Text", "Link", "Direction"];
        let mut table = Table::new();
        table.set_titles(titles);
        debug!("Table setup finished");

        let parser = Parser::new(JsonHeaderParser::new(None));
        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| {
                let res = filter.filter_file(file);
                debug!("Filter: {} -> {}", file.deref().borrow().id(), res);
                res
            })
            .map(|file| {
                debug!("File loaded, can parse for links now: {}", file.deref().borrow().id());
                let data = {
                    let f = file.deref().borrow();
                    debug!("Parsing markdown in file = {:?}", f);
                    f.data().clone()
                };
                let links = MarkdownParser::new(&data).links();
                debug!("Retreived {} links from {}", links.len(), file.deref().borrow().id());
                links
            })
            .flatten()
            .filter(|link| {
                let title       = &link.title;
                let url         = &link.url;
                let is_extern   = is_url(&url);
                debug!("Is external URL {} -> {}", url, is_extern);
                debug!("List external URLs -> {}", list_extern);
                debug!("List internal URLs -> {}", list_intern);
                ((!list_intern && !list_extern) ||
                 (is_extern && list_extern)     ||
                 (!is_extern && list_intern))
            })
            .enumerate()
            .map(|(i_link, link)| {
                let title   = &link.title;
                let url     = &link.url;
                let is_url  = is_url(&url);
                debug!("Listing: {} -> {}", title, url);

                let linkno_cell = Cell::new(&format!("{}", i_link)[..]);
                let title_cell  = Cell::new(&format!("{}", title)[..]);
                let url_cell    = Cell::new(&format!("{}", url)[..]);
                let dir_cell    = Cell::new(if is_url { "extern" } else { "intern" });

                let r = Row::new(vec![linkno_cell,
                                      title_cell,
                                      url_cell,
                                      dir_cell]);
                table.add_row(r);
                true
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        if worked != 0 {
            debug!("Printing table entries");
            table.printstd();
        } else {
            debug!("Not printing table as there wouldn't be any entries in it");
        }

        info!("{}", Green.paint(format!("Listing links succeeded for {} files", worked)));
        info!("{}", Red.paint(  format!("Listing links failed for {} files", failed)));

        return failed == 0;
    }

    fn command_remove(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};

        let parser = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("File loaded, can remove now: {:?}", file);
                let f = file.deref().borrow();
                self.rt.store().remove(f.id().clone())
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        info!("{}", Green.paint(format!("Removing succeeded for {} files", worked)));
        info!("{}", Red.paint(  format!("Removing failed for {} files", failed)));

        return failed == 0;
    }

    fn command_add_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            let mut new_tags = old_tags.clone();
            new_tags.append(&mut cli_tags.clone());
            new_tags
        }, rebuild_header_with_tags)
    }

    fn command_rm_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            old_tags.clone()
                .into_iter()
                .filter(|tag| !cli_tags.contains(tag))
                .collect()
        }, rebuild_header_with_tags)
    }

    fn command_set_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            cli_tags.clone()
        }, rebuild_header_with_tags)
    }

}

impl<'a> Module<'a> for Notes<'a> {

    fn exec(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::Red;

        match matches.subcommand_name() {
            Some("add") => {
                self.command_add(matches.subcommand_matches("add").unwrap())
            },

            Some("edit") => {
                self.command_edit(matches.subcommand_matches("edit").unwrap())
            },

            Some("list") => {
                self.command_list(matches.subcommand_matches("list").unwrap())
            },

            Some("links") => {
                self.command_links(matches.subcommand_matches("links").unwrap())
            },

            Some("remove") => {
                self.command_remove(matches.subcommand_matches("remove").unwrap())
            },

            Some("add_tags") => {
                self.command_add_tags(matches.subcommand_matches("add_tags").unwrap())
            },

            Some("rm_tags") => {
                self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap())
            },

            Some("set_tags") => {
                self.command_set_tags(matches.subcommand_matches("set_tags").unwrap())
            },

            Some(_) | None => {
                info!("{}", Red.bold().paint("No command given, doing nothing"));
                false
            },
        }
    }

    fn name(&self) -> &'static str{
        "notes"
    }

    fn runtime(&self) -> &Runtime {
        self.rt
    }

}

impl<'a> Debug for Notes<'a> {

    fn fmt(&self, fmt: &mut Formatter) -> FMTResult {
        write!(fmt, "[Module][Notes]");
        Ok(())
    }

}
<commit_msg>Add command_open stub<commit_after>use std::fmt::{Debug, Formatter};
use std::fmt::Result as FMTResult;
use std::rc::Rc;
use std::cell::RefCell;
use std::ops::Deref;

use clap::ArgMatches;
use regex::Regex;

mod header;

use module::Module;
use runtime::Runtime;
use storage::file::File;
use storage::parser::Parser;
use storage::json::parser::JsonHeaderParser;
use module::helpers::cli::create_tag_filter;
use module::helpers::cli::create_hash_filter;
use module::helpers::cli::create_text_header_field_grep_filter;
use module::helpers::cli::create_content_grep_filter;
use module::helpers::cli::CliFileFilter;

pub struct Notes<'a> {
    rt: &'a Runtime<'a>,
}

impl<'a> Notes<'a> {

    pub fn new(rt: &'a Runtime<'a>) -> Notes<'a> {
        Notes {
            rt: rt,
        }
    }

    fn command_add(&self, matches: &ArgMatches) -> bool {
        use std::process::exit;
        use ansi_term::Colour::Yellow;
        use self::header::build_header;
        use ui::external::editor::let_user_provide_content;

        let parser = Parser::new(JsonHeaderParser::new(None));
        let name   = matches.value_of("name")
                            .map(String::from)
                            .unwrap_or(String::from(""));
        let tags   = matches.value_of("tags")
                            .and_then(|s| Some(s.split(",").map(String::from).collect()))
                            .unwrap_or(vec![]);

        debug!("Building header with");
        debug!("    name = '{:?}'", name);
        debug!("    tags = '{:?}'", tags);
        let header = build_header(name, tags);

        let content = let_user_provide_content(self.runtime()).unwrap_or(String::from(""));

        let fileid = self.rt.store().new_file_with_content(self, header, content);
        self.rt
            .store()
            .load(self, &parser, &fileid)
            .and_then(|file| {
                info!("{}", Yellow.paint(format!("Created file in memory: {}", fileid)));
                Some(self.rt.store().persist(&parser, file))
            })
            .unwrap_or(false)
    }

    fn command_edit(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};
        use ui::external::editor::edit_content;

        let parser  = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let head_filter = create_text_header_field_grep_filter(matches, "namematch", "NAME", false);
            let text_filter = create_content_grep_filter(matches, "match", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(head_filter)).or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .into_iter()
            .filter(|f| filter.filter_file(f))
            .map(|file| {
                debug!("File loaded, can edit now: {:?}", file);

                let old_content = {
                    let f = file.deref().borrow();
                    f.data().clone()
                };

                debug!("Editing content now...");
                let (new_content, editing_worked) = edit_content(self.runtime(), old_content);
                debug!("... ready with editing");

                if editing_worked {
                    debug!("Editing worked");
                    {
                        let mut f = file.deref().borrow_mut();
                        f.set_data(new_content);
                    }
                    self.runtime().store().persist(&parser, file)
                } else {
                    debug!("Editing didn't work");
                    false
                }
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        info!("{}", Green.paint(format!("Editing succeeded for {} files", worked)));
        info!("{}", Red.paint(format!(  "Editing failed for {} files", failed)));

        return failed == 0;
    }

    fn command_open(&self, matches: &ArgMatches) -> bool {
        unimplemented!()
    }

    fn command_list(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};
        use ui::file::{FilePrinter, TablePrinter};
        use self::header::get_name_from_header;
        use self::header::get_tags_from_header;
        use std::process::exit;
        use module::helpers::cli::CliFileFilter;

        let parser  = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", true);
            let head_filter = create_text_header_field_grep_filter(matches, "match", "NAME", true);
            let text_filter = create_content_grep_filter(matches, "match", true);
            let tags_filter = create_tag_filter(matches, "tags", true);
            hash_filter.or(Box::new(head_filter)).and(Box::new(text_filter)).and(Box::new(tags_filter))
        };

        let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());

        printer.print_files_custom(
            self.rt.store()
                    .load_for_module(self, &parser)
                    .into_iter()
                    .filter(|f| filter.filter_file(f)),
            &|file| {
                let fl      = file.deref().borrow();
                let hdr     = fl.header();
                let name    = get_name_from_header(hdr);
                let tags    = get_tags_from_header(hdr);

                debug!("Custom printer field: name = '{:?}'", name);
                debug!("Custom printer field: tags = '{:?}'", tags);

                vec![name, tags.join(", ")]
            }
        );
        true
    }

    fn command_links(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};
        use module::helpers::content::markdown::MarkdownParser;
        use ui::file::{FilePrinter, TablePrinter};
        use util::is_url;
        use prettytable::Table;
        use prettytable::row::Row;
        use prettytable::cell::Cell;
        use itertools::Itertools;

        debug!("Going to list links in files...");

        let list_intern = matches.is_present("internal");
        let list_extern = matches.is_present("external");
        debug!("list internal links = {}", list_intern);
        debug!("list external links = {}", list_extern);

        let printer = TablePrinter::new(self.rt.is_verbose(), self.rt.is_debugging());
        let titles = row!["#", "Text", "Link", "Direction"];
        let mut table = Table::new();
        table.set_titles(titles);
        debug!("Table setup finished");

        let parser = Parser::new(JsonHeaderParser::new(None));
        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| {
                let res = filter.filter_file(file);
                debug!("Filter: {} -> {}", file.deref().borrow().id(), res);
                res
            })
            .map(|file| {
                debug!("File loaded, can parse for links now: {}", file.deref().borrow().id());
                let data = {
                    let f = file.deref().borrow();
                    debug!("Parsing markdown in file = {:?}", f);
                    f.data().clone()
                };
                let links = MarkdownParser::new(&data).links();
                debug!("Retreived {} links from {}", links.len(), file.deref().borrow().id());
                links
            })
            .flatten()
            .filter(|link| {
                let title       = &link.title;
                let url         = &link.url;
                let is_extern   = is_url(&url);
                debug!("Is external URL {} -> {}", url, is_extern);
                debug!("List external URLs -> {}", list_extern);
                debug!("List internal URLs -> {}", list_intern);
                ((!list_intern && !list_extern) ||
                 (is_extern && list_extern)     ||
                 (!is_extern && list_intern))
            })
            .enumerate()
            .map(|(i_link, link)| {
                let title   = &link.title;
                let url     = &link.url;
                let is_url  = is_url(&url);
                debug!("Listing: {} -> {}", title, url);

                let linkno_cell = Cell::new(&format!("{}", i_link)[..]);
                let title_cell  = Cell::new(&format!("{}", title)[..]);
                let url_cell    = Cell::new(&format!("{}", url)[..]);
                let dir_cell    = Cell::new(if is_url { "extern" } else { "intern" });

                let r = Row::new(vec![linkno_cell,
                                      title_cell,
                                      url_cell,
                                      dir_cell]);
                table.add_row(r);
                true
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        if worked != 0 {
            debug!("Printing table entries");
            table.printstd();
        } else {
            debug!("Not printing table as there wouldn't be any entries in it");
        }

        info!("{}", Green.paint(format!("Listing links succeeded for {} files", worked)));
        info!("{}", Red.paint(  format!("Listing links failed for {} files", failed)));

        return failed == 0;
    }

    fn command_remove(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::{Red, Green};

        let parser = Parser::new(JsonHeaderParser::new(None));

        let filter = {
            let hash_filter = create_hash_filter(matches, "id", false);
            let text_filter = create_text_header_field_grep_filter(matches, "match", "URL", false);
            let tags_filter = create_tag_filter(matches, "tags", false);
            hash_filter.or(Box::new(text_filter)).or(Box::new(tags_filter))
        };

        let result = self.rt
            .store()
            .load_for_module(self, &parser)
            .iter()
            .filter(|file| filter.filter_file(file))
            .map(|file| {
                debug!("File loaded, can remove now: {:?}", file);
                let f = file.deref().borrow();
                self.rt.store().remove(f.id().clone())
            })
            .fold((0, 0), |acc, succeeded| {
                let (worked, failed) = acc;
                if succeeded {
                    (worked + 1, failed)
                } else {
                    (worked, failed + 1)
                }
            });

        let (worked, failed) = result;

        info!("{}", Green.paint(format!("Removing succeeded for {} files", worked)));
        info!("{}", Red.paint(  format!("Removing failed for {} files", failed)));

        return failed == 0;
    }

    fn command_add_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            let mut new_tags = old_tags.clone();
            new_tags.append(&mut cli_tags.clone());
            new_tags
        }, rebuild_header_with_tags)
    }

    fn command_rm_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            old_tags.clone()
                .into_iter()
                .filter(|tag| !cli_tags.contains(tag))
                .collect()
        }, rebuild_header_with_tags)
    }

    fn command_set_tags(&self, matches: &ArgMatches) -> bool {
        use module::helpers::header::tags::data::alter_tags_in_files;
        use self::header::rebuild_header_with_tags;

        let parser = Parser::new(JsonHeaderParser::new(None));
        alter_tags_in_files(self, matches, &parser, |old_tags, cli_tags| {
            cli_tags.clone()
        }, rebuild_header_with_tags)
    }

}

impl<'a> Module<'a> for Notes<'a> {

    fn exec(&self, matches: &ArgMatches) -> bool {
        use ansi_term::Colour::Red;

        match matches.subcommand_name() {
            Some("add") => {
                self.command_add(matches.subcommand_matches("add").unwrap())
            },

            Some("edit") => {
                self.command_edit(matches.subcommand_matches("edit").unwrap())
            },

            Some("open") => {
                self.command_open(matches.subcommand_matches("open").unwrap())
            },

            Some("list") => {
                self.command_list(matches.subcommand_matches("list").unwrap())
            },

            Some("links") => {
                self.command_links(matches.subcommand_matches("links").unwrap())
            },

            Some("remove") => {
                self.command_remove(matches.subcommand_matches("remove").unwrap())
            },

            Some("add_tags") => {
                self.command_add_tags(matches.subcommand_matches("add_tags").unwrap())
            },

            Some("rm_tags") => {
                self.command_rm_tags(matches.subcommand_matches("rm_tags").unwrap())
            },

            Some("set_tags") => {
                self.command_set_tags(matches.subcommand_matches("set_tags").unwrap())
            },

            Some(_) | None => {
                info!("{}", Red.bold().paint("No command given, doing nothing"));
                false
            },
        }
    }

    fn name(&self) -> &'static str{
        "notes"
    }

    fn runtime(&self) -> &Runtime {
        self.rt
    }

}

impl<'a> Debug for Notes<'a> {

    fn fmt(&self, fmt: &mut Formatter) -> FMTResult {
        write!(fmt, "[Module][Notes]");
        Ok(())
    }

}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::env;
use std::fmt;
use std::iter::{FromIterator, IntoIterator};
use std::path::PathBuf;
use std::str::FromStr;
use std::vec::IntoIter;

use error::{Error, Result};
use package::PackageIdent;

#[cfg(not(windows))]
const ENV_PATH_SEPARATOR: char = ':';

#[cfg(windows)]
const ENV_PATH_SEPARATOR: char = ';';

pub fn parse_key_value(s: &str) -> Result<HashMap<String, String>> {
    Ok(HashMap::from_iter(
        s.lines()
            .map(|l| l.splitn(2, '=').collect::<Vec<_>>())
            .map(|kv| (kv[0].to_string(), kv[1].to_string())),
    ))
}

#[derive(Debug)]
pub struct Bind {
    pub service: String,
    pub exports: Vec<String>,
}

impl FromStr for Bind {
    type Err = Error;

    fn from_str(line: &str) -> Result<Self> {
        let mut parts = line.split('=');
        let service = match parts.next() {
            None => return Err(Error::MetaFileBadBind),
            Some(service) => service.to_string(),
        };
        let exports = match parts.next() {
            None => return Err(Error::MetaFileBadBind),
            Some(exports) => exports.split(' ').map(|t| t.to_string()).collect(),
        };
        Ok(Bind {
            service: service,
            exports: exports,
        })
    }
}

/// Describes a bind mapping in a composite package.
#[derive(Debug, PartialEq)]
pub struct BindMapping {
    /// The name of the bind of a given service.
    pub bind_name: String,
    /// The identifier of the service within the composite package
    /// that should satisfy the named bind.
    pub satisfying_service: PackageIdent,
}

impl FromStr for BindMapping {
    type Err = Error;

    fn from_str(line: &str) -> Result<Self> {
        let mut parts = line.split(':');
        let bind_name = parts.next().and_then(|bn| Some(bn.to_string())).ok_or(
            Error::MetaFileBadBind,
        )?;
        let satisfying_service = match parts.next() {
            None => return Err(Error::MetaFileBadBind),
            Some(satisfying_service) => satisfying_service.parse()?,
        };
        Ok(BindMapping {
            bind_name: bind_name,
            satisfying_service: satisfying_service,
        })
    }
}

#[derive(Debug, PartialEq)]
pub struct EnvVar {
    pub key: String,
    pub value: String,
    pub separator: Option<char>,
}

#[derive(Debug)]
pub struct PkgEnv {
    inner: Vec<EnvVar>,
}

impl PkgEnv {
    pub fn new(values: HashMap<String, String>, separators: HashMap<String, String>) -> Self {
        Self {
            inner: values
                .into_iter()
                .map(|(key, value)| if let Some(sep) = separators.get(&key) {
                    EnvVar {
                        key: key,
                        value: value,
                        separator: sep.to_owned().pop(),
                    }
                } else {
                    EnvVar {
                        key: key,
                        value: value,
                        separator: None,
                    }
                })
                .collect(),
        }
    }

    pub fn from_paths(paths: Vec<PathBuf>) -> Self {
        let p = env::join_paths(&paths).expect("Failed to build path string");
        Self {
            inner: vec![
                EnvVar {
                    key: "PATH".to_string(),
                    value: p.into_string().expect(
                        "Failed to convert path to utf8 string"
                    ),
                    separator: Some(ENV_PATH_SEPARATOR),
                },
            ],
        }
    }

    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }
}

impl IntoIterator for PkgEnv {
    type Item = EnvVar;
    type IntoIter = IntoIter<EnvVar>;

    fn into_iter(self) -> Self::IntoIter {
        self.inner.into_iter()
    }
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum MetaFile {
    BindMap, // Composite-only
    Binds,
    BindsOptional,
    CFlags,
    Config,
    Deps,
    Environment,
    EnvironmentSep,
    Exports,
    Exposes,
    Ident,
    LdFlags,
    LdRunPath,
    Manifest,
    Path,
    ResolvedServices, // Composite-only
    Services, // Composite-only
    SvcGroup,
    SvcUser,
    Target,
    TDeps,
    Type,
}

impl fmt::Display for MetaFile {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let id = match *self {
            MetaFile::BindMap => "BIND_MAP",
            MetaFile::Binds => "BINDS",
            MetaFile::BindsOptional => "BINDS_OPTIONAL",
            MetaFile::CFlags => "CFLAGS",
            MetaFile::Config => "default.toml",
            MetaFile::Deps => "DEPS",
            MetaFile::Environment => "ENVIRONMENT",
            MetaFile::EnvironmentSep => "ENVIRONMENT_SEP",
            MetaFile::Exports => "EXPORTS",
            MetaFile::Exposes => "EXPOSES",
            MetaFile::Ident => "IDENT",
            MetaFile::LdFlags => "LDFLAGS",
            MetaFile::LdRunPath => "LD_RUN_PATH",
            MetaFile::Manifest => "MANIFEST",
            MetaFile::Path => "PATH",
            MetaFile::ResolvedServices => "RESOLVED_SERVICES",
            MetaFile::Services => "SERVICES",
            MetaFile::SvcGroup => "SVC_GROUP",
            MetaFile::SvcUser => "SVC_USER",
            MetaFile::Target => "TARGET",
            MetaFile::TDeps => "TDEPS",
            MetaFile::Type => "TYPE",
        };
        write!(f, "{}", id)
    }
}

pub enum PackageType {
    Standalone,
    Composite,
}

impl fmt::Display for PackageType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let id = match *self {
            PackageType::Standalone => "Standalone",
            PackageType::Composite => "Composite",
        };
        write!(f, "{}", id)
    }
}

impl FromStr for PackageType {
    type Err = Error;

    fn from_str(value: &str) -> Result<Self> {
        match value.as_ref() {
            "standalone" => Ok(PackageType::Standalone),
            "composite" => Ok(PackageType::Composite),
            _ => Err(Error::InvalidPackageType(value.to_string())),
        }
    }
}

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

    static ENVIRONMENT: &str = r#"PATH=/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin
PYTHONPATH=/hab/pkgs/python/setuptools/35.0.1/20170424072606/lib/python3.6/site-packages
"#;
    static ENVIRONMENT_SEP: &str = r#"PATH=:
PYTHONPATH=:
"#;
    static EXPORTS: &str = r#"status-port=status.port
port=front-end.port
"#;
    static PATH: &str = "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin";

    #[test]
    #[should_panic]
    fn malformed_file() {
        parse_key_value(&"PATH").unwrap();
    }

    #[test]
    fn can_parse_environment_file() {
        let mut m: HashMap<String, String> = HashMap::new();
        m.insert(
            "PATH".to_string(),
            "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin".to_string(),
        );
        m.insert(
            "PYTHONPATH".to_string(),
            "/hab/pkgs/python/setuptools/35.0.1/20170424072606/lib/python3.6/site-packages"
                .to_string(),
        );

        assert_eq!(parse_key_value(&ENVIRONMENT).unwrap(), m);
    }

    #[test]
    fn can_parse_environment_sep_file() {
        let mut m: HashMap<String, String> = HashMap::new();
        m.insert("PATH".to_string(), ":".to_string());
        m.insert("PYTHONPATH".to_string(), ":".to_string());

        assert_eq!(parse_key_value(&ENVIRONMENT_SEP).unwrap(), m);
    }

    #[test]
    fn can_parse_exports_file() {
        let mut m: HashMap<String, String> = HashMap::new();
        m.insert("status-port".to_string(), "status.port".to_string());
        m.insert("port".to_string(), "front-end.port".to_string());

        assert_eq!(parse_key_value(&EXPORTS).unwrap(), m);
    }

    #[test]
    fn build_pkg_env() {
        let mut result = PkgEnv::new(
            parse_key_value(&ENVIRONMENT).unwrap(),
            parse_key_value(&ENVIRONMENT_SEP).unwrap(),
        ).into_iter()
            .collect::<Vec<_>>();
        // Sort the result by key, so we have a guarantee of order
        result.sort_by_key(|v| v.key.to_owned());

        let expected = vec![
            EnvVar {
                key: "PATH".to_string(),
                value: "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin".to_string(),
                separator: Some(':'),
            },
            EnvVar {
                key: "PYTHONPATH".to_string(),
                value: "/hab/pkgs/python/setuptools/35.0.1/20170424072606/lib/python3.6/site-packages"
                    .to_string(),
                separator: Some(':'),
            },
        ];

        assert_eq!(result, expected);
    }

    #[test]
    fn build_pkg_env_is_empty() {
        let result = PkgEnv::new(HashMap::new(), HashMap::new());
        assert!(result.is_empty());
    }

    #[test]
    fn build_pkg_env_from_path() {
        let result = PkgEnv::from_paths(vec![PathBuf::from(PATH)])
            .into_iter()
            .collect::<Vec<_>>();

        let expected = vec![
            EnvVar {
                key: "PATH".to_string(),
                value: "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin".to_string(),
                separator: Some(ENV_PATH_SEPARATOR),
            },
        ];

        assert_eq!(result, expected);
    }

    #[test]
    fn can_parse_a_valid_bind_mapping() {
        let input = "my_bind:core/test";

        let output: BindMapping = input.parse().unwrap();

        assert_eq!(output.bind_name, "my_bind");
        assert_eq!(
            output.satisfying_service,
            PackageIdent::from_str("core/test").unwrap()
        );
    }

    #[test]
    fn fails_to_parse_a_bind_mapping_with_an_invalid_service_identifier() {
        let input = "my_bind:this-is-a-bad-identifier";
        let output = input.parse::<BindMapping>();
        assert!(output.is_err());
    }
}
<commit_msg>Adds hab pkg binds command to list package bindings<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::collections::HashMap;
use std::env;
use std::fmt;
use std::iter::{FromIterator, IntoIterator};
use std::path::PathBuf;
use std::str::FromStr;
use std::vec::IntoIter;

use error::{Error, Result};
use package::PackageIdent;

#[cfg(not(windows))]
const ENV_PATH_SEPARATOR: char = ':';

#[cfg(windows)]
const ENV_PATH_SEPARATOR: char = ';';

pub fn parse_key_value(s: &str) -> Result<HashMap<String, String>> {
    Ok(HashMap::from_iter(
        s.lines()
            .map(|l| l.splitn(2, '=').collect::<Vec<_>>())
            .map(|kv| (kv[0].to_string(), kv[1].to_string())),
    ))
}

#[derive(Debug)]
pub struct Bind {
    pub service: String,
    pub exports: Vec<String>,
}

impl FromStr for Bind {
    type Err = Error;

    fn from_str(line: &str) -> Result<Self> {
        let mut parts = line.split('=');
        let service = match parts.next() {
            None => return Err(Error::MetaFileBadBind),
            Some(service) => service.to_string(),
        };
        let exports = match parts.next() {
            None => return Err(Error::MetaFileBadBind),
            Some(exports) => exports.split(' ').map(|t| t.to_string()).collect(),
        };
        Ok(Bind {
            service: service,
            exports: exports,
        })
    }
}

impl fmt::Display for Bind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let formatted_exports = self.exports.join(" ");
        write!(f, "[{}]={}", self.service, formatted_exports)
    }
}

/// Describes a bind mapping in a composite package.
#[derive(Debug, PartialEq)]
pub struct BindMapping {
    /// The name of the bind of a given service.
    pub bind_name: String,
    /// The identifier of the service within the composite package
    /// that should satisfy the named bind.
    pub satisfying_service: PackageIdent,
}

impl FromStr for BindMapping {
    type Err = Error;

    fn from_str(line: &str) -> Result<Self> {
        let mut parts = line.split(':');
        let bind_name = parts.next().and_then(|bn| Some(bn.to_string())).ok_or(
            Error::MetaFileBadBind,
        )?;
        let satisfying_service = match parts.next() {
            None => return Err(Error::MetaFileBadBind),
            Some(satisfying_service) => satisfying_service.parse()?,
        };
        Ok(BindMapping {
            bind_name: bind_name,
            satisfying_service: satisfying_service,
        })
    }
}

#[derive(Debug, PartialEq)]
pub struct EnvVar {
    pub key: String,
    pub value: String,
    pub separator: Option<char>,
}

#[derive(Debug)]
pub struct PkgEnv {
    inner: Vec<EnvVar>,
}

impl PkgEnv {
    pub fn new(values: HashMap<String, String>, separators: HashMap<String, String>) -> Self {
        Self {
            inner: values
                .into_iter()
                .map(|(key, value)| if let Some(sep) = separators.get(&key) {
                    EnvVar {
                        key: key,
                        value: value,
                        separator: sep.to_owned().pop(),
                    }
                } else {
                    EnvVar {
                        key: key,
                        value: value,
                        separator: None,
                    }
                })
                .collect(),
        }
    }

    pub fn from_paths(paths: Vec<PathBuf>) -> Self {
        let p = env::join_paths(&paths).expect("Failed to build path string");
        Self {
            inner: vec![
                EnvVar {
                    key: "PATH".to_string(),
                    value: p.into_string().expect(
                        "Failed to convert path to utf8 string"
                    ),
                    separator: Some(ENV_PATH_SEPARATOR),
                },
            ],
        }
    }

    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }
}

impl IntoIterator for PkgEnv {
    type Item = EnvVar;
    type IntoIter = IntoIter<EnvVar>;

    fn into_iter(self) -> Self::IntoIter {
        self.inner.into_iter()
    }
}

#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum MetaFile {
    BindMap, // Composite-only
    Binds,
    BindsOptional,
    CFlags,
    Config,
    Deps,
    Environment,
    EnvironmentSep,
    Exports,
    Exposes,
    Ident,
    LdFlags,
    LdRunPath,
    Manifest,
    Path,
    ResolvedServices, // Composite-only
    Services, // Composite-only
    SvcGroup,
    SvcUser,
    Target,
    TDeps,
    Type,
}

impl fmt::Display for MetaFile {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let id = match *self {
            MetaFile::BindMap => "BIND_MAP",
            MetaFile::Binds => "BINDS",
            MetaFile::BindsOptional => "BINDS_OPTIONAL",
            MetaFile::CFlags => "CFLAGS",
            MetaFile::Config => "default.toml",
            MetaFile::Deps => "DEPS",
            MetaFile::Environment => "ENVIRONMENT",
            MetaFile::EnvironmentSep => "ENVIRONMENT_SEP",
            MetaFile::Exports => "EXPORTS",
            MetaFile::Exposes => "EXPOSES",
            MetaFile::Ident => "IDENT",
            MetaFile::LdFlags => "LDFLAGS",
            MetaFile::LdRunPath => "LD_RUN_PATH",
            MetaFile::Manifest => "MANIFEST",
            MetaFile::Path => "PATH",
            MetaFile::ResolvedServices => "RESOLVED_SERVICES",
            MetaFile::Services => "SERVICES",
            MetaFile::SvcGroup => "SVC_GROUP",
            MetaFile::SvcUser => "SVC_USER",
            MetaFile::Target => "TARGET",
            MetaFile::TDeps => "TDEPS",
            MetaFile::Type => "TYPE",
        };
        write!(f, "{}", id)
    }
}

pub enum PackageType {
    Standalone,
    Composite,
}

impl fmt::Display for PackageType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let id = match *self {
            PackageType::Standalone => "Standalone",
            PackageType::Composite => "Composite",
        };
        write!(f, "{}", id)
    }
}

impl FromStr for PackageType {
    type Err = Error;

    fn from_str(value: &str) -> Result<Self> {
        match value.as_ref() {
            "standalone" => Ok(PackageType::Standalone),
            "composite" => Ok(PackageType::Composite),
            _ => Err(Error::InvalidPackageType(value.to_string())),
        }
    }
}

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

    static ENVIRONMENT: &str = r#"PATH=/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin
PYTHONPATH=/hab/pkgs/python/setuptools/35.0.1/20170424072606/lib/python3.6/site-packages
"#;
    static ENVIRONMENT_SEP: &str = r#"PATH=:
PYTHONPATH=:
"#;
    static EXPORTS: &str = r#"status-port=status.port
port=front-end.port
"#;
    static PATH: &str = "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin";

    #[test]
    #[should_panic]
    fn malformed_file() {
        parse_key_value(&"PATH").unwrap();
    }

    #[test]
    fn can_parse_environment_file() {
        let mut m: HashMap<String, String> = HashMap::new();
        m.insert(
            "PATH".to_string(),
            "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin".to_string(),
        );
        m.insert(
            "PYTHONPATH".to_string(),
            "/hab/pkgs/python/setuptools/35.0.1/20170424072606/lib/python3.6/site-packages"
                .to_string(),
        );

        assert_eq!(parse_key_value(&ENVIRONMENT).unwrap(), m);
    }

    #[test]
    fn can_parse_environment_sep_file() {
        let mut m: HashMap<String, String> = HashMap::new();
        m.insert("PATH".to_string(), ":".to_string());
        m.insert("PYTHONPATH".to_string(), ":".to_string());

        assert_eq!(parse_key_value(&ENVIRONMENT_SEP).unwrap(), m);
    }

    #[test]
    fn can_parse_exports_file() {
        let mut m: HashMap<String, String> = HashMap::new();
        m.insert("status-port".to_string(), "status.port".to_string());
        m.insert("port".to_string(), "front-end.port".to_string());

        assert_eq!(parse_key_value(&EXPORTS).unwrap(), m);
    }

    #[test]
    fn build_pkg_env() {
        let mut result = PkgEnv::new(
            parse_key_value(&ENVIRONMENT).unwrap(),
            parse_key_value(&ENVIRONMENT_SEP).unwrap(),
        ).into_iter()
            .collect::<Vec<_>>();
        // Sort the result by key, so we have a guarantee of order
        result.sort_by_key(|v| v.key.to_owned());

        let expected = vec![
            EnvVar {
                key: "PATH".to_string(),
                value: "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin".to_string(),
                separator: Some(':'),
            },
            EnvVar {
                key: "PYTHONPATH".to_string(),
                value: "/hab/pkgs/python/setuptools/35.0.1/20170424072606/lib/python3.6/site-packages"
                    .to_string(),
                separator: Some(':'),
            },
        ];

        assert_eq!(result, expected);
    }

    #[test]
    fn build_pkg_env_is_empty() {
        let result = PkgEnv::new(HashMap::new(), HashMap::new());
        assert!(result.is_empty());
    }

    #[test]
    fn build_pkg_env_from_path() {
        let result = PkgEnv::from_paths(vec![PathBuf::from(PATH)])
            .into_iter()
            .collect::<Vec<_>>();

        let expected = vec![
            EnvVar {
                key: "PATH".to_string(),
                value: "/hab/pkgs/python/setuptools/35.0.1/20170424072606/bin".to_string(),
                separator: Some(ENV_PATH_SEPARATOR),
            },
        ];

        assert_eq!(result, expected);
    }

    #[test]
    fn can_parse_a_valid_bind_mapping() {
        let input = "my_bind:core/test";

        let output: BindMapping = input.parse().unwrap();

        assert_eq!(output.bind_name, "my_bind");
        assert_eq!(
            output.satisfying_service,
            PackageIdent::from_str("core/test").unwrap()
        );
    }

    #[test]
    fn fails_to_parse_a_bind_mapping_with_an_invalid_service_identifier() {
        let input = "my_bind:this-is-a-bad-identifier";
        let output = input.parse::<BindMapping>();
        assert!(output.is_err());
    }
}
<|endoftext|>