<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 byteorder::{LittleEndian, WriteBytesExt};
use euclid::{Point2D, Size2D};
use ipc_channel::ipc::{self, IpcBytesSender, IpcSender};
use offscreen_gl_context::{GLContextAttributes, GLLimits};
use std::cell::Cell;
use {ApiMsg, AuxiliaryLists, BuiltDisplayList, ColorF, DisplayListId, Epoch};
use {FontKey, IdNamespace, ImageFormat, ImageKey, NativeFontHandle, PipelineId};
use {RenderApiSender, ResourceId, ScrollEventPhase, ScrollLayerState};
use {StackingContext, StackingContextId, WebGLContextId, WebGLCommand};

impl RenderApiSender {
    pub fn new(api_sender: IpcSender<ApiMsg>,
               payload_sender: IpcBytesSender)
               -> RenderApiSender {
        RenderApiSender {
            api_sender: api_sender,
            payload_sender: payload_sender,
        }
    }

    pub fn create_api(&self) -> RenderApi {
        let RenderApiSender {
            ref api_sender,
            ref payload_sender
        } = *self;
        let (sync_tx, sync_rx) = ipc::channel().unwrap();
        let msg = ApiMsg::CloneApi(sync_tx);
        api_sender.send(msg).unwrap();
        RenderApi {
            api_sender: api_sender.clone(),
            payload_sender: payload_sender.clone(),
            id_namespace: sync_rx.recv().unwrap(),
            next_id: Cell::new(ResourceId(0)),
        }
    }
}

pub struct RenderApi {
    pub api_sender: IpcSender<ApiMsg>,
    pub payload_sender: IpcBytesSender,
    pub id_namespace: IdNamespace,
    pub next_id: Cell<ResourceId>,
}

impl RenderApi {
    pub fn clone_sender(&self) -> RenderApiSender {
        RenderApiSender::new(self.api_sender.clone(), self.payload_sender.clone())
    }

    pub fn add_raw_font(&self, bytes: Vec<u8>) -> FontKey {
        let new_id = self.next_unique_id();
        let key = FontKey::new(new_id.0, new_id.1);
        let msg = ApiMsg::AddRawFont(key, bytes);
        self.api_sender.send(msg).unwrap();
        key
    }

    pub fn add_native_font(&self, native_font_handle: NativeFontHandle) -> FontKey {
        let new_id = self.next_unique_id();
        let key = FontKey::new(new_id.0, new_id.1);
        let msg = ApiMsg::AddNativeFont(key, native_font_handle);
        self.api_sender.send(msg).unwrap();
        key
    }

    pub fn alloc_image(&self) -> ImageKey {
        let new_id = self.next_unique_id();
        ImageKey::new(new_id.0, new_id.1)
    }

    pub fn add_image(&self,
                     width: u32,
                     height: u32,
                     format: ImageFormat,
                     bytes: Vec<u8>) -> ImageKey {
        let new_id = self.next_unique_id();
        let key = ImageKey::new(new_id.0, new_id.1);
        let msg = ApiMsg::AddImage(key, width, height, format, bytes);
        self.api_sender.send(msg).unwrap();
        key
    }

    // TODO: Support changing dimensions (and format) during image update?
    pub fn update_image(&self,
                        key: ImageKey,
                        width: u32,
                        height: u32,
                        format: ImageFormat,
                        bytes: Vec<u8>) {
        let msg = ApiMsg::UpdateImage(key, width, height, format, bytes);
        self.api_sender.send(msg).unwrap();
    }

    pub fn delete_image(&self, key: ImageKey) {
        let msg = ApiMsg::DeleteImage(key);
        self.api_sender.send(msg).unwrap();
    }

    pub fn set_root_pipeline(&self, pipeline_id: PipelineId) {
        let msg = ApiMsg::SetRootPipeline(pipeline_id);
        self.api_sender.send(msg).unwrap();
    }

    /// Supplies a new frame to WebRender.
    ///
    /// Arguments:
    /// * `stacking_context_id`: The ID of the root stacking context.
    /// * `background_color`: The background color of this pipeline.
    /// * `epoch`: A monotonically increasing timestamp.
    /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
    /// * `viewport_size`: The size of the viewport for this frame.
    /// * `stacking_contexts`: Stacking contexts used in this frame.
    /// * `display_lists`: Display lists used in this frame.
    /// * `auxiliary_lists`: Various items that the display lists and stacking contexts reference.
    pub fn set_root_stacking_context(&self,
                                     stacking_context_id: StackingContextId,
                                     background_color: ColorF,
                                     epoch: Epoch,
                                     pipeline_id: PipelineId,
                                     viewport_size: Size2D<f32>,
                                     stacking_contexts: Vec<(StackingContextId, StackingContext)>,
                                     display_lists: Vec<(DisplayListId, BuiltDisplayList)>,
                                     auxiliary_lists: AuxiliaryLists) {
        let display_list_descriptors = display_lists.iter().map(|&(display_list_id,
                                                                   ref built_display_list)| {
            (display_list_id, (*built_display_list.descriptor()).clone())
        }).collect();
        let msg = ApiMsg::SetRootStackingContext(stacking_context_id,
                                                 background_color,
                                                 epoch,
                                                 pipeline_id,
                                                 viewport_size,
                                                 stacking_contexts,
                                                 display_list_descriptors,
                                                 *auxiliary_lists.descriptor());
        self.api_sender.send(msg).unwrap();

        let mut payload = vec![];
        payload.write_u32::<LittleEndian>(stacking_context_id.0).unwrap();
        payload.write_u32::<LittleEndian>(epoch.0).unwrap();

        for &(_, ref built_display_list) in &display_lists {
            payload.extend_from_slice(built_display_list.data());
        }
        payload.extend_from_slice(auxiliary_lists.data());

        self.payload_sender.send(&payload[..]).unwrap();
    }

    pub fn scroll(&self, delta: Point2D<f32>, cursor: Point2D<f32>, phase: ScrollEventPhase) {
        let msg = ApiMsg::Scroll(delta, cursor, phase);
        self.api_sender.send(msg).unwrap();
    }

    pub fn tick_scrolling_bounce_animations(&self) {
        let msg = ApiMsg::TickScrollingBounce;
        self.api_sender.send(msg).unwrap();
    }

    pub fn translate_point_to_layer_space(&self, point: &Point2D<f32>)
                                          -> (Point2D<f32>, PipelineId) {
        let (tx, rx) = ipc::channel().unwrap();
        let msg = ApiMsg::TranslatePointToLayerSpace(*point, tx);
        self.api_sender.send(msg).unwrap();
        rx.recv().unwrap()
    }

    pub fn get_scroll_layer_state(&self) -> Vec<ScrollLayerState> {
        let (tx, rx) = ipc::channel().unwrap();
        let msg = ApiMsg::GetScrollLayerState(tx);
        self.api_sender.send(msg).unwrap();
        rx.recv().unwrap()
    }

    pub fn request_webgl_context(&self, size: &Size2D<i32>, attributes: GLContextAttributes)
                                 -> Result<(WebGLContextId, GLLimits), String> {
        let (tx, rx) = ipc::channel().unwrap();
        let msg = ApiMsg::RequestWebGLContext(*size, attributes, tx);
        self.api_sender.send(msg).unwrap();
        rx.recv().unwrap()
    }

    pub fn send_webgl_command(&self, context_id: WebGLContextId, command: WebGLCommand) {
        let msg = ApiMsg::WebGLCommand(context_id, command);
        self.api_sender.send(msg).unwrap();
    }

    #[inline]
    pub fn next_stacking_context_id(&self) -> StackingContextId {
        let new_id = self.next_unique_id();
        StackingContextId(new_id.0, new_id.1)
    }

    #[inline]
    pub fn next_display_list_id(&self) -> DisplayListId {
        let new_id = self.next_unique_id();
        DisplayListId(new_id.0, new_id.1)
    }

    #[inline]
    fn next_unique_id(&self) -> (u32, u32) {
        let IdNamespace(namespace) = self.id_namespace;
        let ResourceId(id) = self.next_id.get();
        self.next_id.set(ResourceId(id + 1));
        (namespace, id)
    }
}

<commit_msg>Fix Argument List<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 byteorder::{LittleEndian, WriteBytesExt};
use euclid::{Point2D, Size2D};
use ipc_channel::ipc::{self, IpcBytesSender, IpcSender};
use offscreen_gl_context::{GLContextAttributes, GLLimits};
use std::cell::Cell;
use {ApiMsg, AuxiliaryLists, BuiltDisplayList, ColorF, DisplayListId, Epoch};
use {FontKey, IdNamespace, ImageFormat, ImageKey, NativeFontHandle, PipelineId};
use {RenderApiSender, ResourceId, ScrollEventPhase, ScrollLayerState};
use {StackingContext, StackingContextId, WebGLContextId, WebGLCommand};

impl RenderApiSender {
    pub fn new(api_sender: IpcSender<ApiMsg>,
               payload_sender: IpcBytesSender)
               -> RenderApiSender {
        RenderApiSender {
            api_sender: api_sender,
            payload_sender: payload_sender,
        }
    }

