<commit_before>use std::io::Cursor;

use kuchiki::NodeRef;
use kuchiki::NodeDataRef;
use kuchiki::NodeData;
use kuchiki::ElementData;

use model::ListTopicItem;
use model::ListTopicTitleItem;
use model::ListTopicAuthorItem;
use model::ShowItem;
use model::ShowReplyItem;
use model::UrlQueryItem;
use reply_model::*;

use regex::Regex;
use url::Url;
use std::collections::HashMap;

#[derive(Clone)]
pub struct Builder { }

impl Builder {
    pub fn new() -> Self {
        Builder {}
    }
    pub fn url_query_item(&mut self, url: &str) -> UrlQueryItem {
        parse_url_query_item(&url)
    }
}

impl Builder {
    pub fn show_item(&mut self, document: &NodeRef,  url: &str) -> Result<ShowItem, &'static str> {
        let url_query = parse_url_query_item(&url);

        let (title, reply_count) = {
            let repliers_tr = document.select(".repliers tr").ok().expect("fail to build title and reply_count, reason: 'repliers_tr' not found").next().expect("fail to build title and reply_count, reason: 'repliers_tr' not found");
            let repliers_header = repliers_tr.as_node()
                                             .select(".repliers_header")
                                             .ok().expect("fail to build title and reply_count, reason: 'repliers_header' not found")
                                             .last()
                                             .expect("fail to build title and reply_count, reason: 'repliers_header' not found");
            let divs = repliers_header.as_node().select("div").ok().expect("fail to build title and reply_count, reason: 'divs' not found").collect::<Vec<_>>();

            let topic_data = divs.iter()
                                 .enumerate()
                                 .map(|(index, div)| {
                                     let s_trimmed = div.text_contents().trim().to_string();
                                     if index == 1 {
                                         let re = Regex::new(r"^(?P<count>\d+)個回應$").expect("fail to build title and reply_count, reason: invalid regex");
                                         let cap = re.captures(&s_trimmed).expect("fail to build title and reply_count, reason: 'cap' not found");
                                         // String::from(cap.name("count").unwrap_or("0"))
                                         cap.name("count").unwrap_or("0").to_string()
                                     } else {
                                         s_trimmed
                                     }
                                 })
                                 .collect::<Vec<_>>();

            if topic_data.len() < 2 {
                panic!("length of topic_data is invalid.")
            }

            (topic_data.get(0).expect("fail to build title and reply_count, reason: 'topic_data' not found").to_string(), // return as title
             topic_data.get(1).expect("fail to build title and reply_count, reason: 'topic_data' not found").to_string() /* return as reply_count */)
        };

        let (page, max_page) = {

            let page_select = document.select("select[name='page']").ok().expect("fail to build page and max_page, reason: 'page_select' not found").last().expect("fail to build page and max_page, reason: 'page_select' not found");
            let page_str = page_select.as_node()
                                      .select("option[selected='selected']")
                                      .ok().expect("fail to build page and max_page, reason: 'page_str' not found")
                                      .next()
                                      .expect("fail to build page and max_page, reason: 'page_str' not found");
            let max_page_str = page_select.as_node().select("option").ok().expect("fail to build page and max_page, reason: 'max_page_str' not found").last().expect("fail to build page and max_page, reason: 'max_page_str' not found");

            let page = page_str.text_contents().trim().to_string().parse::<usize>().unwrap_or(0);
            let max_page = max_page_str.text_contents()
                                       .trim()
                                       .to_string()
                                       .parse::<usize>()
                                       .unwrap_or(0);

            (page, max_page)
        };

        let replies = parse_show_reply_items(&document);

        let show_item = ShowItem {
            url_query: url_query,
            replies: replies,
            page: page,
            max_page: max_page,
            reply_count: String::from(reply_count),
            title: String::from(title),
        };

        Ok(show_item)
    }
    pub fn default_show_item(&self) -> ShowItem {
        default_show_item()
    }
}

impl Builder {
    pub fn list_topic_items(&mut self, document: &NodeRef) -> Vec<ListTopicItem> {
        parse_list_topic_items(&document)
    }
    pub fn default_list_item(&self) -> ListTopicItem {
        default_list_item()
    }
}

fn default_show_item() -> ShowItem {
    ShowItem {
        url_query: UrlQueryItem { channel: "".to_string(), message: String::from("") },
        replies: vec![],
        page: 0,
        max_page: 0,
        reply_count: String::from(""),
        title: String::from(""),
    }
}

fn default_list_item() -> ListTopicItem {
    ListTopicItem {
        title: ListTopicTitleItem {
            url: "".to_string(),
            url_query: UrlQueryItem {
                channel: "".to_string(),
                message: "".to_string()
            },
            text: "".to_string(),
            num_of_pages: 0
        },
        author: ListTopicAuthorItem {
            url: "".to_string(),
            name: "".to_string()
        },
        last_replied_date: "".to_string(),
        last_replied_time: "".to_string(),
        reply_count: "".to_string(),
        rating: "".to_string()
    }
}

fn parse_list_topic_items(document: &NodeRef) -> Vec<ListTopicItem>{

    let trs = match document.select(".Topic_ListPanel tr[id]") {
            Ok(trs) => trs,
            Err(e) => panic!("{:?}", e)
    };

    trs.enumerate().map(|(i, tr)| {
        let items = match tr.as_node().select("td") {
            Ok(items) => items,
            Err(e) => panic!("{:?}", e)
        };
        let mut result = default_list_item();

        for (j, item) in items.enumerate().filter(|&(j, _)| j > 0 && j < 6) {
            match j {
                1 => { result.title = parse_list_topic_title_item(&item) },
                2 => { result.author = parse_list_topic_author_item(&item) },
                3 => {
                    let (date, time) = {
                        let text = item.text_contents().trim().to_string();
                        let map = text.split("\n").map(|x| x.trim().to_string()).collect::<Vec<_>>();
                        if map.len() < 2 {
                            panic!("length of map is invalid.");
                        }
                        (map[0].clone(), map[1].clone())
                    };
                    result.last_replied_date = date;
                    result.last_replied_time = time;
                },
                4 => {
                    let text = item.text_contents().trim().to_string();
                    result.reply_count = text
                },
                5 => {
                    let text = item.text_contents().trim().to_string();
                    result.rating = text
                },
                _ => {}
            }
        }
        result
    }).collect::<Vec<_>>()
}

fn parse_list_topic_title_item(item: &NodeDataRef<ElementData>) -> ListTopicTitleItem {
    let (first_link, links_count) = {
        let mut links = match item.as_node().select("a") {
            Ok(links) => links,
            Err(e) => panic!("ERR: {:?}", e)
        };

        let first_link_option = links.next();
        let last_link_option = links.last();

        let first_link = match first_link_option {
            Some(first_link) => first_link,
            None => { panic!("ERR: Can't find 'first_link'.") }
        };

        let max_page = match last_link_option {
            Some(last_link) =>
                last_link.text_contents().trim().to_string().parse::<usize>()
                .unwrap_or(0)
            ,
            None => { 1 }
        };

        (first_link, max_page)
    };

    let (url_str, url_query_item) = {
        let attrs = &(first_link.attributes).borrow();
        let href = attrs.get("href").unwrap_or("");

        let base_url = match Url::parse("http://forum1.hkgolden.com/view.aspx") {
            Ok(url) => url,
            Err(e) => { panic!(format!("fail to parse Base URL. Reason: {}", e)) }
        };
        let url = match base_url.join(&href) {
            Ok(url) => url,
            Err(e) => { panic!(format!("fail to build URL. Reason: {}", e)) }
        };
        let url_str = url.into_string();
        let url_query_item = parse_url_query_item(&url_str);
        (url_str, url_query_item)
    };

    let text = first_link.text_contents().trim().to_string();

     ListTopicTitleItem {
        url: url_str,
        url_query: url_query_item,
        text: text,
        num_of_pages: links_count
    }
}

fn parse_list_topic_author_item(item: &NodeDataRef<ElementData>) -> ListTopicAuthorItem {
    let (url, name) = {
        let mut links = match item.as_node().select("a") {
            Ok(links) => links,
            Err(e) => panic!("ERR: {:?}", e)
        };

        let first_link_option = links.next();

        let first_link = match first_link_option {
            Some(first_link) => first_link,
            None => { panic!("ERR: Can't find 'first_link'.") }
        };

        let attrs = &(first_link.attributes).borrow();
        let href = attrs.get("href").unwrap_or("").to_string();
        let text = first_link.text_contents().trim().to_string();
        (href, text)
    };

    ListTopicAuthorItem {
       url: url,
       name: name
    }
}