    pub fn create_api(&self) -> RenderApi {
        let RenderApiSender {
            ref api_sender,
            ref payload_sender
        } = *self;
        let (sync_tx, sync_rx) = ipc::channel().unwrap();
        let msg = ApiMsg::CloneApi(sync_tx);
        api_sender.send(msg).unwrap();
        RenderApi {
            api_sender: api_sender.clone(),
            payload_sender: payload_sender.clone(),
            id_namespace: sync_rx.recv().unwrap(),
            next_id: Cell::new(ResourceId(0)),
        }
    }
}

pub struct RenderApi {
    pub api_sender: IpcSender<ApiMsg>,
    pub payload_sender: IpcBytesSender,
    pub id_namespace: IdNamespace,
    pub next_id: Cell<ResourceId>,
}

impl RenderApi {
    pub fn clone_sender(&self) -> RenderApiSender {
        RenderApiSender::new(self.api_sender.clone(), self.payload_sender.clone())
    }

    pub fn add_raw_font(&self, bytes: Vec<u8>) -> FontKey {
        let new_id = self.next_unique_id();
        let key = FontKey::new(new_id.0, new_id.1);
        let msg = ApiMsg::AddRawFont(key, bytes);
        self.api_sender.send(msg).unwrap();
        key
    }

    pub fn add_native_font(&self, native_font_handle: NativeFontHandle) -> FontKey {
        let new_id = self.next_unique_id();
        let key = FontKey::new(new_id.0, new_id.1);
        let msg = ApiMsg::AddNativeFont(key, native_font_handle);
        self.api_sender.send(msg).unwrap();
        key
    }

    pub fn alloc_image(&self) -> ImageKey {
        let new_id = self.next_unique_id();
        ImageKey::new(new_id.0, new_id.1)
    }

    pub fn add_image(&self,
                     width: u32,
                     height: u32,
                     format: ImageFormat,
                     bytes: Vec<u8>) -> ImageKey {
        let new_id = self.next_unique_id();
        let key = ImageKey::new(new_id.0, new_id.1);
        let msg = ApiMsg::AddImage(key, width, height, format, bytes);
        self.api_sender.send(msg).unwrap();
        key
    }

    // TODO: Support changing dimensions (and format) during image update?
    pub fn update_image(&self,
                        key: ImageKey,
                        width: u32,
                        height: u32,
                        format: ImageFormat,
                        bytes: Vec<u8>) {
        let msg = ApiMsg::UpdateImage(key, width, height, format, bytes);
        self.api_sender.send(msg).unwrap();
    }

    pub fn delete_image(&self, key: ImageKey) {
        let msg = ApiMsg::DeleteImage(key);
        self.api_sender.send(msg).unwrap();
    }

    pub fn set_root_pipeline(&self, pipeline_id: PipelineId) {
        let msg = ApiMsg::SetRootPipeline(pipeline_id);
        self.api_sender.send(msg).unwrap();
    }

    /// Supplies a new frame to WebRender.
    ///
    /// Arguments:
    ///
    /// * `stacking_context_id`: The ID of the root stacking context.
    /// * `background_color`: The background color of this pipeline.
    /// * `epoch`: A monotonically increasing timestamp.
    /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
    /// * `viewport_size`: The size of the viewport for this frame.
    /// * `stacking_contexts`: Stacking contexts used in this frame.
    /// * `display_lists`: Display lists used in this frame.
    /// * `auxiliary_lists`: Various items that the display lists and stacking contexts reference.
    pub fn set_root_stacking_context(&self,
                                     stacking_context_id: StackingContextId,
                                     background_color: ColorF,
                                     epoch: Epoch,
                                     pipeline_id: PipelineId,
                                     viewport_size: Size2D<f32>,
                                     stacking_contexts: Vec<(StackingContextId, StackingContext)>,
                                     display_lists: Vec<(DisplayListId, BuiltDisplayList)>,
                                     auxiliary_lists: AuxiliaryLists) {
        let display_list_descriptors = display_lists.iter().map(|&(display_list_id,
                                                                   ref built_display_list)| {
            (display_list_id, (*built_display_list.descriptor()).clone())
        }).collect();
        let msg = ApiMsg::SetRootStackingContext(stacking_context_id,
                                                 background_color,
                                                 epoch,
                                                 pipeline_id,
                                                 viewport_size,
                                                 stacking_contexts,
                                                 display_list_descriptors,
                                                 *auxiliary_lists.descriptor());
        self.api_sender.send(msg).unwrap();

        let mut payload = vec![];
        payload.write_u32::<LittleEndian>(stacking_context_id.0).unwrap();
        payload.write_u32::<LittleEndian>(epoch.0).unwrap();

        for &(_, ref built_display_list) in &display_lists {
            payload.extend_from_slice(built_display_list.data());
        }
        payload.extend_from_slice(auxiliary_lists.data());

        self.payload_sender.send(&payload[..]).unwrap();
    }

    pub fn scroll(&self, delta: Point2D<f32>, cursor: Point2D<f32>, phase: ScrollEventPhase) {
        let msg = ApiMsg::Scroll(delta, cursor, phase);
        self.api_sender.send(msg).unwrap();
    }

    pub fn tick_scrolling_bounce_animations(&self) {
        let msg = ApiMsg::TickScrollingBounce;
        self.api_sender.send(msg).unwrap();
    }

    pub fn translate_point_to_layer_space(&self, point: &Point2D<f32>)
                                          -> (Point2D<f32>, PipelineId) {
        let (tx, rx) = ipc::channel().unwrap();
        let msg = ApiMsg::TranslatePointToLayerSpace(*point, tx);
        self.api_sender.send(msg).unwrap();
        rx.recv().unwrap()
    }

    pub fn get_scroll_layer_state(&self) -> Vec<ScrollLayerState> {
        let (tx, rx) = ipc::channel().unwrap();
        let msg = ApiMsg::GetScrollLayerState(tx);
        self.api_sender.send(msg).unwrap();
        rx.recv().unwrap()
    }

    pub fn request_webgl_context(&self, size: &Size2D<i32>, attributes: GLContextAttributes)
                                 -> Result<(WebGLContextId, GLLimits), String> {
        let (tx, rx) = ipc::channel().unwrap();
        let msg = ApiMsg::RequestWebGLContext(*size, attributes, tx);
        self.api_sender.send(msg).unwrap();
        rx.recv().unwrap()
    }

    pub fn send_webgl_command(&self, context_id: WebGLContextId, command: WebGLCommand) {
        let msg = ApiMsg::WebGLCommand(context_id, command);
        self.api_sender.send(msg).unwrap();
    }

    #[inline]
    pub fn next_stacking_context_id(&self) -> StackingContextId {
        let new_id = self.next_unique_id();
        StackingContextId(new_id.0, new_id.1)
    }

    #[inline]
    pub fn next_display_list_id(&self) -> DisplayListId {
        let new_id = self.next_unique_id();
        DisplayListId(new_id.0, new_id.1)
    }

    #[inline]
    fn next_unique_id(&self) -> (u32, u32) {
        let IdNamespace(namespace) = self.id_namespace;
        let ResourceId(id) = self.next_id.get();
        self.next_id.set(ResourceId(id + 1));
        (namespace, id)
    }
}
<|endoftext|>


<commit_before>use std;
import io::{writer_util, reader_util};
import std::map;
import map::hashmap;
import result;

export rowreader, rowaccess, rowiter,
       new_reader, new_reader_readlen,
       hashmap_iter, hashmap_iter_full;

enum state {
    fieldstart(bool),
    infield(uint, uint),
    inquotedfield(uint, uint),
    inquote(uint, uint)
}

type rowreader = {
    readlen: uint,
    delim: char,
    quote: char,
    f : io::reader,
    mutable offset : uint,
    mutable buffers : [@[char]],
    mutable state : state,
    mutable trailing_nl : bool,
    mutable terminating : bool
};

type row = {
    fields : [ fieldtype ]
};

type bufferdescr = {
    escaped: bool,
    buffers: [@[char]],
    start: uint,
    end: uint
};

enum fieldtype {
    emptyfield(),
    bufferfield(bufferdescr)
}

iface rowiter {
    fn readrow() -> result::result<row, str>;
}

iface rowaccess {
    fn len() -> uint;
    fn getchars(uint) -> [char];
    fn getstr(uint) -> str;
}

fn new_reader(+f: io::reader, +delim: char, +quote: char) -> rowreader {
    {
        readlen: 1024u,
        delim: delim,
        quote: quote,
        f: f,
        mutable offset : 0u,
        mutable buffers : [],
        mutable state : fieldstart(false),
        mutable trailing_nl : false,
        mutable terminating: false
    }
}

fn new_reader_readlen(+f: io::reader, +delim: char, +quote: char, rl: uint) -> rowreader {
    {
        readlen: rl,
        delim: delim,
        quote: quote,
        f: f,
        mutable offset : 0u,
        mutable buffers : [],
        mutable state : fieldstart(false),
        mutable trailing_nl : false,
        mutable terminating: false
    }
}