fn parse_show_reply_items(document: &NodeRef) -> Vec<ShowReplyItem> {

    let replies_data = document.select(".repliers tr[userid][username]")
                               .ok().expect("fail to parse show reply items, reason: 'replies_data' not found")
                               .collect::<Vec<_>>();

    replies_data.iter()
                .enumerate()
                .map(|(index, tr)| {

                    let tr_attrs = (&tr.attributes).borrow();
                    let userid = tr_attrs.get("userid").expect("fail to parse show reply item, reason: 'userid' not found");
                    let username = tr_attrs.get("username").expect("fail to parse show reply item, reason: 'userame' not found");

                    let content_elm = tr.as_node()
                                        .select(".repliers_right .ContentGrid")
                                        .ok().expect("fail to parse show reply item, reason: 'content_elm' not found")
                                        .next()
                                        .expect("fail to parse show reply item, reason: 'content_elm' not found"); // first

                    let buff = Cursor::new(Vec::new());
                    let serialize_result = content_elm.as_node().serialize(&mut buff);
                    let vec = buff.into_inner();
                    let content = String::from_utf8(vec).expect("fail to parse show reply item, reason: 'content' invalid");

                    let datatime = tr.as_node()
                                     .select(".repliers_right span")
                                     .ok().expect("fail to parse show reply item, reason: 'datatime' not found")
                                     .last()
                                     .expect("fail to parse show reply item, reason: 'datatime' not found")
                                     .text_contents();

                    let mut vec: Vec<NodeType> = Vec::new();

                    vec = recursive(content_elm.as_node());

                    ShowReplyItem {
                        userid: String::from(userid),
                        username: String::from(username),
                        content: String::from(content),
                        body: vec,
                        published_at: String::from(datatime),
                    }
                })
                .collect::<Vec<_>>()
}

fn parse_url_query_item(url_str: &str) -> UrlQueryItem {
    let url = Url::parse(&url_str).expect("fail to parse url query item, reason: invalid url");
    let query = url.query().expect("fail to parse url query item, reason: invalid url query");
    let re = Regex::new(r"(\\?|&)(?P<key>[^&=]+)=(?P<value>[^&]+)").expect("fail to parse url query item, reason: invalid regex");

    let (channel, message) = {

        let mut map = HashMap::new();

        for cap in re.captures_iter(query) {
            let key = cap.name("key").unwrap_or("").to_string();
            let value = cap.name("value").unwrap_or("").to_string();
            map.entry(key).or_insert(value);
        }

        if map.len() < 2 {
            panic!("length of map is invalid.")
        }

        (
            map.get("type").expect("fail to parse url query item, reason: can not get value of 'type' attribute").to_string(),
            map.get("message").expect("fail to parse url query item, reason: can not get value of 'message' attribute").to_string()
        )
    };

    UrlQueryItem {
        channel: String::from(channel),
        message: String::from(message)
    }

}

fn recursive(elm: &NodeRef) -> Vec<NodeType> {

    let mut vec: Vec<NodeType> = Vec::new();

    for (index, child) in elm.children().enumerate() {
        // println!("[{}] => {:?}", index, child);
        let node_data = child.data().clone();

        match node_data {
            NodeData::Element(element_data) => {
                // println!("[{}] => [ELEMENT] => {:?}", index, element_data);
                // println!("[{}] => [ELEMENT] => {:?}", index, child);

                if element_data.name.local.trim().eq("blockquote") {

                    // println!("[{}] => [ELEMENT] => {:?}", index, child.children());
                    let subvec = recursive(&child);
                    let node = NodeType::BlockQuote(BlockQuoteNode { data: subvec });
                    vec.push(node);
                } else if element_data.name.local.trim().eq("br") {
                    let node = NodeType::Br(BrNode {});
                    vec.push(node);
                } else if element_data.name.local.trim().eq("img") {

                    let attrs = (&element_data.attributes).borrow();
                    let url = attrs.get("src").unwrap_or("");
                    let alt = attrs.get("alt").unwrap_or("");
                    let node = NodeType::Image(ImageNode { data: url.to_string(), alt: alt.to_string() });
                    vec.push(node);

                } else {
                    // println!("[{}] => [ELEMENT] => {:?}", index, child);
                    let mut subvec = recursive(&child);
                    vec.append(&mut subvec);
                }
            }
            NodeData::Text(rc) => {
                // println!("[{}] => [TEXT] => {:?}", index, rc);
                let d = rc.clone();
                let b = d.borrow();

                let s = b.trim().to_string();

                if s == "\n" {
                    // s = "\\n".to_string()
                    continue;
                }

                let node = NodeType::Text(TextNode { data: s });
                vec.push(node);
            }
            _ => {}
        }
    }
    vec
}
<commit_msg>extract parse_title_and_reply_count<commit_after>use std::io::Cursor;

use kuchiki::NodeRef;
use kuchiki::NodeDataRef;
use kuchiki::NodeData;
use kuchiki::ElementData;

use model::ListTopicItem;
use model::ListTopicTitleItem;
use model::ListTopicAuthorItem;
use model::ShowItem;
use model::ShowReplyItem;
use model::UrlQueryItem;
use reply_model::*;

use regex::Regex;
use url::Url;
use std::collections::HashMap;

#[derive(Clone)]
pub struct Builder { }

impl Builder {
    pub fn new() -> Self {
        Builder {}
    }
    pub fn url_query_item(&mut self, url: &str) -> UrlQueryItem {
        parse_url_query_item(&url)
    }
}

impl Builder {
    pub fn show_item(&mut self, document: &NodeRef,  url: &str) -> Result<ShowItem, &'static str> {

        let url_query = parse_url_query_item(&url);

        let (title, reply_count) = match parse_title_and_reply_count(&document, &url) {
            Ok((title,reply_count)) => (title, reply_count),
            Err(e) =>  { return Err(e) }
        };

        let (page, max_page) = {

            let page_select = document.select("select[name='page']").ok().expect("fail to build page and max_page, reason: 'page_select' not found").last().expect("fail to build page and max_page, reason: 'page_select' not found");
            let page_str = page_select.as_node()
                                      .select("option[selected='selected']")
                                      .ok().expect("fail to build page and max_page, reason: 'page_str' not found")
                                      .next()
                                      .expect("fail to build page and max_page, reason: 'page_str' not found");
            let max_page_str = page_select.as_node().select("option").ok().expect("fail to build page and max_page, reason: 'max_page_str' not found").last().expect("fail to build page and max_page, reason: 'max_page_str' not found");

            let page = page_str.text_contents().trim().to_string().parse::<usize>().unwrap_or(0);
            let max_page = max_page_str.text_contents()
                                       .trim()
                                       .to_string()
                                       .parse::<usize>()
                                       .unwrap_or(0);

            (page, max_page)
        };

        let replies = parse_show_reply_items(&document);

        let show_item = ShowItem {
            url_query: url_query,
            replies: replies,
            page: page,
            max_page: max_page,
            reply_count: String::from(reply_count),
            title: String::from(title),
        };

        Ok(show_item)
    }
    pub fn default_show_item(&self) -> ShowItem {
        default_show_item()
    }
}

impl Builder {
    pub fn list_topic_items(&mut self, document: &NodeRef) -> Vec<ListTopicItem> {
        parse_list_topic_items(&document)
    }
    pub fn default_list_item(&self) -> ListTopicItem {
        default_list_item()
    }
}

fn default_show_item() -> ShowItem {
    ShowItem {
        url_query: UrlQueryItem { channel: "".to_string(), message: String::from("") },
        replies: vec![],
        page: 0,
        max_page: 0,
        reply_count: String::from(""),
        title: String::from(""),
    }
}

fn default_list_item() -> ListTopicItem {
    ListTopicItem {
        title: ListTopicTitleItem {
            url: "".to_string(),
            url_query: UrlQueryItem {
                channel: "".to_string(),
                message: "".to_string()
            },
            text: "".to_string(),
            num_of_pages: 0
        },
        author: ListTopicAuthorItem {
            url: "".to_string(),
            name: "".to_string()
        },
        last_replied_date: "".to_string(),
        last_replied_time: "".to_string(),
        reply_count: "".to_string(),
        rating: "".to_string()
    }
}

fn parse_list_topic_items(document: &NodeRef) -> Vec<ListTopicItem>{

    let trs = match document.select(".Topic_ListPanel tr[id]") {
            Ok(trs) => trs,
            Err(e) => panic!("{:?}", e)
    };

    trs.enumerate().map(|(i, tr)| {
        let items = match tr.as_node().select("td") {
            Ok(items) => items,
            Err(e) => panic!("{:?}", e)
        };
        let mut result = default_list_item();

        for (j, item) in items.enumerate().filter(|&(j, _)| j > 0 && j < 6) {
            match j {
                1 => { result.title = parse_list_topic_title_item(&item) },
                2 => { result.author = parse_list_topic_author_item(&item) },
                3 => {
                    let (date, time) = {
                        let text = item.text_contents().trim().to_string();
                        let map = text.split("\n").map(|x| x.trim().to_string()).collect::<Vec<_>>();
                        if map.len() < 2 {
                            panic!("length of map is invalid.");
                        }
                        (map[0].clone(), map[1].clone())
                    };
                    result.last_replied_date = date;
                    result.last_replied_time = time;
                },
                4 => {
                    let text = item.text_contents().trim().to_string();
                    result.reply_count = text
                },
                5 => {
                    let text = item.text_contents().trim().to_string();
                    result.rating = text
                },
                _ => {}
            }
        }
        result
    }).collect::<Vec<_>>()
}

fn parse_list_topic_title_item(item: &NodeDataRef<ElementData>) -> ListTopicTitleItem {
    let (first_link, links_count) = {
        let mut links = match item.as_node().select("a") {
            Ok(links) => links,
            Err(e) => panic!("ERR: {:?}", e)
        };

        let first_link_option = links.next();
        let last_link_option = links.last();

        let first_link = match first_link_option {
            Some(first_link) => first_link,
            None => { panic!("ERR: Can't find 'first_link'.") }
        };

        let max_page = match last_link_option {
            Some(last_link) =>
                last_link.text_contents().trim().to_string().parse::<usize>()
                .unwrap_or(0)
            ,
            None => { 1 }
        };

        (first_link, max_page)
    };

    let (url_str, url_query_item) = {
        let attrs = &(first_link.attributes).borrow();
        let href = attrs.get("href").unwrap_or("");

        let base_url = match Url::parse("http://forum1.hkgolden.com/view.aspx") {
            Ok(url) => url,
            Err(e) => { panic!(format!("fail to parse Base URL. Reason: {}", e)) }
        };
        let url = match base_url.join(&href) {
            Ok(url) => url,
            Err(e) => { panic!(format!("fail to build URL. Reason: {}", e)) }
        };
        let url_str = url.into_string();
        let url_query_item = parse_url_query_item(&url_str);
        (url_str, url_query_item)
    };

    let text = first_link.text_contents().trim().to_string();

     ListTopicTitleItem {
        url: url_str,
        url_query: url_query_item,
        text: text,
        num_of_pages: links_count
    }
}

fn parse_list_topic_author_item(item: &NodeDataRef<ElementData>) -> ListTopicAuthorItem {
    let (url, name) = {
        let mut links = match item.as_node().select("a") {
            Ok(links) => links,
            Err(e) => panic!("ERR: {:?}", e)
        };

        let first_link_option = links.next();

        let first_link = match first_link_option {
            Some(first_link) => first_link,
            None => { panic!("ERR: Can't find 'first_link'.") }
        };

        let attrs = &(first_link.attributes).borrow();
        let href = attrs.get("href").unwrap_or("").to_string();
        let text = first_link.text_contents().trim().to_string();
        (href, text)
    };

    ListTopicAuthorItem {
       url: url,
       name: name
    }
}

// Show Item
fn parse_title_and_reply_count (document: &NodeRef,  url: &str) -> Result<(String, String), &'static str> {

    return match document.select(".repliers tr") {
        Ok(mut trs) => {
            return match trs.next()  {
                Some(repliers_tr) => {
                    let repliers_header = repliers_tr.as_node()
                                                     .select(".repliers_header")
                                                     .ok().expect("fail to build title and reply_count, reason: 'repliers_header' not found")
                                                     .last()
                                                     .expect("fail to build title and reply_count, reason: 'repliers_header' not found");
                    let divs = repliers_header.as_node().select("div").ok().expect("fail to build title and reply_count, reason: 'divs' not found").collect::<Vec<_>>();

                    let topic_data = divs.iter()
                                         .enumerate()
                                         .map(|(index, div)| {
                                             let s_trimmed = div.text_contents().trim().to_string();
                                             if index == 1 {
                                                 let re = Regex::new(r"^(?P<count>\d+)個回應$").expect("fail to build title and reply_count, reason: invalid regex");
                                                 let cap = re.captures(&s_trimmed).expect("fail to build title and reply_count, reason: 'cap' not found");
                                                 // String::from(cap.name("count").unwrap_or("0"))
                                                 cap.name("count").unwrap_or("0").to_string()
                                             } else {
                                                 s_trimmed
                                             }
                                         })
                                         .collect::<Vec<_>>();

                    if topic_data.len() < 2 {
                        Err(&"length of topic_data is invalid.")
                    } else {
                        Ok(
                            (topic_data.get(0).expect("fail to build title and reply_count, reason: 'topic_data' not found").to_string(), // return as title
                            topic_data.get(1).expect("fail to build title and reply_count, reason: 'topic_data' not found").to_string() /* return as reply_count */)
                        )
                    }
                },
                None => Err(&"fail to build title and reply_count, reason: 'repliers_tr' not found")
            }
        },
        Err(e) =>  Err("fail to build title and reply_count, reason: 'repliers_tr' not found")
    };

}

fn parse_show_reply_items(document: &NodeRef) -> Vec<ShowReplyItem> {

    let replies_data = document.select(".repliers tr[userid][username]")
                               .ok().expect("fail to parse show reply items, reason: 'replies_data' not found")
                               .collect::<Vec<_>>();

    replies_data.iter()
                .enumerate()
                .map(|(index, tr)| {

                    let tr_attrs = (&tr.attributes).borrow();
                    let userid = tr_attrs.get("userid").expect("fail to parse show reply item, reason: 'userid' not found");
                    let username = tr_attrs.get("username").expect("fail to parse show reply item, reason: 'userame' not found");

                    let content_elm = tr.as_node()
                                        .select(".repliers_right .ContentGrid")
                                        .ok().expect("fail to parse show reply item, reason: 'content_elm' not found")
                                        .next()
                                        .expect("fail to parse show reply item, reason: 'content_elm' not found"); // first

                    let mut buff = Cursor::new(Vec::new());
                    let serialize_result = content_elm.as_node().serialize(&mut buff);
                    let vec = buff.into_inner();
                    let content = String::from_utf8(vec).expect("fail to parse show reply item, reason: 'content' invalid");

                    let datatime = tr.as_node()
                                     .select(".repliers_right span")
                                     .ok().expect("fail to parse show reply item, reason: 'datatime' not found")
                                     .last()
                                     .expect("fail to parse show reply item, reason: 'datatime' not found")
                                     .text_contents();

                    let mut vec: Vec<NodeType> = Vec::new();

                    vec = recursive(content_elm.as_node());

                    ShowReplyItem {
                        userid: String::from(userid),
                        username: String::from(username),
                        content: String::from(content),
                        body: vec,
                        published_at: String::from(datatime),
                    }
                })
                .collect::<Vec<_>>()
}

fn parse_url_query_item(url_str: &str) -> UrlQueryItem {
    let url = Url::parse(&url_str).expect("fail to parse url query item, reason: invalid url");
    let query = url.query().expect("fail to parse url query item, reason: invalid url query");
    let re = Regex::new(r"(\\?|&)(?P<key>[^&=]+)=(?P<value>[^&]+)").expect("fail to parse url query item, reason: invalid regex");

    let (channel, message) = {

        let mut map = HashMap::new();

        for cap in re.captures_iter(query) {
            let key = cap.name("key").unwrap_or("").to_string();
            let value = cap.name("value").unwrap_or("").to_string();
            map.entry(key).or_insert(value);
        }

        if map.len() < 2 {
            panic!("length of map is invalid.")
        }

        (
            map.get("type").expect("fail to parse url query item, reason: can not get value of 'type' attribute").to_string(),
            map.get("message").expect("fail to parse url query item, reason: can not get value of 'message' attribute").to_string()
        )
    };

    UrlQueryItem {
        channel: String::from(channel),
        message: String::from(message)
    }

}

fn recursive(elm: &NodeRef) -> Vec<NodeType> {

    let mut vec: Vec<NodeType> = Vec::new();

    for (index, child) in elm.children().enumerate() {
        // println!("[{}] => {:?}", index, child);
        let node_data = child.data().clone();

        match node_data {
            NodeData::Element(element_data) => {
                // println!("[{}] => [ELEMENT] => {:?}", index, element_data);
                // println!("[{}] => [ELEMENT] => {:?}", index, child);

                if element_data.name.local.trim().eq("blockquote") {

                    // println!("[{}] => [ELEMENT] => {:?}", index, child.children());
                    let subvec = recursive(&child);
                    let node = NodeType::BlockQuote(BlockQuoteNode { data: subvec });
                    vec.push(node);
                } else if element_data.name.local.trim().eq("br") {
                    let node = NodeType::Br(BrNode {});
                    vec.push(node);
                } else if element_data.name.local.trim().eq("img") {

                    let attrs = (&element_data.attributes).borrow();
                    let url = attrs.get("src").unwrap_or("");
                    let alt = attrs.get("alt").unwrap_or("");
                    let node = NodeType::Image(ImageNode { data: url.to_string(), alt: alt.to_string() });
                    vec.push(node);

                } else {
                    // println!("[{}] => [ELEMENT] => {:?}", index, child);
                    let mut subvec = recursive(&child);
                    vec.append(&mut subvec);
                }
            }
            NodeData::Text(rc) => {
                // println!("[{}] => [TEXT] => {:?}", index, rc);
                let d = rc.clone();
                let b = d.borrow();

                let s = b.trim().to_string();

                if s == "\n" {
                    // s = "\\n".to_string()
                    continue;
                }

                let node = NodeType::Text(TextNode { data: s });
                vec.push(node);
            }
            _ => {}
        }
    }
    vec
}
<|endoftext|>


<commit_before>extern crate sdl2;
extern crate minifb;
use minifb::*;
use utils;
use debugger;
pub mod cpu;
pub mod opcodes;
mod clock;
pub mod memory;
mod io;
mod cia;
pub mod vic;
mod sid;

pub const SCREEN_WIDTH:  usize = 384; // extend 20 pixels left and right for the borders
pub const SCREEN_HEIGHT: usize = 272; // extend 36 pixels top and down for the borders

// PAL clock frequency in Hz
const CLOCK_FREQ: f64 = 985248.0;

pub struct C64
{
    pub window: minifb::Window,
    memory: memory::MemShared,
    io:    io::IO,
    clock: clock::Clock,
    cpu: cpu::CPUShared,
    cia1: cia::CIAShared,
    cia2: cia::CIAShared,
    vic: vic::VICShared,
    sid: sid::SID,

    debugger: debugger::Debugger,
    boot_complete: bool,
    pub file_to_load: String,
    cycle_count: u32,
}