impl of rowaccess for row {
    fn len() -> uint {
        vec::len(self.fields)
    }
    fn getchars(field: uint) -> [char] {
        fn unescape(escaped: [char]) -> [char] {
            let r : [char] = [];
            vec::reserve(r, vec::len(escaped));
            let in_q = false;
            for c in escaped {
                if in_q {
                    assert(c == '"');
                    in_q = false;
                } else {
                    in_q = c == '"';
                    r += [c];
                }
            }
            ret r;
        }
        alt self.fields[field] {
            emptyfield() { ret []; }
            bufferfield(desc) {
                let buf = [];
                {
                    let i = 0u;
                    while i < vec::len(desc.buffers) {
                        let from = if (i == 0u)
                            { desc.start } else { 0u };
                        let to = if (i == vec::len(desc.buffers) - 1u)
                            { desc.end } else { vec::len(*desc.buffers[i]) };
                        buf += vec::slice(*desc.buffers[i], from, to);
                        i = i + 1u;
                    }
                }
                if field == self.len() - 1u {
                    // there may be a trailing \r on the last field; we should strip it
                    // if so. bodgy here but seems the most efficient place to deal with this
                    if vec::len(buf) > 0u {
                        if buf[vec::len(buf)-1u] == '\r' {
                            buf = vec::slice(buf, 0u, vec::len(buf)-1u);
                        }
                    }
                }
                if desc.escaped {
                    buf = unescape(buf);
                }
                ret buf;
            }
        };
    }
    fn getstr(field: uint) -> str {
        ret str::from_chars(self.getchars(field));
    }
    fn getall() -> [str] {
        let a = [];
        self.map() { |s|
            a += [s];
        }
        ret a;
    }
    fn map(f: fn(s: str)) {
        let i = 0u;
        let len = self.len();
        while i < len {
            f(self.getstr(i));
            i += 1u;
        }
    }
}

impl of rowiter for rowreader {
    fn readrow() -> result::result<row, str> {
        fn statestr(state: state) -> str {
            alt state {
                fieldstart(after_delim) {
                    #fmt("fieldstart : after_delim %b", after_delim)
                }
                infield(b,o) {
                    #fmt("field : %u %u", b, o)
                }
                inquotedfield(b, o) {
                    #fmt("inquotedfield : %u %u", b, o)
                }
                inquote(b, o) {
                    #fmt("inquote : %u %u", b, o)
                }
            }
        }
        fn row_from_buf(self: rowreader, &fields: [fieldtype]) -> bool {
            fn new_bufferfield(self: rowreader, escaped: bool, sb: uint, so: uint, eo: uint) -> fieldtype {
                let eb = vec::len(self.buffers) - 1u;
                let sb = sb, so = so, eo = eo;
                //#debug("sb %u so %u eb %u eo %u", sb, so, eb, eo);
                //log(debug, vec::map(self.buffers) { |t| str::from_chars(*t) } );
                //log(debug, vec::map(self.buffers) { |t| vec::len(*t) });
                if escaped {
                    so += 1u;
                    if so > vec::len(*self.buffers[sb]) {
                        sb += 1u;
                        so = vec::len(*self.buffers[sb]) - 1u;
                    }
                    if eo > 0u {
                        eo -= 1u;
                    } else {
                        eb -= 1u;
                        eo = vec::len(*self.buffers[eb]) - 1u;
                    }
                }
                //#debug("sb %u so %u eb %u eo %u", sb, so, eb, eo);
                bufferfield({ escaped: escaped, buffers: vec::slice(self.buffers, sb, eb+1u), start: so, end: eo })
            }
            let cbuffer = vec::len(self.buffers) - 1u;
            let buf: @[char] = self.buffers[cbuffer];
            while self.offset < vec::len(*buf) {
                let coffset = self.offset;
                let c : char = buf[coffset];
                #debug("got '%c' | %s", c, statestr(self.state));
                self.offset += 1u;
                alt self.state {
                    fieldstart(after_delim) {
                        #debug("fieldstart : after_delim %b", after_delim);
                        if c == self.quote {
                            self.state = inquotedfield(cbuffer, coffset);
                        } else if c == '\n' {
                            if after_delim {
                                fields += [emptyfield];
                            }
                            ret true;
                        } else if c == self.delim {
                            self.state = fieldstart(true);
                            fields += [emptyfield];
                        } else {
                            self.state = infield(cbuffer, coffset);
                        }
                    }
                    infield(b,o) {
                        #debug("field : %u %u", b, o);
                        if c == '\n' {
                            fields += [new_bufferfield(self, false, b, o, coffset)];
                            ret true;
                        } else if c == self.delim {
                            self.state = fieldstart(true);
                            fields += [new_bufferfield(self, false, b, o, coffset)];
                        }
                    }
                    inquotedfield(b, o) {
                        #debug("inquotedfield : %u %u", b, o);
                        if c == self.quote {
                            self.state = inquote(b, o);
                        }
                    }
                    inquote(b, o) {
                        #debug("inquote : %u %u", b, o);
                        if c == '\n' {
                            fields += [new_bufferfield(self, true, b, o, coffset)];
                            ret true;
                        } else if c == self.quote {
                            self.state = inquotedfield(b, o);
                        } else if c == self.delim {
                            self.state = fieldstart(true);
                            fields += [new_bufferfield(self, true, b, o, coffset)];
                        }
                        // swallow odd chars, eg. space between field and "
                    }
                }
                #debug("now %s", statestr(self.state));
            }
            ret false;
        }

        self.state = fieldstart(false);
        let do_read = vec::len(self.buffers) == 0u;
        let fields = [];

        while !self.terminating {
            if do_read {
                let data: @[char] = @self.f.read_chars(self.readlen);
                if vec::len(*data) == 0u {
                    if !self.trailing_nl {
                        self.terminating = true;
                        data = @['\n'];
                    } else {
                        ret result::err("EOF");
                    }
                } else {
                    self.trailing_nl = data[vec::len(*data) - 1u] == '\n';
                }
                self.buffers += [data];
                self.offset = 0u;
            }

            if row_from_buf(self, fields) {
                let r: row = { fields: fields };
                fields = [];
                if vec::len(self.buffers) > 1u {
                    self.buffers = vec::slice(self.buffers, vec::len(self.buffers) - 1u, vec::len(self.buffers));
                }
                ret result::ok(r);
            }
            do_read = true;
        }
        ret result::err("unreachable");
    }
}

fn hashmap_iter_cols(r: rowreader, cols: [str], f: fn(map::hashmap<str, str>)) {
    loop {
        let res = r.readrow();
        if result::failure(res) {
            break;
        }
        let m : map::hashmap<str, str> = map::str_hash();
        let col = 0u;
        let row = result::get(res);
        if row.len() != vec::len(cols) {
            cont; // FIXME: how to flag that we dropped a crazy row?
        }
        result::get(res).map() { |s|
            m.insert(cols[col], s);
            col += 1u;
        };
        f(m);
    }
}

// reads the first row as a header, to derive keys for a hashmap
// emitted for each subsequent row
fn hashmap_iter(r: rowreader, f: fn(map::hashmap<str, str>)) {
    let res = r.readrow();
    alt res {
        result::ok(row) {
            hashmap_iter_cols(r, result::get(res).getall(), f);
        }
        result::err(_) { }
    }
}

// as hashmap_iter, but first apply 'hc' to each header; allows
// cleaning up headers; also allows verification that heads are
// satisfactory
fn hashmap_iter_full(r: rowreader, hmap: fn(&&h: str) -> str, hver: fn(cols: [str]) -> bool, f: fn(map::hashmap<str, str>)) {
    let res = r.readrow();
    alt res {
        result::ok(row) {
            let cols : [str] = vec::map(result::get(res).getall(), hmap);
            if !hver(cols) {
                ret;
            }
            hashmap_iter_cols(r, cols, f);
        }
        result::err(_) { }
    }
}

#[cfg(test)]
mod test {
    fn rowmatch(testdata: str, expected: [[str]]) {
        let chk = fn@(mk: fn(io::reader) -> rowreader) {
            let f = io::str_reader(testdata);
            let r = mk(f);
            let i = 0u;
            loop {
                let res = r.readrow();
                if result::failure(res) {
                    break;
                }
                let row = result::get(res);
                let expect = expected[i];

                assert(row.len() == vec::len(expect));
                let j = 0u;
                while j < row.len() {
                    assert(row.getstr(j) == expect[j]);
                    j += 1u;
                }
                i += 1u;
            }
            assert(i == vec::len(expected));
        };
        let runchecks = fn@(testdata: str) {
            // test default reader params
            chk() { |inp|
                new_reader_readlen(inp, ',', '"', 2u)
            };
            // test continuations over read buffers
            let j = 1u;
            while j < str::len(testdata) {
                chk() { |inp|
                    new_reader_readlen(inp, ',', '"', j)
                };
                j += 1u;
            }
            ret;
        };
        // so we can test trailing newline case, testdata
        // must not end in \n - leave off the last newline
        runchecks(testdata);
        runchecks(testdata+"\n");
        runchecks(str::replace(testdata, "\n", "\r\n"));
    }

    #[test]
    fn test_simple() {
        rowmatch("a,b,c,d\n1,2,3,4",
                 [["a", "b", "c", "d"], ["1", "2", "3", "4"]]);
    }