impl C64
{
    pub fn new() -> C64
    {
        let memory = memory::Memory::new_shared();
        let vic    = vic::VIC::new_shared();
        let cia1   = cia::CIA::new_shared(true);
        let cia2   = cia::CIA::new_shared(false);
        let cpu    = cpu::CPU::new_shared();

        let mut c64 = C64
        {
            window: Window::new("Rust64", SCREEN_WIDTH, SCREEN_HEIGHT, Scale::X1).unwrap(),
            memory: memory.clone(), // shared system memory (RAM, ROM, IO registers)
            io:    io::IO::new(),
            clock: clock::Clock::new(CLOCK_FREQ),
            cpu: cpu.clone(),
            cia1: cia1.clone(),
            cia2: cia2.clone(),
            vic: vic.clone(),
            sid: sid::SID::new(),

            debugger: debugger::Debugger::new(),
            boot_complete: false,
            file_to_load: String::new(),
            cycle_count: 0,
        };

        c64.window.set_position(75, 20);

        // cyclic dependencies are not possible in Rust (yet?), so we have
        // to resort to setting references manually
        c64.cia1.borrow_mut().set_references(memory.clone(), cpu.clone(), vic.clone());
        c64.cia2.borrow_mut().set_references(memory.clone(), cpu.clone(), vic.clone());
        c64.vic.borrow_mut().set_references(memory.clone(), cpu.clone());
        c64.sid.set_references(memory.clone());
        c64.cpu.borrow_mut().set_references(memory.clone(), vic.clone(), cia1.clone(), cia2.clone());

        drop(memory);
        drop(cia1);
        drop(cia2);
        drop(vic);
        drop(cpu);

        c64
    }

    pub fn reset(&mut self)
    {
        self.memory.borrow_mut().reset();
        self.cpu.borrow_mut().reset();
        self.cia1.borrow_mut().reset();
        self.cia2.borrow_mut().reset();
    }

    fn load_prg(&mut self, filename: &str)
    {
        let prg_data = utils::open_file(filename, 0);
        let start_address: u16 = ((prg_data[1] as u16) << 8) | (prg_data[0] as u16);
        //let end_addr = start_address + (prg_data.len() as u16) - 2;
        println!("Loading {} to start location at ${:04x} ({})", filename, start_address, start_address);

        for i in 2..(prg_data.len())
        {
            //println!("${:04x} -> {:02x}", start_address + (i as u16) - 2, prg_data[i]);
            self.memory.borrow_mut().write_byte(start_address + (i as u16) - 2, prg_data[i]);
        }

        //self.memory.borrow_mut().write_word_le(0x02b, start_address);
        //self.memory.borrow_mut().write_word_le(0x02d, end_addr);
        //self.memory.borrow_mut().write_word_le(0x02f, end_addr);
        //self.memory.borrow_mut().write_word_le(0x031, end_addr);
    }

    pub fn run(&mut self)
    {
        if !self.boot_complete
        {
            // $A480 is the BASIC warm start sequence - safe to assume we can load a cmdline program now
            //self.boot_complete = self.cpu.borrow_mut().PC == 0xA480;

            //if self.boot_complete
            {
                //let prg_file = &self.file_to_load.to_owned()[..];
                let prg_file = "bcs-01.prg";     // ok
                let prg_file = "triad-01.prg";
                //let prg_file = "dd-01.prg";    // ok - need sprites
               // let prg_file = "flt-01.prg";  // ok - blinking
                //let prg_file = "esi-02.prg";   // ok - blinking
                //let prg_file = "htl-03.prg";
                //let prg_file = "ikari-02.prg"; // ok - need sprites
                //let prg_file = "img.prg";
                let prg_file ="tests/bgcolor.prg";
                //let prg_file ="spritedemo.prg";
                //let prg_file ="flapper.prg";
                //let prg_file ="superball.prg";
                //let prg_file = "flt-09.prg";    // bw?
                //let prg_file = "newage-03.prg"; // ok
                //let prg_file = "orion-26.prg";  // ok
                //let prg_file = "energy-09.prg";
                //let prg_file = "jam-10.prg";  // ok
                //let prg_file = "tpi-01.prg";
                //let prg_file = "711-01.prg"; // ok - need sprites

                if prg_file.len() > 0
                {

                    if self.window.is_key_pressed(Key::F11, KeyRepeat::No)
                        { self.boot_complete = true; self.load_prg(prg_file); }
                }
            }
        }

        if self.clock.tick()
        {
            let mut should_trigger_vblank = false;

            if self.vic.borrow_mut().update(self.cycle_count, &mut should_trigger_vblank)
            {
                self.sid.update();
            }

            self.cia1.borrow_mut().process_irq();
            self.cia2.borrow_mut().process_irq();
            self.cia1.borrow_mut().update();
            self.cia2.borrow_mut().update();

            self.cpu.borrow_mut().update();

            self.debugger.update_raster_window(&mut self.vic);

            if should_trigger_vblank
            {
                self.debugger.render(&mut self.cpu, &mut self.memory);
                self.window.update(&self.vic.borrow_mut().window_buffer);
                self.io.update(&self.window, &mut self.cia1);
                self.cia1.borrow_mut().count_tod();
                self.cia2.borrow_mut().count_tod();

                if self.io.check_restore_key(&self.window)
                {
                    self.cpu.borrow_mut().trigger_nmi();
                }
            }

            self.cycle_count += 1;
        }
    }
}
<commit_msg>autoload prg files on start<commit_after>extern crate sdl2;
extern crate minifb;
use minifb::*;
use utils;
use debugger;
pub mod cpu;
pub mod opcodes;
mod clock;
pub mod memory;
mod io;
mod cia;
pub mod vic;
mod sid;

pub const SCREEN_WIDTH:  usize = 384; // extend 20 pixels left and right for the borders
pub const SCREEN_HEIGHT: usize = 272; // extend 36 pixels top and down for the borders

// PAL clock frequency in Hz
const CLOCK_FREQ: f64 = 985248.0;

pub struct C64
{
    pub window: minifb::Window,
    memory: memory::MemShared,
    io:    io::IO,
    clock: clock::Clock,
    cpu: cpu::CPUShared,
    cia1: cia::CIAShared,
    cia2: cia::CIAShared,
    vic: vic::VICShared,
    sid: sid::SID,

    debugger: debugger::Debugger,
    boot_complete: bool,
    pub file_to_load: String,
    cycle_count: u32,
}

impl C64
{
    pub fn new() -> C64
    {
        let memory = memory::Memory::new_shared();
        let vic    = vic::VIC::new_shared();
        let cia1   = cia::CIA::new_shared(true);
        let cia2   = cia::CIA::new_shared(false);
        let cpu    = cpu::CPU::new_shared();

        let mut c64 = C64
        {
            window: Window::new("Rust64", SCREEN_WIDTH, SCREEN_HEIGHT, Scale::X1).unwrap(),
            memory: memory.clone(), // shared system memory (RAM, ROM, IO registers)
            io:    io::IO::new(),
            clock: clock::Clock::new(CLOCK_FREQ),
            cpu: cpu.clone(),
            cia1: cia1.clone(),
            cia2: cia2.clone(),
            vic: vic.clone(),
            sid: sid::SID::new(),

            debugger: debugger::Debugger::new(),
            boot_complete: false,
            file_to_load: String::new(),
            cycle_count: 0,
        };

        c64.window.set_position(75, 20);

        // cyclic dependencies are not possible in Rust (yet?), so we have
        // to resort to setting references manually
        c64.cia1.borrow_mut().set_references(memory.clone(), cpu.clone(), vic.clone());
        c64.cia2.borrow_mut().set_references(memory.clone(), cpu.clone(), vic.clone());
        c64.vic.borrow_mut().set_references(memory.clone(), cpu.clone());
        c64.sid.set_references(memory.clone());
        c64.cpu.borrow_mut().set_references(memory.clone(), vic.clone(), cia1.clone(), cia2.clone());

        drop(memory);
        drop(cia1);
        drop(cia2);
        drop(vic);
        drop(cpu);

        c64
    }

    pub fn reset(&mut self)
    {
        self.memory.borrow_mut().reset();
        self.cpu.borrow_mut().reset();
        self.cia1.borrow_mut().reset();
        self.cia2.borrow_mut().reset();
    }

    fn load_prg(&mut self, filename: &str)
    {
        let prg_data = utils::open_file(filename, 0);
        let start_address: u16 = ((prg_data[1] as u16) << 8) | (prg_data[0] as u16);
        //let end_addr = start_address + (prg_data.len() as u16) - 2;
        println!("Loading {} to start location at ${:04x} ({})", filename, start_address, start_address);

        for i in 2..(prg_data.len())
        {
            //println!("${:04x} -> {:02x}", start_address + (i as u16) - 2, prg_data[i]);
            self.memory.borrow_mut().write_byte(start_address + (i as u16) - 2, prg_data[i]);
        }

        //self.memory.borrow_mut().write_word_le(0x02b, start_address);
        //self.memory.borrow_mut().write_word_le(0x02d, end_addr);
        //self.memory.borrow_mut().write_word_le(0x02f, end_addr);
        //self.memory.borrow_mut().write_word_le(0x031, end_addr);
    }

    pub fn run(&mut self)
    {
        if !self.boot_complete
        {
            // $A480 is the BASIC warm start sequence - safe to assume we can load a cmdline program now
            self.boot_complete = self.cpu.borrow_mut().PC == 0xA480;

            if self.boot_complete
            {
                //let prg_file = &self.file_to_load.to_owned()[..];
                let prg_file = "bcs-01.prg";     // ok
                let prg_file = "triad-01.prg";
                let prg_file = "dd-01.prg";    // sprites null
                //let prg_file = "flt-01.prg";  // ok - blinking
                //let prg_file = "esi-02.prg";   // ok - blinking
                //let prg_file = "htl-03.prg";
                //let prg_file = "ikari-02.prg"; // ok
                //let prg_file = "img.prg";
                let prg_file ="tests/bgcolor.prg";
                //let prg_file = "spritedemo.prg";
                //let prg_file ="flapper.prg";
                //let prg_file ="superball.prg";
                //let prg_file = "flt-09.prg";    // bw - blinking
                //let prg_file = "newage-03.prg"; // ok
                //let prg_file = "orion-26.prg";  // ok
                //let prg_file = "energy-09.prg";
                //let prg_file = "jam-10.prg";  // ok
                //let prg_file = "tpi-01.prg";
                //let prg_file = "711-01.prg"; // ok - blinking

                if prg_file.len() > 0
                {
                    //if self.window.is_key_pressed(Key::F11, KeyRepeat::No) {
                    self.boot_complete = true; self.load_prg(prg_file);
                    //}
                }
            }
        }

        if self.clock.tick()
        {
            let mut should_trigger_vblank = false;

            if self.vic.borrow_mut().update(self.cycle_count, &mut should_trigger_vblank)
            {
                self.sid.update();
            }

            self.cia1.borrow_mut().process_irq();
            self.cia2.borrow_mut().process_irq();
            self.cia1.borrow_mut().update();
            self.cia2.borrow_mut().update();

            self.cpu.borrow_mut().update();

            self.debugger.update_raster_window(&mut self.vic);

            if should_trigger_vblank
            {
                self.debugger.render(&mut self.cpu, &mut self.memory);
                self.window.update(&self.vic.borrow_mut().window_buffer);
                self.io.update(&self.window, &mut self.cia1);
                self.cia1.borrow_mut().count_tod();
                self.cia2.borrow_mut().count_tod();

                if self.io.check_restore_key(&self.window)
                {
                    self.cpu.borrow_mut().trigger_nmi();
                }
            }

            self.cycle_count += 1;
        }
    }
}
<|endoftext|>


<commit_before>/*!
    Menu control definition that integrates with the built-in window type
*/
/*
    Copyright (C) 2016  Gabriel Dubé

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

use std::hash::Hash;
use std::any::TypeId;
use std::mem;

use winapi::{HMENU, UINT};

use ui::Ui;
use controls::{Control, ControlT, ControlType, AnyHandle};
use error::Error;
use events::Event;

static mut menu_items_id: UINT = 0;

/**
    A template to create menu controls

    Members:
      • `text` : The menu text
      • `parent` : ID of the parent control to add the menu to
*/
#[derive(Clone)]
pub struct MenuT<S: Clone+Into<String>, ID: Hash+Clone> {
    pub text: S,
    pub parent: ID,
}

impl<S: Clone+Into<String>, ID: Hash+Clone> ControlT<ID> for MenuT<S, ID> {
    fn type_id(&self) -> TypeId { TypeId::of::<Menu>() }

    fn events(&self) -> Vec<Event> {
        vec![Event::Destroyed]
    }

    fn build(&self, ui: &Ui<ID>) -> Result<Box<Control>, Error> {
        let handle_result = unsafe { build_menu(ui, self) };
        match handle_result {
            Ok((h, parent)) => { Ok( Box::new(Menu{handle: h, parent: parent}) as Box<Control> ) },
            Err(e) => Err(e)
        }
    }
}

/**
    A menu control
*/
pub struct Menu {
    handle: HMENU,
    parent: AnyHandle
}

impl Control for Menu {

    fn handle(&self) -> AnyHandle {
        AnyHandle::HMENU(self.handle)
    }

    fn control_type(&self) -> ControlType {
        ControlType::Menu
    }

    fn free(&mut self) {
        use user32::DestroyMenu;
        use low::menu_helper::remove_menu_from_parent;

        unsafe{ remove_menu_from_parent(self.handle, &self.parent) };

        unsafe{ DestroyMenu(self.handle) };
    }

}

/**
    A template to create menuitems

    Members:
      • `text` : The menu text
      • `parent` : ID of the parent control to add the menu to
*/
#[derive(Clone)]
pub struct MenuItemT<S: Clone+Into<String>, ID: Hash+Clone> {
    pub text: S,
    pub parent: ID,
}

impl<S: Clone+Into<String>, ID: Hash+Clone> ControlT<ID> for MenuItemT<S, ID> {
    fn type_id(&self) -> TypeId { TypeId::of::<MenuItem>() }

    fn events(&self) -> Vec<Event> {
        vec![Event::Destroyed, Event::Click]
    }

   #[allow(unused_variables)]
    fn build(&self, ui: &Ui<ID>) -> Result<Box<Control>, Error> {
        let handle_result = unsafe { build_menu_item(ui, self) };
        match handle_result {
            Ok((parent, uid)) => { Ok( Box::new(MenuItem{parent: parent, unique_id: uid}) as Box<Control> ) },
            Err(e) => Err(e)
        }
    }
}

/**
    A menu item control
*/
pub struct MenuItem {
    parent: HMENU,
    unique_id: UINT
}

/**
    A menu item control
*/
impl Control for MenuItem {

    fn handle(&self) -> AnyHandle {
        AnyHandle::HMENU_ITEM(self.parent, self.unique_id)
    }

    fn control_type(&self) -> ControlType {
        ControlType::MenuItem
    }

    fn free(&mut self) {
        use low::menu_helper::remove_menu_item_from_parent;
        unsafe{ remove_menu_item_from_parent(self.parent, self.unique_id) };
    }
}

/*
    Private unsafe menu/menuitem methods
*/

#[inline(always)]
unsafe fn build_menu<S: Clone+Into<String>, ID: Clone+Hash>(ui: &Ui<ID>, t: &MenuT<S, ID>) -> Result<(HMENU, AnyHandle), Error> {
    use user32::{CreateMenu, AppendMenuW, GetMenu, SetMenu, DrawMenuBar};
    use winapi::{MF_STRING, MF_POPUP};
    use low::menu_helper::use_menu_command;
    use low::other_helper::to_utf16;

    let ph_result = ui.handle_of(&t.parent);
    if ph_result.is_err() { return Err(ph_result.err().unwrap()); }

    match ph_result.unwrap() {
        AnyHandle::HWND(parent_h) => {
            let mut menubar = GetMenu(parent_h);
            if menubar.is_null() {
                // If the window do not have a menu bar, create one
                menubar = CreateMenu();
                use_menu_command(menubar);
                SetMenu(parent_h, menubar);
            }

            let h = CreateMenu();
            use_menu_command(h);

            let text = to_utf16(t.text.clone().into().as_ref());
            AppendMenuW(menubar, MF_STRING|MF_POPUP, mem::transmute(h), text.as_ptr());

            DrawMenuBar(parent_h); // Draw the menu bar to make sure the changes are visible
            Ok( ( h, AnyHandle::HWND(parent_h)) )
        },
        AnyHandle::HMENU(parent_h) => {
            let h = CreateMenu();
            use_menu_command(h);

            let text = to_utf16(t.text.clone().into().as_ref());
            AppendMenuW(parent_h, MF_STRING|MF_POPUP, mem::transmute(h), text.as_ptr());
            Ok( ( h, AnyHandle::HMENU(parent_h) ) )
        },
        AnyHandle::HMENU_ITEM(_, _) => Err(Error::BadParent("Window or menu parent required, got MenuItem".to_string())),
        AnyHandle::HFONT(_) =>  Err(Error::BadParent("Window or menu parent required, got Font".to_string())),
        AnyHandle::None | AnyHandle::Custom(_) =>  Err(Error::BadParent("Window or menu parent required, got control with no defined handle".to_string())),
   }
}

#[inline(always)]
unsafe fn build_menu_item<S: Clone+Into<String>, ID: Clone+Hash>(ui: &Ui<ID>, t: &MenuItemT<S, ID>) -> Result<(HMENU, UINT), Error> {
    use user32::{AppendMenuW, CreateMenu, GetMenu, SetMenu, DrawMenuBar};
    use winapi::{MF_STRING, UINT_PTR};
    use low::other_helper::to_utf16;

    let ph_result = ui.handle_of(&t.parent);
    if ph_result.is_err() { return Err(ph_result.err().unwrap()); }

    menu_items_id += 1;

    match ph_result.unwrap() {
        AnyHandle::HWND(parent_h) => {
            let mut menubar = GetMenu(parent_h);
            if menubar.is_null() {
                // If the window do not have a menu bar, create one
                menubar = CreateMenu();
                SetMenu(parent_h, menubar);
            }

            let text = to_utf16(t.text.clone().into().as_ref());
            let new_id = menu_items_id;

            AppendMenuW(menubar, MF_STRING, new_id as UINT_PTR, text.as_ptr());
            DrawMenuBar(parent_h); // Draw the menu bar to make sure the changes are visible

            Ok( (menubar, new_id) )
        },
        AnyHandle::HMENU(parent_h) => {
            let text = to_utf16(t.text.clone().into().as_ref());
            menu_items_id += 1;
            AppendMenuW(parent_h, MF_STRING, menu_items_id as UINT_PTR, text.as_ptr());
            Ok( (parent_h, menu_items_id) )
        },
        AnyHandle::HMENU_ITEM(_, _) => Err(Error::BadParent("Window or menu parent required, got MenuItem".to_string())),
        AnyHandle::HFONT(_) =>  Err(Error::BadParent("Window or menu parent required, got Font".to_string())),
        AnyHandle::None | AnyHandle::Custom(_) =>  Err(Error::BadParent("Window or menu parent required, got control with no defined handle".to_string())),
    }
}<commit_msg>comment for the dreadful menuid bug<commit_after>/*!
    Menu control definition that integrates with the built-in window type
*/
/*
    Copyright (C) 2016  Gabriel Dubé

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

use std::hash::Hash;
use std::any::TypeId;
use std::mem;

use winapi::{HMENU, UINT};

use ui::Ui;
use controls::{Control, ControlT, ControlType, AnyHandle};
use error::Error;
use events::Event;

static mut menu_items_id: UINT = 0;

/**
    A template to create menu controls

    Members:
      • `text` : The menu text
      • `parent` : ID of the parent control to add the menu to
*/
#[derive(Clone)]
pub struct MenuT<S: Clone+Into<String>, ID: Hash+Clone> {
    pub text: S,
    pub parent: ID,
}