    #[test]
    fn test_trailing_comma() {
        rowmatch("a,b,c,d\n1,2,3,4,",
                 [["a", "b", "c", "d"], ["1", "2", "3", "4", ""]]);
    }

    #[test]
    fn test_leading_comma() {
        rowmatch("a,b,c,d\n,1,2,3,4",
                 [["a", "b", "c", "d"], ["", "1", "2", "3", "4"]]);
    }

    #[test]
    fn test_quote_simple() {
        rowmatch("\"Hello\",\"There\"\na,b,\"c\",d",
                 [["Hello", "There"], ["a", "b", "c", "d"]]);
    }

    #[test]
    fn test_quote_nested() {
        rowmatch("\"Hello\",\"There is a \"\"fly\"\" in my soup\"\na,b,\"c\",d",
                 [["Hello", "There is a \"fly\" in my soup"], ["a", "b", "c", "d"]]);
    }

    #[test]
    fn test_quote_with_comma() {
        rowmatch("\"1,2\"",
                 [["1,2"]])
    }

    #[test]
    fn test_quote_with_other_comma() {
        rowmatch("1,2,3,\"a,b,c\"",
                 [["1", "2", "3", "a,b,c"]])
    }

    #[test]
    fn test_blank_line() {
        rowmatch("\n\n", [[], []]);
    }
}

<commit_msg>fix for mutability changes<commit_after>use std;
import io::{writer_util, reader_util};
import std::map;
import map::hashmap;
import result;

export rowreader, rowaccess, rowiter,
       new_reader, new_reader_readlen,
       hashmap_iter, hashmap_iter_full;

enum state {
    fieldstart(bool),
    infield(uint, uint),
    inquotedfield(uint, uint),
    inquote(uint, uint)
}

type rowreader = {
    readlen: uint,
    delim: char,
    quote: char,
    f : io::reader,
    mutable offset : uint,
    mutable buffers : [@[char]],
    mutable state : state,
    mutable trailing_nl : bool,
    mutable terminating : bool
};

type row = {
    fields : [ fieldtype ]
};

type bufferdescr = {
    escaped: bool,
    buffers: [@[char]],
    start: uint,
    end: uint
};

enum fieldtype {
    emptyfield(),
    bufferfield(bufferdescr)
}

iface rowiter {
    fn readrow() -> result::result<row, str>;
}

iface rowaccess {
    fn len() -> uint;
    fn getchars(uint) -> [char];
    fn getstr(uint) -> str;
}

fn new_reader(+f: io::reader, +delim: char, +quote: char) -> rowreader {
    {
        readlen: 1024u,
        delim: delim,
        quote: quote,
        f: f,
        mutable offset : 0u,
        mutable buffers : [],
        mutable state : fieldstart(false),
        mutable trailing_nl : false,
        mutable terminating: false
    }
}

fn new_reader_readlen(+f: io::reader, +delim: char, +quote: char, rl: uint) -> rowreader {
    {
        readlen: rl,
        delim: delim,
        quote: quote,
        f: f,
        mutable offset : 0u,
        mutable buffers : [],
        mutable state : fieldstart(false),
        mutable trailing_nl : false,
        mutable terminating: false
    }
}

impl of rowaccess for row {
    fn len() -> uint {
        vec::len(self.fields)
    }
    fn getchars(field: uint) -> [char] {
        fn unescape(escaped: [char]) -> [char] {
            let mut r : [char] = [];
            vec::reserve(r, vec::len(escaped));
            let mut in_q = false;
            for c in escaped {
                if in_q {
                    assert(c == '"');
                    in_q = false;
                } else {
                    in_q = c == '"';
                    r += [c];
                }
            }
            ret r;
        }
        alt self.fields[field] {
            emptyfield() { ret []; }
            bufferfield(desc) {
                let mut buf = [];
                {
                    let mut i = 0u;
                    while i < vec::len(desc.buffers) {
                        let from = if (i == 0u)
                            { desc.start } else { 0u };
                        let to = if (i == vec::len(desc.buffers) - 1u)
                            { desc.end } else { vec::len(*desc.buffers[i]) };
                        buf += vec::slice(*desc.buffers[i], from, to);
                        i = i + 1u;
                    }
                }
                if field == self.len() - 1u {
                    // there may be a trailing \r on the last field; we should strip it
                    // if so. bodgy here but seems the most efficient place to deal with this
                    if vec::len(buf) > 0u {
                        if buf[vec::len(buf)-1u] == '\r' {
                            buf = vec::slice(buf, 0u, vec::len(buf)-1u);
                        }
                    }
                }
                if desc.escaped {
                    buf = unescape(buf);
                }
                ret buf;
            }
        };
    }
    fn getstr(field: uint) -> str {
        ret str::from_chars(self.getchars(field));
    }
    fn getall() -> [str] {
        let mut a = [];
        self.map() { |s|
            a += [s];
        }
        ret a;
    }
    fn map(f: fn(s: str)) {
        let mut i = 0u;
        let len = self.len();
        while i < len {
            f(self.getstr(i));
            i += 1u;
        }
    }
}

impl of rowiter for rowreader {
    fn readrow() -> result::result<row, str> {
        fn statestr(state: state) -> str {
            alt state {
                fieldstart(after_delim) {
                    #fmt("fieldstart : after_delim %b", after_delim)
                }
                infield(b,o) {
                    #fmt("field : %u %u", b, o)
                }
                inquotedfield(b, o) {
                    #fmt("inquotedfield : %u %u", b, o)
                }
                inquote(b, o) {
                    #fmt("inquote : %u %u", b, o)
                }
            }
        }
        fn row_from_buf(self: rowreader, &fields: [fieldtype]) -> bool {
            fn new_bufferfield(self: rowreader, escaped: bool, sb: uint, so: uint, eo: uint) -> fieldtype {
                let mut eb = vec::len(self.buffers) - 1u;
                let mut sb = sb, so = so, eo = eo;
                //#debug("sb %u so %u eb %u eo %u", sb, so, eb, eo);
                //log(debug, vec::map(self.buffers) { |t| str::from_chars(*t) } );
                //log(debug, vec::map(self.buffers) { |t| vec::len(*t) });
                if escaped {
                    so += 1u;
                    if so > vec::len(*self.buffers[sb]) {
                        sb += 1u;
                        so = vec::len(*self.buffers[sb]) - 1u;
                    }
                    if eo > 0u {
                        eo -= 1u;
                    } else {
                        eb -= 1u;
                        eo = vec::len(*self.buffers[eb]) - 1u;
                    }
                }
                //#debug("sb %u so %u eb %u eo %u", sb, so, eb, eo);
                bufferfield({ escaped: escaped, buffers: vec::slice(self.buffers, sb, eb+1u), start: so, end: eo })
            }
            let cbuffer = vec::len(self.buffers) - 1u;
            let buf: @[char] = self.buffers[cbuffer];
            while self.offset < vec::len(*buf) {
                let coffset = self.offset;
                let c : char = buf[coffset];
                #debug("got '%c' | %s", c, statestr(self.state));
                self.offset += 1u;
                alt self.state {
                    fieldstart(after_delim) {
                        #debug("fieldstart : after_delim %b", after_delim);
                        if c == self.quote {
                            self.state = inquotedfield(cbuffer, coffset);
                        } else if c == '\n' {
                            if after_delim {
                                fields += [emptyfield];
                            }
                            ret true;
                        } else if c == self.delim {
                            self.state = fieldstart(true);
                            fields += [emptyfield];
                        } else {
                            self.state = infield(cbuffer, coffset);
                        }
                    }
                    infield(b,o) {
                        #debug("field : %u %u", b, o);
                        if c == '\n' {
                            fields += [new_bufferfield(self, false, b, o, coffset)];
                            ret true;
                        } else if c == self.delim {
                            self.state = fieldstart(true);
                            fields += [new_bufferfield(self, false, b, o, coffset)];
                        }
                    }
                    inquotedfield(b, o) {
                        #debug("inquotedfield : %u %u", b, o);
                        if c == self.quote {
                            self.state = inquote(b, o);
                        }
                    }
                    inquote(b, o) {
                        #debug("inquote : %u %u", b, o);
                        if c == '\n' {
                            fields += [new_bufferfield(self, true, b, o, coffset)];
                            ret true;
                        } else if c == self.quote {
                            self.state = inquotedfield(b, o);
                        } else if c == self.delim {
                            self.state = fieldstart(true);
                            fields += [new_bufferfield(self, true, b, o, coffset)];
                        }
                        // swallow odd chars, eg. space between field and "
                    }
                }
                #debug("now %s", statestr(self.state));
            }
            ret false;
        }

        self.state = fieldstart(false);
        let mut do_read = vec::len(self.buffers) == 0u;
        let mut fields = [];

        while !self.terminating {
            if do_read {
                let mut data: @[char] = @self.f.read_chars(self.readlen);
                if vec::len(*data) == 0u {
                    if !self.trailing_nl {
                        self.terminating = true;
                        data = @['\n'];
                    } else {
                        ret result::err("EOF");
                    }
                } else {
                    self.trailing_nl = data[vec::len(*data) - 1u] == '\n';
                }
                self.buffers += [data];
                self.offset = 0u;
            }

            if row_from_buf(self, fields) {
                let r: row = { fields: fields };
                fields = [];
                if vec::len(self.buffers) > 1u {
                    self.buffers = vec::slice(self.buffers, vec::len(self.buffers) - 1u, vec::len(self.buffers));
                }
                ret result::ok(r);
            }
            do_read = true;
        }
        ret result::err("unreachable");
    }
}