impl<S: Clone+Into<String>, ID: Hash+Clone> ControlT<ID> for MenuT<S, ID> {
    fn type_id(&self) -> TypeId { TypeId::of::<Menu>() }

    fn events(&self) -> Vec<Event> {
        vec![Event::Destroyed]
    }

    fn build(&self, ui: &Ui<ID>) -> Result<Box<Control>, Error> {
        let handle_result = unsafe { build_menu(ui, self) };
        match handle_result {
            Ok((h, parent)) => { Ok( Box::new(Menu{handle: h, parent: parent}) as Box<Control> ) },
            Err(e) => Err(e)
        }
    }
}

/**
    A menu control
*/
pub struct Menu {
    handle: HMENU,
    parent: AnyHandle
}

impl Control for Menu {

    fn handle(&self) -> AnyHandle {
        AnyHandle::HMENU(self.handle)
    }

    fn control_type(&self) -> ControlType {
        ControlType::Menu
    }

    fn free(&mut self) {
        use user32::DestroyMenu;
        use low::menu_helper::remove_menu_from_parent;

        unsafe{ remove_menu_from_parent(self.handle, &self.parent) };

        unsafe{ DestroyMenu(self.handle) };
    }

}

/**
    A template to create menuitems

    Members:
      • `text` : The menu text
      • `parent` : ID of the parent control to add the menu to
*/
#[derive(Clone)]
pub struct MenuItemT<S: Clone+Into<String>, ID: Hash+Clone> {
    pub text: S,
    pub parent: ID,
}

impl<S: Clone+Into<String>, ID: Hash+Clone> ControlT<ID> for MenuItemT<S, ID> {
    fn type_id(&self) -> TypeId { TypeId::of::<MenuItem>() }

    fn events(&self) -> Vec<Event> {
        vec![Event::Destroyed, Event::Click]
    }

   #[allow(unused_variables)]
    fn build(&self, ui: &Ui<ID>) -> Result<Box<Control>, Error> {
        let handle_result = unsafe { build_menu_item(ui, self) };
        match handle_result {
            Ok((parent, uid)) => { Ok( Box::new(MenuItem{parent: parent, unique_id: uid}) as Box<Control> ) },
            Err(e) => Err(e)
        }
    }
}

/**
    A menu item control
*/
pub struct MenuItem {
    parent: HMENU,
    unique_id: UINT
}

/**
    A menu item control
*/
impl Control for MenuItem {

    fn handle(&self) -> AnyHandle {
        AnyHandle::HMENU_ITEM(self.parent, self.unique_id)
    }

    fn control_type(&self) -> ControlType {
        ControlType::MenuItem
    }

    fn free(&mut self) {
        use low::menu_helper::remove_menu_item_from_parent;
        unsafe{ remove_menu_item_from_parent(self.parent, self.unique_id) };
    }
}

/*
    Private unsafe menu/menuitem methods
*/

#[inline(always)]
unsafe fn build_menu<S: Clone+Into<String>, ID: Clone+Hash>(ui: &Ui<ID>, t: &MenuT<S, ID>) -> Result<(HMENU, AnyHandle), Error> {
    use user32::{CreateMenu, AppendMenuW, GetMenu, SetMenu, DrawMenuBar};
    use winapi::{MF_STRING, MF_POPUP};
    use low::menu_helper::use_menu_command;
    use low::other_helper::to_utf16;

    let ph_result = ui.handle_of(&t.parent);
    if ph_result.is_err() { return Err(ph_result.err().unwrap()); }

    match ph_result.unwrap() {
        AnyHandle::HWND(parent_h) => {
            let mut menubar = GetMenu(parent_h);
            if menubar.is_null() {
                // If the window do not have a menu bar, create one
                menubar = CreateMenu();
                use_menu_command(menubar);
                SetMenu(parent_h, menubar);
            }

            let h = CreateMenu();
            use_menu_command(h);

            let text = to_utf16(t.text.clone().into().as_ref());
            AppendMenuW(menubar, MF_STRING|MF_POPUP, mem::transmute(h), text.as_ptr());

            DrawMenuBar(parent_h); // Draw the menu bar to make sure the changes are visible
            Ok( ( h, AnyHandle::HWND(parent_h)) )
        },
        AnyHandle::HMENU(parent_h) => {
            let h = CreateMenu();
            use_menu_command(h);

            let text = to_utf16(t.text.clone().into().as_ref());
            AppendMenuW(parent_h, MF_STRING|MF_POPUP, mem::transmute(h), text.as_ptr());
            Ok( ( h, AnyHandle::HMENU(parent_h) ) )
        },
        AnyHandle::HMENU_ITEM(_, _) => Err(Error::BadParent("Window or menu parent required, got MenuItem".to_string())),
        AnyHandle::HFONT(_) =>  Err(Error::BadParent("Window or menu parent required, got Font".to_string())),
        AnyHandle::None | AnyHandle::Custom(_) =>  Err(Error::BadParent("Window or menu parent required, got control with no defined handle".to_string())),
   }
}

#[inline(always)]
unsafe fn build_menu_item<S: Clone+Into<String>, ID: Clone+Hash>(ui: &Ui<ID>, t: &MenuItemT<S, ID>) -> Result<(HMENU, UINT), Error> {
    use user32::{AppendMenuW, CreateMenu, GetMenu, SetMenu, DrawMenuBar};
    use winapi::{MF_STRING, UINT_PTR};
    use low::other_helper::to_utf16;

    let ph_result = ui.handle_of(&t.parent);
    if ph_result.is_err() { return Err(ph_result.err().unwrap()); }

    menu_items_id += 1;

    match ph_result.unwrap() {
        AnyHandle::HWND(parent_h) => {
            let mut menubar = GetMenu(parent_h);
            if menubar.is_null() {
                // If the window do not have a menu bar, create one
                menubar = CreateMenu();
                SetMenu(parent_h, menubar);
            }

            let text = to_utf16(t.text.clone().into().as_ref());
            let ensure_id_stays_the_same = menu_items_id;

            AppendMenuW(menubar, MF_STRING, ensure_id_stays_the_same as UINT_PTR, text.as_ptr());
            DrawMenuBar(parent_h); // Draw the menu bar to make sure the changes are visible

            // WATCH OUT HERE!!! Calling `DrawMenuBar` (or maybe AppendMenuW) made the menu_items_id value corrupted (which in turn f* the whole menuitem system)
            // Saving the id in its own little variable saved the day

            Ok( (menubar, ensure_id_stays_the_same) )
        },
        AnyHandle::HMENU(parent_h) => {
            let text = to_utf16(t.text.clone().into().as_ref());
            menu_items_id += 1;
            AppendMenuW(parent_h, MF_STRING, menu_items_id as UINT_PTR, text.as_ptr());
            Ok( (parent_h, menu_items_id) )
        },
        AnyHandle::HMENU_ITEM(_, _) => Err(Error::BadParent("Window or menu parent required, got MenuItem".to_string())),
        AnyHandle::HFONT(_) =>  Err(Error::BadParent("Window or menu parent required, got Font".to_string())),
        AnyHandle::None | AnyHandle::Custom(_) =>  Err(Error::BadParent("Window or menu parent required, got control with no defined handle".to_string())),
    }
}<|endoftext|>


<commit_before>//!Router asigns handlers to paths and resolves them per request

#[cfg(test)]
use http::method;
use http::method::{ Method, Get, Post, Put, Delete };
use http::server::request::{AbsolutePath};
use regex::Regex;
use std::collections::hashmap::HashMap;
use request::Request;
use response::Response;
use middleware::{ Middleware, Action, Halt, Continue };
use nickel_error::NickelError;

/// A Route is the basic data structure that stores both the path
/// and the handler that gets executed for the route.
/// The path can contain variable pattern such as `user/:userid/invoices`
pub struct Route {
    pub path: String,
    pub method: Method,
    pub handler: fn(request: &Request, response: &mut Response),
    pub variables: HashMap<String, uint>,
    matcher: Regex
}

impl Clone for Route {
    fn clone(&self) -> Route {
        Route {
            path: self.path.clone(),
            method: self.method.clone(),
            handler: self.handler,
            matcher: self.matcher.clone(),
            variables: self.variables.clone()
        }
    }
}

/// A RouteResult is what the router returns when `match_route` is called.
/// It contains the matched `route` and also a `params` property holding
/// a HashMap with the keys being the variable names and the value being the
/// evaluated string
pub struct RouteResult<'a> {
    pub route: &'a Route,
    params: Vec<String>
}

impl<'a> RouteResult<'a> {
    pub fn param(&self, key: &str) -> &str {
        let idx = self.route.variables.find_equiv(&key).unwrap();
        self.params[*idx].as_slice()
    }
}

/// The path_utils collects some small helper methods that operate on the path
mod path_utils {
    use regex::Regex;
    use std::collections::hashmap::HashMap;

    // matches named variables (e.g. :userid)
    static REGEX_VAR_SEQ: Regex                 = regex!(r":([,a-zA-Z0-9_-]*)");
    static VAR_SEQ:&'static str                 = "[,a-zA-Z0-9_-]*";
    static VAR_SEQ_WITH_SLASH:&'static str      = "[,/a-zA-Z0-9_-]*";
    static VAR_SEQ_WITH_CAPTURE:&'static str    = "([,a-zA-Z0-9%_-]*)";
    // matches request params (e.g. ?foo=true&bar=false)
    static REGEX_PARAM_SEQ:&'static str         = "(\\?[a-zA-Z0-9%_=&-]*)?";
    static REGEX_START:&'static str             = "^";
    static REGEX_END:&'static str               = "$";
    pub fn create_regex (route_path: &str) -> Regex {

        let updated_path = route_path.to_string()
                                     // first mark all double wildcards for replacement. We can't directly replace them
                                     // since the replacement does contain the * symbol as well, which would get overwritten
                                     // with the next replace call
                                     .replace("**", "___DOUBLE_WILDCARD___")
                                     // then replace the regular wildcard symbols (*) with the appropriate regex
                                     .replace("*", VAR_SEQ)
                                     // now replace the previously marked double wild cards (**)
                                     .replace("___DOUBLE_WILDCARD___", VAR_SEQ_WITH_SLASH);

        // then replace the variable symbols (:variable) with the appropriate regex
        let result = [REGEX_START,
                      REGEX_VAR_SEQ.replace_all(updated_path.as_slice(),
                                                VAR_SEQ_WITH_CAPTURE)
                                   .as_slice(),
                      REGEX_PARAM_SEQ,
                      REGEX_END].concat();

        Regex::new(result.as_slice()).ok().unwrap()
    }

    pub fn get_variable_info (route_path: &str) -> HashMap<String, uint> {
        REGEX_VAR_SEQ.captures_iter(route_path)
             .enumerate()
             .map(|(i, matched)| (matched.at(1).to_string(), i))
             .collect()
    }
}

/// The Router's job is it to hold routes and to resolve them later against
/// concrete URLs. The router is also a regular middleware and needs to be
/// added to the middleware stack with `server.utilize(router)`.

#[deriving(Clone)]
pub struct Router{
    routes: Vec<Route>,
}