fn hashmap_iter_cols(r: rowreader, cols: [str], f: fn(map::hashmap<str, str>)) {
    loop {
        let res = r.readrow();
        if result::failure(res) {
            break;
        }
        let m : map::hashmap<str, str> = map::str_hash();
        let mut col = 0u;
        let row = result::get(res);
        if row.len() != vec::len(cols) {
            cont; // FIXME: how to flag that we dropped a crazy row?
        }
        result::get(res).map() { |s|
            m.insert(cols[col], s);
            col += 1u;
        };
        f(m);
    }
}

// reads the first row as a header, to derive keys for a hashmap
// emitted for each subsequent row
fn hashmap_iter(r: rowreader, f: fn(map::hashmap<str, str>)) {
    let res = r.readrow();
    alt res {
        result::ok(row) {
            hashmap_iter_cols(r, result::get(res).getall(), f);
        }
        result::err(_) { }
    }
}

// as hashmap_iter, but first apply 'hc' to each header; allows
// cleaning up headers; also allows verification that heads are
// satisfactory
fn hashmap_iter_full(r: rowreader, hmap: fn(&&h: str) -> str, hver: fn(cols: [str]) -> bool, f: fn(map::hashmap<str, str>)) {
    let res = r.readrow();
    alt res {
        result::ok(row) {
            let cols : [str] = vec::map(result::get(res).getall(), hmap);
            if !hver(cols) {
                ret;
            }
            hashmap_iter_cols(r, cols, f);
        }
        result::err(_) { }
    }
}

#[cfg(test)]
mod test {
    fn rowmatch(testdata: str, expected: [[str]]) {
        let chk = fn@(mk: fn(io::reader) -> rowreader) {
            let f = io::str_reader(testdata);
            let r = mk(f);
            let mut i = 0u;
            loop {
                let res = r.readrow();
                if result::failure(res) {
                    break;
                }
                let row = result::get(res);
                let expect = expected[i];

                assert(row.len() == vec::len(expect));
                let mut j = 0u;
                while j < row.len() {
                    assert(row.getstr(j) == expect[j]);
                    j += 1u;
                }
                i += 1u;
            }
            assert(i == vec::len(expected));
        };
        let runchecks = fn@(testdata: str) {
            // test default reader params
            chk() { |inp|
                new_reader_readlen(inp, ',', '"', 2u)
            };
            // test continuations over read buffers
            let mut j = 1u;
            while j < str::len(testdata) {
                chk() { |inp|
                    new_reader_readlen(inp, ',', '"', j)
                };
                j += 1u;
            }
            ret;
        };
        // so we can test trailing newline case, testdata
        // must not end in \n - leave off the last newline
        runchecks(testdata);
        runchecks(testdata+"\n");
        runchecks(str::replace(testdata, "\n", "\r\n"));
    }

    #[test]
    fn test_simple() {
        rowmatch("a,b,c,d\n1,2,3,4",
                 [["a", "b", "c", "d"], ["1", "2", "3", "4"]]);
    }

    #[test]
    fn test_trailing_comma() {
        rowmatch("a,b,c,d\n1,2,3,4,",
                 [["a", "b", "c", "d"], ["1", "2", "3", "4", ""]]);
    }

    #[test]
    fn test_leading_comma() {
        rowmatch("a,b,c,d\n,1,2,3,4",
                 [["a", "b", "c", "d"], ["", "1", "2", "3", "4"]]);
    }

    #[test]
    fn test_quote_simple() {
        rowmatch("\"Hello\",\"There\"\na,b,\"c\",d",
                 [["Hello", "There"], ["a", "b", "c", "d"]]);
    }

    #[test]
    fn test_quote_nested() {
        rowmatch("\"Hello\",\"There is a \"\"fly\"\" in my soup\"\na,b,\"c\",d",
                 [["Hello", "There is a \"fly\" in my soup"], ["a", "b", "c", "d"]]);
    }

    #[test]
    fn test_quote_with_comma() {
        rowmatch("\"1,2\"",
                 [["1,2"]])
    }

    #[test]
    fn test_quote_with_other_comma() {
        rowmatch("1,2,3,\"a,b,c\"",
                 [["1", "2", "3", "a,b,c"]])
    }

    #[test]
    fn test_blank_line() {
        rowmatch("\n\n", [[], []]);
    }
}

<|endoftext|>


<commit_before>// Copyright © 2017, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use super::get_values;
use super::BragiHandler;
use serde_json::json;
use std::path::Path;

pub fn bragi_bano_test(es_wrapper: crate::ElasticSearchWrapper<'_>) {
    let mut bragi = BragiHandler::new(es_wrapper.host());

    // *********************************
    // We load bano files
    // *********************************
    let bano2mimir = Path::new(env!("OUT_DIR"))
        .join("../../../bano2mimir")
        .display()
        .to_string();
    crate::launch_and_assert(
        &bano2mimir,
        &[
            "--input=./tests/fixtures/sample-bano.csv".into(),
            format!("--connection-string={}", es_wrapper.host()),
        ],
        &es_wrapper,
    );

    status_test(&mut bragi);
    simple_bano_autocomplete_test(&mut bragi);
    simple_bano_shape_filter_test(&mut bragi);
    simple_bano_lon_lat_test(&mut bragi);
    long_bano_address_test(&mut bragi);
    reverse_bano_test(&mut bragi);
}

fn status_test(bragi: &mut BragiHandler) {
    assert_eq!(
        bragi.get_json("/status").pointer("/status"),
        Some(&json!("good"))
    );
}

fn simple_bano_autocomplete_test(bragi: &mut BragiHandler) {
    assert_eq!(
        bragi.get_json("/autocomplete?q=15 Rue Hector Malot (Paris)"),
        json!(
        {
            "features": [
                {
                    "geometry": {
                        "coordinates": [
                            2.376_379,
                            48.846_495
                        ],
                        "type": "Point"
                    },
                    "properties": {
                        "geocoding": {
                            "administrative_regions": [],
                            "city": null,
                            "citycode": null,
                            "country_codes": ["fr"],
                            "housenumber": "15",
                            "id": "addr:2.376379;48.846495:15",
                            "label": "15 Rue Hector Malot (Paris)",
                            "name": "15 Rue Hector Malot",
                            "postcode": "75012",
                            "street": "Rue Hector Malot",
                            "type": "house"
                        }
                    },
                    "type": "Feature"
                }
            ],
            "geocoding": {
                "query": "",
                "version": "0.1.0"
            },
            "type": "FeatureCollection"
        }
        )
    );
}

// A(48.846431 2.376488)
// B(48.846430 2.376306)
// C(48.846606 2.376309)
// D(48.846603 2.376486)
// R(48.846495 2.376378) : 15 Rue Hector Malot, (Paris)
// E(48.846452 2.376580) : 18 Rue Hector Malot, (Paris)
//
//             E
//
//      A ---------------------D
//      |                      |
//      |         R            |
//      |                      |
//      |                      |
//      B ---------------------C
fn simple_bano_shape_filter_test(bragi: &mut BragiHandler) {
    // Search with shape where house number in shape
    // We don't use a scope_shape, so it should be as if we didn't use any shape.
    let shape = r#"{"shape":{"type":"Feature","properties":{},"geometry":{"type":"Polygon",
        "coordinates":[[[2.376488, 48.846431],
        [2.376306, 48.846430],[2.376309, 48.846606],[2.376486, 48.846603], [2.376488, 48.846431]]]}}}"#;
    let r = bragi.post_as_json("/autocomplete?q=15 Rue Hector Malot, (Paris)", shape);
    assert_eq!(
        r,
        json!({
          "type": "FeatureCollection",
          "geocoding": {
            "version": "0.1.0",
            "query": ""
          },
          "features": [
            {
              "type": "Feature",
              "geometry": {
                "coordinates": [
                  2.376_379,
                  48.846_495
                ],
                "type": "Point"
              },
              "properties": {
                "geocoding": {
                  "id": "addr:2.376379;48.846495:15",
                  "type": "house",
                  "label": "15 Rue Hector Malot (Paris)",
                  "name": "15 Rue Hector Malot",
                  "housenumber": "15",
                  "street": "Rue Hector Malot",
                  "postcode": "75012",
                  "city": null,
                  "citycode": null,
                  "country_codes": ["fr"],
                  "administrative_regions": []
                }
              }
            }
          ]
        }
        )
    );

    // Search with shape where house number out of shape and house not in scope
    // since the house is not in the filter scope, it should be found
    let r = bragi.post_as_json(
        "/autocomplete?q=18 Rue Hector Malot, (Paris)&shape_scope[]=admin",
        shape,
    );
    assert_eq!(
        r,
        json!({
          "type": "FeatureCollection",
          "geocoding": {
            "version": "0.1.0",
            "query": ""
          },
          "features": [
            {
              "type": "Feature",
              "geometry": {
                "coordinates": [
                  2.376_58,
                  48.846_452
                ],
                "type": "Point"
              },
              "properties": {
                "geocoding": {
                  "id": "addr:2.37658;48.846452:18",
                  "type": "house",
                  "label": "18 Rue Hector Malot (Paris)",
                  "name": "18 Rue Hector Malot",
                  "housenumber": "18",
                  "street": "Rue Hector Malot",
                  "postcode": "75012",
                  "city": null,
                  "citycode": null,
                  "country_codes": ["fr"],
                  "administrative_regions": []
                }
              }
            }
          ]
        }
        )
    );
    // Search with shape where house number out of shape and house not in scope
    // since the house is in the filter scope, it should be filtered out.
    let r = bragi.post_as_json(
        "/autocomplete?q=18 Rue Hector Malot, (Paris)&shape_scope[]=addr",
        shape,
    );
    assert_eq!(
        r,
        json!({
          "type": "FeatureCollection",
          "geocoding": {
            "version": "0.1.0",
            "query": ""
          },
          "features": []
        }
        )
    );
}