impl<'a> Router {
    pub fn new () -> Router {
        Router {
            routes: Vec::new()
        }
    }

    /// Registers a handler to be used for a specific GET request.
    /// Handlers are assigned to paths and paths are allowed to contain
    /// variables and wildcards.
    ///
    /// # Example without variables and wildcards
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user");
    /// };
    /// router.get("/user", handler);
    /// ```
    /// # Example with variables
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     let text = format!("This is user: {}", request.params.get(&"userid".to_string()));
    ///     response.send(text.as_slice());
    /// };
    /// router.get("/user/:userid", handler);
    /// ```
    /// # Example with simple wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 but not /user/extended/list/4711");
    /// };
    /// router.get("/user/*/:userid", handler);
    /// ```
    /// # Example with double wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 and also /user/extended/list/4711");
    /// };
    /// router.get("/user/**/:userid", handler);
    /// ```
    pub fn get(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Get, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific POST request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/post/request");
    /// };
    /// router.post("/a/post/request", handler);
    /// ```
    /// Take a look at `get()` for a more detailed description.
    pub fn post(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Post, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific PUT request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/put/request");
    /// };
    /// router.put("/a/put/request", handler);
    /// ```
    /// Take a look at `get(..)` for a more detailed description.
    pub fn put(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Put, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific DELETE request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a DELETE request to /a/delete/request");
    /// };
    /// router.delete("/a/delete/request", handler);
    /// ```
    /// Take a look at `get(...)` for a more detailed description.
    pub fn delete(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Delete, String::from_str(uri), handler);
    }

    pub fn add_route(&mut self, method: Method, path: String, handler: fn(request: &Request, response: &mut Response)) -> () {
        let matcher = path_utils::create_regex(path.as_slice());
        let variable_infos = path_utils::get_variable_info(path.as_slice());
        let route = Route {
            path: path,
            method: method,
            matcher: matcher,
            handler: handler,
            variables: variable_infos
        };
        self.routes.push(route);
    }

    pub fn match_route(&'a self, method: &Method, path: &str) -> Option<RouteResult<'a>> {
        self.routes.iter().find(|item| item.method == *method && item.matcher.is_match(path))
            .map(|route| {
                let vec = match route.matcher.captures(path) {
                    Some(captures) => {
                        range(0, route.variables.len()).map(|pos|
                            captures.at(pos + 1).to_string()
                        ).collect()
                    },
                    None => vec![],
                };
                RouteResult {
                    route: route,
                    params: vec
                }
            })
    }
}

impl Middleware for Router {
    fn invoke<'a, 'b>(&'a self, req: &mut Request<'b, 'a>, res: &mut Response) -> Result<Action, NickelError> {
        match req.origin.request_uri {
            AbsolutePath(ref url) => {
                match self.match_route(&req.origin.method, url.as_slice()) {
                    Some(route_result) => {
                        res.origin.status = ::http::status::Ok;
                        let handler = &route_result.route.handler;
                        req.route_result = Some(route_result);
                        (*handler)(req, res);
                        Ok(Halt)
                    },
                    None => Ok(Continue)
                }
            },
            _ => Ok(Continue)
        }
    }
}

#[test]
fn creates_map_with_var_variable_infos () {
    let map = path_utils::get_variable_info("foo/:uid/bar/:groupid");

    assert_eq!(map.len(), 2);
    assert_eq!(map.get(&"uid".to_string()), &0);
    assert_eq!(map.get(&"groupid".to_string()), &1);
}

#[test]
fn creates_regex_with_captures () {
    let regex = path_utils::create_regex("foo/:uid/bar/:groupid");
    let caps = regex.captures("foo/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = path_utils::create_regex("foo/*/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = path_utils::create_regex("foo/**/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/another/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");
}

#[test]
fn creates_valid_regex_for_routes () {
    let regex1 = path_utils::create_regex("foo/:uid/bar/:groupid");
    let regex2 = path_utils::create_regex("foo/*/bar");
    let regex3 = path_utils::create_regex("foo/**/bar");

    assert_eq!(regex1.is_match("foo/4711/bar/5490"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=true&bar=false"), false);
    assert_eq!(regex1.is_match("foo/4711/bar/test%20spacing"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5281?foo=test%20spacing&bar=false"), true);

    assert_eq!(regex2.is_match("foo/4711/bar"), true);
    assert_eq!(regex2.is_match("foo/4711/barr"), false);
    assert_eq!(regex2.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex2.is_match("foo/4711/4712/bar"), false);
    assert_eq!(regex2.is_match("foo/4711/4712/bar?foo=true&bar=false"), false);

    assert_eq!(regex3.is_match("foo/4711/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar?foo=true&bar=false"), true);

    //ensure that this works with commas too
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=1,2,3&bar=false"), false);
}

#[test]
fn can_match_var_routes () {
    let route_store = &mut Router::new();

    fn handler (_request: &Request, response: &mut Response) -> () {
        let _ = response.origin.write("hello from foo".as_bytes());
    };

    route_store.add_route(method::Get, "/foo/:userid".to_string(), handler);
    route_store.add_route(method::Get, "/bar".to_string(), handler);

    let route_result = route_store.match_route(&method::Get, "/foo/4711").unwrap();
    let route = route_result.route;

    assert_eq!(route_result.param("userid"), "4711");

    //assert the route has identified the variable
    assert_eq!(route.variables.len(), 1);
    assert_eq!(route.variables.get(&"userid".to_string()), &0);

    let route_result = route_store.match_route(&method::Get, "/bar/4711");
    assert!(route_result.is_none());

    let route_result = route_store.match_route(&method::Get, "/foo");
    assert!(route_result.is_none());

    //ensure that this will work with commas too
    let route_result = route_store.match_route(&method::Get, "/foo/123,456");
    assert!(route_result.is_some());

    let route_result = route_result.unwrap();
    assert_eq!(route_result.param("userid"), "123,456");

    //ensure that this will work with spacing too
    let route_result = route_store.match_route(&method::Get, "/foo/John%20Doe");
    assert!(route_result.is_some());

    let route_result = route_result.unwrap();
    assert_eq!(route_result.param("userid"), "John%20Doe");
}
<commit_msg>router: Fix warnings.<commit_after>//!Router asigns handlers to paths and resolves them per request

#[cfg(test)]
use http::method;
use http::method::{ Method, Get, Post, Put, Delete };
use http::server::request::{AbsolutePath};
use regex::Regex;
use std::collections::hashmap::HashMap;
use request::Request;
use response::Response;
use middleware::{ Middleware, Action, Halt, Continue };
use nickel_error::NickelError;

/// A Route is the basic data structure that stores both the path
/// and the handler that gets executed for the route.
/// The path can contain variable pattern such as `user/:userid/invoices`
pub struct Route {
    pub path: String,
    pub method: Method,
    pub handler: fn(request: &Request, response: &mut Response),
    pub variables: HashMap<String, uint>,
    matcher: Regex
}

impl Clone for Route {
    fn clone(&self) -> Route {
        Route {
            path: self.path.clone(),
            method: self.method.clone(),
            handler: self.handler,
            matcher: self.matcher.clone(),
            variables: self.variables.clone()
        }
    }
}

/// A RouteResult is what the router returns when `match_route` is called.
/// It contains the matched `route` and also a `params` property holding
/// a HashMap with the keys being the variable names and the value being the
/// evaluated string
pub struct RouteResult<'a> {
    pub route: &'a Route,
    params: Vec<String>
}

impl<'a> RouteResult<'a> {
    pub fn param(&self, key: &str) -> &str {
        let idx = self.route.variables.find_equiv(&key).unwrap();
        self.params[*idx].as_slice()
    }
}

/// The path_utils collects some small helper methods that operate on the path
mod path_utils {
    use regex::Regex;
    use std::collections::hashmap::HashMap;

    // matches named variables (e.g. :userid)
    static REGEX_VAR_SEQ: Regex                 = regex!(r":([,a-zA-Z0-9_-]*)");
    static VAR_SEQ:&'static str                 = "[,a-zA-Z0-9_-]*";
    static VAR_SEQ_WITH_SLASH:&'static str      = "[,/a-zA-Z0-9_-]*";
    static VAR_SEQ_WITH_CAPTURE:&'static str    = "([,a-zA-Z0-9%_-]*)";
    // matches request params (e.g. ?foo=true&bar=false)
    static REGEX_PARAM_SEQ:&'static str         = "(\\?[a-zA-Z0-9%_=&-]*)?";
    static REGEX_START:&'static str             = "^";
    static REGEX_END:&'static str               = "$";
    pub fn create_regex (route_path: &str) -> Regex {

        let updated_path = route_path.to_string()
                                     // first mark all double wildcards for replacement. We can't directly replace them
                                     // since the replacement does contain the * symbol as well, which would get overwritten
                                     // with the next replace call
                                     .replace("**", "___DOUBLE_WILDCARD___")
                                     // then replace the regular wildcard symbols (*) with the appropriate regex
                                     .replace("*", VAR_SEQ)
                                     // now replace the previously marked double wild cards (**)
                                     .replace("___DOUBLE_WILDCARD___", VAR_SEQ_WITH_SLASH);

        // then replace the variable symbols (:variable) with the appropriate regex
        let result = [REGEX_START,
                      REGEX_VAR_SEQ.replace_all(updated_path.as_slice(),
                                                VAR_SEQ_WITH_CAPTURE)
                                   .as_slice(),
                      REGEX_PARAM_SEQ,
                      REGEX_END].concat();

        Regex::new(result.as_slice()).ok().unwrap()
    }

    pub fn get_variable_info (route_path: &str) -> HashMap<String, uint> {
        REGEX_VAR_SEQ.captures_iter(route_path)
             .enumerate()
             .map(|(i, matched)| (matched.at(1).to_string(), i))
             .collect()
    }
}

/// The Router's job is it to hold routes and to resolve them later against
/// concrete URLs. The router is also a regular middleware and needs to be
/// added to the middleware stack with `server.utilize(router)`.

#[deriving(Clone)]
pub struct Router{
    routes: Vec<Route>,
}

impl<'a> Router {
    pub fn new () -> Router {
        Router {
            routes: Vec::new()
        }
    }

    /// Registers a handler to be used for a specific GET request.
    /// Handlers are assigned to paths and paths are allowed to contain
    /// variables and wildcards.
    ///
    /// # Example without variables and wildcards
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user");
    /// };
    /// router.get("/user", handler);
    /// ```
    /// # Example with variables
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     let text = format!("This is user: {}", request.params.get(&"userid".to_string()));
    ///     response.send(text.as_slice());
    /// };
    /// router.get("/user/:userid", handler);
    /// ```
    /// # Example with simple wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 but not /user/extended/list/4711");
    /// };
    /// router.get("/user/*/:userid", handler);
    /// ```
    /// # Example with double wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 and also /user/extended/list/4711");
    /// };
    /// router.get("/user/**/:userid", handler);
    /// ```
    pub fn get(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Get, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific POST request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/post/request");
    /// };
    /// router.post("/a/post/request", handler);
    /// ```
    /// Take a look at `get()` for a more detailed description.
    pub fn post(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Post, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific PUT request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/put/request");
    /// };
    /// router.put("/a/put/request", handler);
    /// ```
    /// Take a look at `get(..)` for a more detailed description.
    pub fn put(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Put, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific DELETE request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a DELETE request to /a/delete/request");
    /// };
    /// router.delete("/a/delete/request", handler);
    /// ```
    /// Take a look at `get(...)` for a more detailed description.
    pub fn delete(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Delete, String::from_str(uri), handler);
    }

    pub fn add_route(&mut self, method: Method, path: String, handler: fn(request: &Request, response: &mut Response)) -> () {
        let matcher = path_utils::create_regex(path.as_slice());
        let variable_infos = path_utils::get_variable_info(path.as_slice());
        let route = Route {
            path: path,
            method: method,
            matcher: matcher,
            handler: handler,
            variables: variable_infos
        };
        self.routes.push(route);
    }

    pub fn match_route(&'a self, method: &Method, path: &str) -> Option<RouteResult<'a>> {
        self.routes.iter().find(|item| item.method == *method && item.matcher.is_match(path))
            .map(|route| {
                let vec = match route.matcher.captures(path) {
                    Some(captures) => {
                        range(0, route.variables.len()).map(|pos|
                            captures.at(pos + 1).to_string()
                        ).collect()
                    },
                    None => vec![],
                };
                RouteResult {
                    route: route,
                    params: vec
                }
            })
    }
}

impl Middleware for Router {
    fn invoke<'a, 'b>(&'a self, req: &mut Request<'b, 'a>, res: &mut Response) -> Result<Action, NickelError> {
        match req.origin.request_uri {
            AbsolutePath(ref url) => {
                match self.match_route(&req.origin.method, url.as_slice()) {
                    Some(route_result) => {
                        res.origin.status = ::http::status::Ok;
                        let handler = &route_result.route.handler;
                        req.route_result = Some(route_result);
                        (*handler)(req, res);
                        Ok(Halt)
                    },
                    None => Ok(Continue)
                }
            },
            _ => Ok(Continue)
        }
    }
}

#[test]
fn creates_map_with_var_variable_infos () {
    let map = path_utils::get_variable_info("foo/:uid/bar/:groupid");

    assert_eq!(map.len(), 2);
    assert_eq!(map["uid".to_string()], 0);
    assert_eq!(map["groupid".to_string()], 1);
}

#[test]
fn creates_regex_with_captures () {
    let regex = path_utils::create_regex("foo/:uid/bar/:groupid");
    let caps = regex.captures("foo/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = path_utils::create_regex("foo/*/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = path_utils::create_regex("foo/**/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/another/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");
}

#[test]
fn creates_valid_regex_for_routes () {
    let regex1 = path_utils::create_regex("foo/:uid/bar/:groupid");
    let regex2 = path_utils::create_regex("foo/*/bar");
    let regex3 = path_utils::create_regex("foo/**/bar");

    assert_eq!(regex1.is_match("foo/4711/bar/5490"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=true&bar=false"), false);
    assert_eq!(regex1.is_match("foo/4711/bar/test%20spacing"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5281?foo=test%20spacing&bar=false"), true);

    assert_eq!(regex2.is_match("foo/4711/bar"), true);
    assert_eq!(regex2.is_match("foo/4711/barr"), false);
    assert_eq!(regex2.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex2.is_match("foo/4711/4712/bar"), false);
    assert_eq!(regex2.is_match("foo/4711/4712/bar?foo=true&bar=false"), false);

    assert_eq!(regex3.is_match("foo/4711/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar?foo=true&bar=false"), true);

    //ensure that this works with commas too
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=1,2,3&bar=false"), false);
}

#[test]
fn can_match_var_routes () {
    let route_store = &mut Router::new();

    fn handler (_request: &Request, response: &mut Response) -> () {
        let _ = response.origin.write("hello from foo".as_bytes());
    };

    route_store.add_route(method::Get, "/foo/:userid".to_string(), handler);
    route_store.add_route(method::Get, "/bar".to_string(), handler);

    let route_result = route_store.match_route(&method::Get, "/foo/4711").unwrap();
    let route = route_result.route;

    assert_eq!(route_result.param("userid"), "4711");

    //assert the route has identified the variable
    assert_eq!(route.variables.len(), 1);
    assert_eq!(route.variables["userid".to_string()], 0);

    let route_result = route_store.match_route(&method::Get, "/bar/4711");
    assert!(route_result.is_none());

    let route_result = route_store.match_route(&method::Get, "/foo");
    assert!(route_result.is_none());

    //ensure that this will work with commas too
    let route_result = route_store.match_route(&method::Get, "/foo/123,456");
    assert!(route_result.is_some());

    let route_result = route_result.unwrap();
    assert_eq!(route_result.param("userid"), "123,456");

    //ensure that this will work with spacing too
    let route_result = route_store.match_route(&method::Get, "/foo/John%20Doe");
    assert!(route_result.is_some());

    let route_result = route_result.unwrap();
    assert_eq!(route_result.param("userid"), "John%20Doe");
}
<|endoftext|>