fn simple_bano_lon_lat_test(bragi: &mut BragiHandler) {
    // test with a lon/lat priorisation
    // in the dataset there are two '20 rue hector malot',
    // one in paris and one in trifouilli-les-Oies
    // in the mean time we test our prefix search_query
    let all_20 = bragi.get("/autocomplete?q=20 rue hect mal");
    assert_eq!(all_20.len(), 2);
    // the first one is paris (since Paris has more streets, it is prioritized first)
    // TODO uncomment this test, for the moment since osm is not loaded, the order is random
    // assert_eq!(get_labels(&all_20),
    //            vec!["20 Rue Hector Malot (Paris)", "20 Rue Hector Malot (Trifouilli-les-Oies)"]);

    // if we give a lon/lat near trifouilli-les-Oies, we'll have another sort
    let all_20 = bragi.get("/autocomplete?q=20 rue hector malot&lat=50.2&lon=2.0");
    assert_eq!(
        get_values(&all_20, "label"),
        vec![
            "20 Rue Hector Malot (Trifouilli-les-Oies)",
            "20 Rue Hector Malot (Paris)",
        ]
    );
    // and when we're in paris, we get paris first
    let all_20 = bragi.get("/autocomplete?q=20 rue hector malot&lat=48&lon=2.4");
    assert_eq!(
        get_values(&all_20, "label"),
        vec![
            "20 Rue Hector Malot (Paris)",
            "20 Rue Hector Malot (Trifouilli-les-Oies)",
        ]
    );
}

fn long_bano_address_test(bragi: &mut BragiHandler) {
    // test with a very long request which consists of an exact address and something else
    // and the "something else" should not disturb the research
    let all_20 = bragi.get(
        "/autocomplete?q=The Marvellous Navitia Developers Kisio Digital 20 rue hector \
         malot paris",
    );
    assert_eq!(all_20.len(), 1);
    assert_eq!(
        get_values(&all_20, "label"),
        vec!["20 Rue Hector Malot (Paris)"]
    );
}

fn reverse_bano_test(bragi: &mut BragiHandler) {
    let res = bragi.get("/reverse?lon=2.37716&lat=48.8468");
    assert_eq!(res.len(), 1);
    assert_eq!(
        get_values(&res, "label"),
        vec!["20 Rue Hector Malot (Paris)"]
    );

    let res = bragi.get("/reverse?lon=1.3787628&lat=43.6681995");
    assert_eq!(
        get_values(&res, "label"),
        vec!["2 Rue des Pins (Beauzelle)"]
    );
}
<commit_msg>Add test bad request<commit_after>// Copyright © 2017, Canal TP and/or its affiliates. All rights reserved.
//
// This file is part of Navitia,
//     the software to build cool stuff with public transport.
//
// Hope you'll enjoy and contribute to this project,
//     powered by Canal TP (www.canaltp.fr).
// Help us simplify mobility and open public transport:
//     a non ending quest to the responsive locomotion way of traveling!
//
// LICENCE: This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// Stay tuned using
// twitter @navitia
// IRC #navitia on freenode
// https://groups.google.com/d/forum/navitia
// www.navitia.io

use super::get_values;
use super::BragiHandler;
use serde_json::json;
use std::path::Path;

pub fn bragi_bano_test(es_wrapper: crate::ElasticSearchWrapper<'_>) {
    let mut bragi = BragiHandler::new(es_wrapper.host());

    // *********************************
    // We load bano files
    // *********************************
    let bano2mimir = Path::new(env!("OUT_DIR"))
        .join("../../../bano2mimir")
        .display()
        .to_string();
    crate::launch_and_assert(
        &bano2mimir,
        &[
            "--input=./tests/fixtures/sample-bano.csv".into(),
            format!("--connection-string={}", es_wrapper.host()),
        ],
        &es_wrapper,
    );

    status_test(&mut bragi);
    simple_bano_autocomplete_test(&mut bragi);
    simple_bano_shape_filter_test(&mut bragi);
    simple_bano_lon_lat_test(&mut bragi);
    long_bano_address_test(&mut bragi);
    reverse_bano_test(&mut bragi);
}

fn status_test(bragi: &mut BragiHandler) {
    assert_eq!(
        bragi.get_json("/status").pointer("/status"),
        Some(&json!("good"))
    );
}

fn simple_bano_autocomplete_test(bragi: &mut BragiHandler) {
    assert_eq!(
        bragi.get_json("/autocomplete?q=15 Rue Hector Malot (Paris)"),
        json!(
        {
            "features": [
                {
                    "geometry": {
                        "coordinates": [
                            2.376_379,
                            48.846_495
                        ],
                        "type": "Point"
                    },
                    "properties": {
                        "geocoding": {
                            "administrative_regions": [],
                            "city": null,
                            "citycode": null,
                            "country_codes": ["fr"],
                            "housenumber": "15",
                            "id": "addr:2.376379;48.846495:15",
                            "label": "15 Rue Hector Malot (Paris)",
                            "name": "15 Rue Hector Malot",
                            "postcode": "75012",
                            "street": "Rue Hector Malot",
                            "type": "house"
                        }
                    },
                    "type": "Feature"
                }
            ],
            "geocoding": {
                "query": "",
                "version": "0.1.0"
            },
            "type": "FeatureCollection"
        }
        )
    );
}

// A(48.846431 2.376488)
// B(48.846430 2.376306)
// C(48.846606 2.376309)
// D(48.846603 2.376486)
// R(48.846495 2.376378) : 15 Rue Hector Malot, (Paris)
// E(48.846452 2.376580) : 18 Rue Hector Malot, (Paris)
//
//             E
//
//      A ---------------------D
//      |                      |
//      |         R            |
//      |                      |
//      |                      |
//      B ---------------------C
fn simple_bano_shape_filter_test(bragi: &mut BragiHandler) {
    // Search with shape where house number in shape
    // We don't use a scope_shape, so it should be as if we didn't use any shape.
    let shape = r#"{"shape":{"type":"Feature","properties":{},"geometry":{"type":"Polygon",
        "coordinates":[[[2.376488, 48.846431],
        [2.376306, 48.846430],[2.376309, 48.846606],[2.376486, 48.846603], [2.376488, 48.846431]]]}}}"#;
    let r = bragi.post_as_json("/autocomplete?q=15 Rue Hector Malot, (Paris)", shape);
    assert_eq!(
        r,
        json!({
          "type": "FeatureCollection",
          "geocoding": {
            "version": "0.1.0",
            "query": ""
          },
          "features": [
            {
              "type": "Feature",
              "geometry": {
                "coordinates": [
                  2.376_379,
                  48.846_495
                ],
                "type": "Point"
              },
              "properties": {
                "geocoding": {
                  "id": "addr:2.376379;48.846495:15",
                  "type": "house",
                  "label": "15 Rue Hector Malot (Paris)",
                  "name": "15 Rue Hector Malot",
                  "housenumber": "15",
                  "street": "Rue Hector Malot",
                  "postcode": "75012",
                  "city": null,
                  "citycode": null,
                  "country_codes": ["fr"],
                  "administrative_regions": []
                }
              }
            }
          ]
        }
        )
    );

    // Search with shape where house number out of shape and house not in scope
    // since the house is not in the filter scope, it should be found
    let r = bragi.post_as_json(
        "/autocomplete?q=18 Rue Hector Malot, (Paris)&shape_scope[]=admin",
        shape,
    );
    assert_eq!(
        r,
        json!({
          "type": "FeatureCollection",
          "geocoding": {
            "version": "0.1.0",
            "query": ""
          },
          "features": [
            {
              "type": "Feature",
              "geometry": {
                "coordinates": [
                  2.376_58,
                  48.846_452
                ],
                "type": "Point"
              },
              "properties": {
                "geocoding": {
                  "id": "addr:2.37658;48.846452:18",
                  "type": "house",
                  "label": "18 Rue Hector Malot (Paris)",
                  "name": "18 Rue Hector Malot",
                  "housenumber": "18",
                  "street": "Rue Hector Malot",
                  "postcode": "75012",
                  "city": null,
                  "citycode": null,
                  "country_codes": ["fr"],
                  "administrative_regions": []
                }
              }
            }
          ]
        }
        )
    );
    // Search with shape where house number out of shape and house not in scope
    // since the house is in the filter scope, it should be filtered out.
    let r = bragi.post_as_json(
        "/autocomplete?q=18 Rue Hector Malot, (Paris)&shape_scope[]=addr",
        shape,
    );
    assert_eq!(
        r,
        json!({
          "type": "FeatureCollection",
          "geocoding": {
            "version": "0.1.0",
            "query": ""
          },
          "features": []
        }
        )
    );

    // Searching with an invalid shape_scope parameter... should yield an error.
    let (status, _) = bragi.raw_post(
        "/autocomplete?q=18 Rue Hector Malot, (Paris)&shape_scope[]=add",
        shape,
    );
    assert_eq!(status, actix_web::http::StatusCode::BAD_REQUEST);
}

fn simple_bano_lon_lat_test(bragi: &mut BragiHandler) {
    // test with a lon/lat priorisation
    // in the dataset there are two '20 rue hector malot',
    // one in paris and one in trifouilli-les-Oies
    // in the mean time we test our prefix search_query
    let all_20 = bragi.get("/autocomplete?q=20 rue hect mal");
    assert_eq!(all_20.len(), 2);
    // the first one is paris (since Paris has more streets, it is prioritized first)
    // TODO uncomment this test, for the moment since osm is not loaded, the order is random
    // assert_eq!(get_labels(&all_20),
    //            vec!["20 Rue Hector Malot (Paris)", "20 Rue Hector Malot (Trifouilli-les-Oies)"]);

    // if we give a lon/lat near trifouilli-les-Oies, we'll have another sort
    let all_20 = bragi.get("/autocomplete?q=20 rue hector malot&lat=50.2&lon=2.0");
    assert_eq!(
        get_values(&all_20, "label"),
        vec![
            "20 Rue Hector Malot (Trifouilli-les-Oies)",
            "20 Rue Hector Malot (Paris)",
        ]
    );
    // and when we're in paris, we get paris first
    let all_20 = bragi.get("/autocomplete?q=20 rue hector malot&lat=48&lon=2.4");
    assert_eq!(
        get_values(&all_20, "label"),
        vec![
            "20 Rue Hector Malot (Paris)",
            "20 Rue Hector Malot (Trifouilli-les-Oies)",
        ]
    );
}

fn long_bano_address_test(bragi: &mut BragiHandler) {
    // test with a very long request which consists of an exact address and something else
    // and the "something else" should not disturb the research
    let all_20 = bragi.get(
        "/autocomplete?q=The Marvellous Navitia Developers Kisio Digital 20 rue hector \
         malot paris",
    );
    assert_eq!(all_20.len(), 1);
    assert_eq!(
        get_values(&all_20, "label"),
        vec!["20 Rue Hector Malot (Paris)"]
    );
}

fn reverse_bano_test(bragi: &mut BragiHandler) {
    let res = bragi.get("/reverse?lon=2.37716&lat=48.8468");
    assert_eq!(res.len(), 1);
    assert_eq!(
        get_values(&res, "label"),
        vec!["20 Rue Hector Malot (Paris)"]
    );

    let res = bragi.get("/reverse?lon=1.3787628&lat=43.6681995");
    assert_eq!(
        get_values(&res, "label"),
        vec!["2 Rue des Pins (Beauzelle)"]
    );
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

// Progress bars implementation. Moved into a different file so it is possible
// to make some dependencies (incidacitf) optional.

use book::{Book, Crowbar, CrowbarState};

use indicatif::{ProgressBar, ProgressStyle, MultiProgress};

use std::sync::Arc;
use std::thread;
use std::mem;

/// Store the progress bars needed for the book
pub struct Bars {
    /// Whether or not to use emoji
    pub emoji: bool,
    /// Container for the progress bars
    pub multibar: Option<Arc<MultiProgress>>,
    /// Main progress bar (actually a spinner)
    pub mainbar: Option<ProgressBar>,
    /// Secondary bar
    pub secondbar: Option<ProgressBar>,
    /// Guard for thread
    pub guard: Option<thread::JoinHandle<()>>,
    /// Spinners for each renderier
    pub spinners: Vec<ProgressBar>,
}

impl Bars {
    /// Create a new bars storage
    pub fn new() -> Bars {
        Bars {
            emoji: false,
            multibar: None,
            mainbar: None,
            secondbar: None,
            guard: None,
            spinners: vec![],
        }
    }
}

/// Return the style of a bar

impl Book {
    /// Adds a progress bar where where info should be written.
    ///
    /// See [indicatif doc](https://docs.rs/indicatif) for more information.
    pub fn private_add_progress_bar(&mut self, emoji: bool) {
        self.bars.emoji = emoji;
        let multibar = Arc::new(MultiProgress::new());
        self.bars.multibar = Some(multibar.clone());
        let b = self.bars.multibar
            .as_ref()
            .unwrap()
            .add(ProgressBar::new_spinner());
        b.enable_steady_tick(200);
        self.bars.mainbar = Some(b);
//        let sty = ProgressStyle::default_spinner()
//            .tick_chars("🕛🕐🕑🕒🕓🕔🕔🕕🕖🕗🕘🕘🕙🕚V")
//            .tick_chars("/|\\-V")
//            .template("{spinner:.dim.bold.yellow} {prefix} {wide_msg}");
        self.bar_set_style(Crowbar::Main, CrowbarState::Running);
        self.bars.guard = Some(thread::spawn(move || {
            if let Err(_) = multibar.join() {
                error!("{}", lformat!("could not display fancy UI, try running crowbook with --no-fancy"));
            }
        }));
    }

    /// Sets a finished message to the progress bar, if it is set
    pub fn bar_finish(&self, bar: Crowbar, state: CrowbarState, msg: &str) {
        self.bar_set_style(bar, state);
        let pb = match bar {
            Crowbar::Main => if let Some(ref bar) = self.bars.mainbar { bar } else { return; },
            Crowbar::Second => if let Some(ref bar) = self.bars.secondbar { bar } else { return; },
            Crowbar::Spinner(i) => if i < self.bars.spinners.len() { &self.bars.spinners[i] } else { return; },
        };

        match bar {
            Crowbar::Second => pb.finish_and_clear(),
            _ => pb.finish_with_message(msg),
        };
    }

    /// Adds a secondary progress bar to display progress of book parsing
    pub fn add_second_bar(&mut self, msg: &str, len: u64)  {
        if let Some(ref multibar) = self.bars.multibar {
            let bar = multibar.add(ProgressBar::new(len));
            self.bar_set_style(Crowbar::Second, CrowbarState::Running);
            bar.set_message(msg);
            self.bars.secondbar = Some(bar);
        }
    }

    /// Increment second bar
    pub fn inc_second_bar(&self) {
        if let Some(ref bar) = self.bars.secondbar {
            bar.inc(1);
        }
    }

    /// Adds a spinner labeled key to the multibar, and set mainbar to "rendering"
    pub fn add_spinner_to_multibar(&mut self, key: &str) -> usize {
        if let Some(ref multibar) = self.bars.multibar {
            if let Some(ref mainbar) = self.bars.mainbar {
                mainbar.set_message(&lformat!("Rendering..."));
            }

            let bar = multibar.add(ProgressBar::new_spinner());
            bar.enable_steady_tick(200);
            bar.set_message(&lformat!("waiting..."));
            bar.set_prefix(&format!("{}:", key));
            let i = self.bars.spinners.len();
            self.bars.spinners.push(bar);
            self.bar_set_style(Crowbar::Spinner(i), CrowbarState::Running);

            i
        } else {
            0
        }
    }

    pub fn bar_set_message(&self, bar: Crowbar, msg: &str) {
        let bar = match bar {
            Crowbar::Main => if let Some(ref bar) = self.bars.mainbar { bar } else { return; },
            Crowbar::Second => if let Some(ref bar) = self.bars.secondbar { bar } else { return; },
            Crowbar::Spinner(i) => if i < self.bars.spinners.len() { &self.bars.spinners[i] } else { return; },
        };
        bar.set_message(msg);
    }

    /// Sets the style of a  bar
    fn bar_set_style(&self, bar: Crowbar, state: CrowbarState) -> () {
        let pb = match bar {
            Crowbar::Main => if let Some(ref bar) = self.bars.mainbar { bar } else { return; },
            Crowbar::Second => if let Some(ref bar) = self.bars.secondbar { bar } else { return; },
            Crowbar::Spinner(i) => if i < self.bars.spinners.len() { &self.bars.spinners[i] } else { return; },

        };
        let emoji = self.bars.emoji;
        let mut style = match bar {
            Crowbar::Second => ProgressStyle::default_bar(),
            _ => ProgressStyle::default_spinner()
        };

        let color = match state {
            CrowbarState::Running => "yellow",
            CrowbarState::Success => "cyan",
            CrowbarState::Error => "red",
        };
        let tick_chars = match (bar, emoji) {
            (Crowbar::Main, false)  | (Crowbar::Spinner(_), false) => "-\\|/",
            (Crowbar::Main, true) => "🕛🕐🕑🕒🕓🕔🕔🕕🕖🕗🕘🕘🕙🕚",
            (Crowbar::Spinner(_), true) => "◐◓◑◒",
            (_, _) => ""
        };
        let end_tick = match (state, emoji) {
            (CrowbarState::Running, _) => "V",
            (CrowbarState::Success, true) => "✔",
            (CrowbarState::Error, true) => "❌",
            (CrowbarState::Success, false) => "V",
            (CrowbarState::Error, false) => "X",
        };
        match bar {
            Crowbar::Second => {
                style = style.template("{bar:40.cyan/blue} {percent:>7} {wide_msg}")
                    .progress_chars("##-");
            },
            bar => {
                style = style.tick_chars(&format!("{}{}", tick_chars, end_tick));
                match bar {
                    Crowbar::Spinner(_) => {
                        style = style
                            .template(&format!("{{spinner:.bold.{color}}} {{prefix}} {{wide_msg}}",
                                               color = color));
                    },
                    _ => {
                        style = style
                            .template(&format!("{{spinner:.bold.{color}}} {{prefix}}{{wide_msg}}",
                                               color = color));
                    },
                };
            },
        }
        pb.set_style(style);
    }
}

impl Drop for Book {
    fn drop(&mut self) {
        if let Some(ref bar) = self.bars.mainbar {
            bar.finish();
            let guard = mem::replace(&mut self.bars.guard, None);
            guard.unwrap()
                .join()
                .unwrap();
        }
    }
}
<commit_msg>Clear secondary bar on parsing error<commit_after>// Copyright (C) 2017 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

// Progress bars implementation. Moved into a different file so it is possible
// to make some dependencies (incidacitf) optional.

use book::{Book, Crowbar, CrowbarState};

use indicatif::{ProgressBar, ProgressStyle, MultiProgress};

use std::sync::Arc;
use std::thread;
use std::mem;

/// Store the progress bars needed for the book
pub struct Bars {
    /// Whether or not to use emoji
    pub emoji: bool,
    /// Container for the progress bars
    pub multibar: Option<Arc<MultiProgress>>,
    /// Main progress bar (actually a spinner)
    pub mainbar: Option<ProgressBar>,
    /// Secondary bar
    pub secondbar: Option<ProgressBar>,
    /// Guard for thread
    pub guard: Option<thread::JoinHandle<()>>,
    /// Spinners for each renderier
    pub spinners: Vec<ProgressBar>,
}

impl Bars {
    /// Create a new bars storage
    pub fn new() -> Bars {
        Bars {
            emoji: false,
            multibar: None,
            mainbar: None,
            secondbar: None,
            guard: None,
            spinners: vec![],
        }
    }
}

/// Return the style of a bar

impl Book {
    /// Adds a progress bar where where info should be written.
    ///
    /// See [indicatif doc](https://docs.rs/indicatif) for more information.
    pub fn private_add_progress_bar(&mut self, emoji: bool) {
        self.bars.emoji = emoji;
        let multibar = Arc::new(MultiProgress::new());
        self.bars.multibar = Some(multibar.clone());
        let b = self.bars.multibar
            .as_ref()
            .unwrap()
            .add(ProgressBar::new_spinner());
        b.enable_steady_tick(200);
        self.bars.mainbar = Some(b);
//        let sty = ProgressStyle::default_spinner()
//            .tick_chars("🕛🕐🕑🕒🕓🕔🕔🕕🕖🕗🕘🕘🕙🕚V")
//            .tick_chars("/|\\-V")
//            .template("{spinner:.dim.bold.yellow} {prefix} {wide_msg}");
        self.bar_set_style(Crowbar::Main, CrowbarState::Running);
        self.bars.guard = Some(thread::spawn(move || {
            if let Err(_) = multibar.join() {
                error!("{}", lformat!("could not display fancy UI, try running crowbook with --no-fancy"));
            }
        }));
    }

    /// Sets a finished message to the progress bar, if it is set
    pub fn bar_finish(&self, bar: Crowbar, state: CrowbarState, msg: &str) {
        self.bar_set_style(bar, state);
        let pb = match bar {
            Crowbar::Main => if let Some(ref bar) = self.bars.mainbar { bar } else { return; },
            Crowbar::Second => if let Some(ref bar) = self.bars.secondbar { bar } else { return; },
            Crowbar::Spinner(i) => if i < self.bars.spinners.len() { &self.bars.spinners[i] } else { return; },
        };

        match bar {
            Crowbar::Second => pb.finish_and_clear(),
            _ => pb.finish_with_message(msg),
        };
    }

    /// Adds a secondary progress bar to display progress of book parsing
    pub fn add_second_bar(&mut self, msg: &str, len: u64)  {
        if let Some(ref multibar) = self.bars.multibar {
            let bar = multibar.add(ProgressBar::new(len));
            self.bar_set_style(Crowbar::Second, CrowbarState::Running);
            bar.set_message(msg);
            self.bars.secondbar = Some(bar);
        }
    }

    /// Increment second bar
    pub fn inc_second_bar(&self) {
        if let Some(ref bar) = self.bars.secondbar {
            bar.inc(1);
        }
    }

    /// Adds a spinner labeled key to the multibar, and set mainbar to "rendering"
    pub fn add_spinner_to_multibar(&mut self, key: &str) -> usize {
        if let Some(ref multibar) = self.bars.multibar {
            if let Some(ref mainbar) = self.bars.mainbar {
                mainbar.set_message(&lformat!("Rendering..."));
            }

            let bar = multibar.add(ProgressBar::new_spinner());
            bar.enable_steady_tick(200);
            bar.set_message(&lformat!("waiting..."));
            bar.set_prefix(&format!("{}:", key));
            let i = self.bars.spinners.len();
            self.bars.spinners.push(bar);
            self.bar_set_style(Crowbar::Spinner(i), CrowbarState::Running);

            i
        } else {
            0
        }
    }

    pub fn bar_set_message(&self, bar: Crowbar, msg: &str) {
        let bar = match bar {
            Crowbar::Main => if let Some(ref bar) = self.bars.mainbar { bar } else { return; },
            Crowbar::Second => if let Some(ref bar) = self.bars.secondbar { bar } else { return; },
            Crowbar::Spinner(i) => if i < self.bars.spinners.len() { &self.bars.spinners[i] } else { return; },
        };
        bar.set_message(msg);
    }

    /// Sets the style of a  bar
    fn bar_set_style(&self, bar: Crowbar, state: CrowbarState) -> () {
        let pb = match bar {
            Crowbar::Main => if let Some(ref bar) = self.bars.mainbar { bar } else { return; },
            Crowbar::Second => if let Some(ref bar) = self.bars.secondbar { bar } else { return; },
            Crowbar::Spinner(i) => if i < self.bars.spinners.len() { &self.bars.spinners[i] } else { return; },

        };
        let emoji = self.bars.emoji;
        let mut style = match bar {
            Crowbar::Second => ProgressStyle::default_bar(),
            _ => ProgressStyle::default_spinner()
        };

        let color = match state {
            CrowbarState::Running => "yellow",
            CrowbarState::Success => "cyan",
            CrowbarState::Error => "red",
        };
        let tick_chars = match (bar, emoji) {
            (Crowbar::Main, false)  | (Crowbar::Spinner(_), false) => "-\\|/",
            (Crowbar::Main, true) => "🕛🕐🕑🕒🕓🕔🕔🕕🕖🕗🕘🕘🕙🕚",
            (Crowbar::Spinner(_), true) => "◐◓◑◒",
            (_, _) => ""
        };
        let end_tick = match (state, emoji) {
            (CrowbarState::Running, _) => "V",
            (CrowbarState::Success, true) => "✔",
            (CrowbarState::Error, true) => "❌",
            (CrowbarState::Success, false) => "V",
            (CrowbarState::Error, false) => "X",
        };
        match bar {
            Crowbar::Second => {
                style = style.template("{bar:40.cyan/blue} {percent:>7} {wide_msg}")
                    .progress_chars("##-");
            },
            bar => {
                style = style.tick_chars(&format!("{}{}", tick_chars, end_tick));
                match bar {
                    Crowbar::Spinner(_) => {
                        style = style
                            .template(&format!("{{spinner:.bold.{color}}} {{prefix}} {{wide_msg}}",
                                               color = color));
                    },
                    _ => {
                        style = style
                            .template(&format!("{{spinner:.bold.{color}}} {{prefix}}{{wide_msg}}",
                                               color = color));
                    },
                };
            },
        }
        pb.set_style(style);
    }
}

impl Drop for Book {
    fn drop(&mut self) {
        if let Some(ref bar) = self.bars.secondbar {
            bar.finish_and_clear();
        }
        if let Some(ref bar) = self.bars.mainbar {
            bar.finish();
            let guard = mem::replace(&mut self.bars.guard, None);
            guard.unwrap()
                .join()
                .unwrap();
        }
    }
}
<|endoftext|>