<commit_before>use address::{Address, Addressable};
use messages;
use std::fmt::Debug;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket, IpAddr, Ipv4Addr};
use std::collections::HashMap;
use time;
use transaction::TransactionId;

pub fn deserialize(message: &messages::protobufs::Node) -> Box<Node> {
    let ip = message.get_ip_address();
    let ip = Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]);
    let port = message.get_port() as u16;
    let address = Address::from_str(message.get_id());
    Box::new(UdpNode::new(address, (ip, port)))
}

pub trait Serialize {
    fn serialize(&self) -> messages::protobufs::Node;
}

#[derive(Debug, PartialEq)]
pub enum Status {
    Good,
    Questionable
}

pub trait Node : Addressable + Debug + Serialize + Send + Sync {
    fn is_questionable(&self) -> bool;
    fn received_query(&mut self, transaction_id: TransactionId);
    fn received_response(&mut self, transaction_id: TransactionId);
    fn send(&self, message: Vec<u8>);
    fn sent_query(&mut self, transaction_id: TransactionId);
}

impl PartialEq for Node {
    fn eq(&self, other: &Self) -> bool {
        self.get_address() == other.get_address()
    }
}

#[derive(Debug)]
pub struct UdpNode {
    address: Address,
    socket_address: SocketAddr,
    pending_queries: HashMap<TransactionId, time::Tm>,
    has_ever_responded: bool,
    last_seen: time::Tm,
}

impl UdpNode {
    pub fn new<S: ToSocketAddrs>(address: Address, socket_address: S) -> UdpNode {
        let socket_address = socket_address
            .to_socket_addrs()
            .unwrap()
            .next()
            .unwrap();
        UdpNode {
            address: address,
            socket_address: socket_address,
            pending_queries: HashMap::new(),
            has_ever_responded: false,
            last_seen: time::empty_tm(),
        }
    }

    fn status(&self) -> Status {
        let time_since_last_seen = time::now_utc() - self.last_seen;

        if self.has_ever_responded && time_since_last_seen < time::Duration::minutes(1) {
            Status::Good
        } else {
            Status::Questionable
        }
    }
}

impl Node for UdpNode {
    fn is_questionable(&self) -> bool {
        self.status() == Status::Questionable
    }

    fn received_query(&mut self, _: TransactionId) {
        self.last_seen = time::now_utc();
    }

    fn received_response(&mut self, transaction_id: TransactionId) {
        self.last_seen = time::now_utc();
        if let Some(queried_at) = self.pending_queries.remove(&transaction_id) {
            self.has_ever_responded = true;
            let time_to_respond = time::now_utc() - queried_at;
            println!("{} took {:?} to respond", transaction_id, time_to_respond);
        } else {
            println!("{:?} was not expecting response to {}", self.address, transaction_id);
            println!("pending queries: {:?}", self.pending_queries.len());
        }
    }

    fn send(&self, message: Vec<u8>) {
        let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
        socket.send_to(&message[..], self.socket_address).unwrap();
    }

    fn sent_query(&mut self, transaction_id: TransactionId) {
        self.pending_queries.insert(transaction_id, time::now_utc());
    }
}

impl Serialize for UdpNode {
    fn serialize(&self) -> messages::protobufs::Node {
        let mut message = messages::protobufs::Node::new();
        message.set_id(self.address.to_str());
        match self.socket_address.ip() {
            IpAddr::V4(ipv4_addr) => {
                message.set_ip_address(ipv4_addr.octets().iter().cloned().collect());
            }
            IpAddr::V6(ipv6_addr) => {
                // TODO ipv6 node support
            }
        }
        message.set_port(self.socket_address.port() as u32);
        message
    }
}

impl Addressable for UdpNode {
    fn get_address(&self) -> Address {
        self.address
    }
}

#[cfg(test)]
mod tests {
    use address::Address;
    use super::{Node, UdpNode, Serialize};

    #[test]
    fn test_received_response() {
        let address = Address::for_content("some string");
        let mut node = UdpNode::new(address, ("0.0.0.0", 9000));

        // When it's not expecting the response
        node.received_response(1);
        assert!(!node.has_ever_responded);

        // When it's expecting the response
        let last_seen_before = node.last_seen;
        let transaction_id = 2;
        node.sent_query(transaction_id);
        node.received_response(transaction_id);
        let last_seen_after = node.last_seen;
        assert!(last_seen_before < last_seen_after);
        assert!(node.has_ever_responded);
    }

    #[test]
    fn test_serialize() {
        use messages;
        use protobuf::Message;
        let mut protobuf = messages::protobufs::Node::new();
        protobuf.set_id("8b45e4bd1c6acb88bebf6407d16205f567e62a3e".to_string());
        protobuf.set_ip_address(vec![192, 168, 1, 2]);
        protobuf.set_port(9000);
        let address = Address::for_content("some string");
        let node = UdpNode::new(address, ("192.168.1.2", 9000));
        assert_eq!(node.serialize(), protobuf);
    }
}
<commit_msg>Extract constant for minutes until questionable<commit_after>use address::{Address, Addressable};
use messages;
use std::fmt::Debug;
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket, IpAddr, Ipv4Addr};
use std::collections::HashMap;
use time;
use transaction::TransactionId;

const MINUTES_UNTIL_QUESTIONABLE: i64 = 1;

pub fn deserialize(message: &messages::protobufs::Node) -> Box<Node> {
    let ip = message.get_ip_address();
    let ip = Ipv4Addr::new(ip[0], ip[1], ip[2], ip[3]);
    let port = message.get_port() as u16;
    let address = Address::from_str(message.get_id());
    Box::new(UdpNode::new(address, (ip, port)))
}

pub trait Serialize {
    fn serialize(&self) -> messages::protobufs::Node;
}

#[derive(Debug, PartialEq)]
pub enum Status {
    Good,
    Questionable
}

pub trait Node : Addressable + Debug + Serialize + Send + Sync {
    fn is_questionable(&self) -> bool;
    fn received_query(&mut self, transaction_id: TransactionId);
    fn received_response(&mut self, transaction_id: TransactionId);
    fn send(&self, message: Vec<u8>);
    fn sent_query(&mut self, transaction_id: TransactionId);
}

impl PartialEq for Node {
    fn eq(&self, other: &Self) -> bool {
        self.get_address() == other.get_address()
    }
}

#[derive(Debug)]
pub struct UdpNode {
    address: Address,
    socket_address: SocketAddr,
    pending_queries: HashMap<TransactionId, time::Tm>,
    has_ever_responded: bool,
    last_seen: time::Tm,
}

impl UdpNode {
    pub fn new<S: ToSocketAddrs>(address: Address, socket_address: S) -> UdpNode {
        let socket_address = socket_address
            .to_socket_addrs()
            .unwrap()
            .next()
            .unwrap();
        UdpNode {
            address: address,
            socket_address: socket_address,
            pending_queries: HashMap::new(),
            has_ever_responded: false,
            last_seen: time::empty_tm(),
        }
    }

    fn status(&self) -> Status {
        let time_since_last_seen = time::now_utc() - self.last_seen;

        if self.has_ever_responded &&
            time_since_last_seen < time::Duration::minutes(MINUTES_UNTIL_QUESTIONABLE) {
            Status::Good
        } else {
            Status::Questionable
        }
    }
}

impl Node for UdpNode {
    fn is_questionable(&self) -> bool {
        self.status() == Status::Questionable
    }

    fn received_query(&mut self, _: TransactionId) {
        self.last_seen = time::now_utc();
    }

    fn received_response(&mut self, transaction_id: TransactionId) {
        self.last_seen = time::now_utc();
        if let Some(queried_at) = self.pending_queries.remove(&transaction_id) {
            self.has_ever_responded = true;
            let time_to_respond = time::now_utc() - queried_at;
            println!("{} took {:?} to respond", transaction_id, time_to_respond);
        } else {
            println!("{:?} was not expecting response to {}", self.address, transaction_id);
            println!("pending queries: {:?}", self.pending_queries.len());
        }
    }

    fn send(&self, message: Vec<u8>) {
        let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
        socket.send_to(&message[..], self.socket_address).unwrap();
    }

    fn sent_query(&mut self, transaction_id: TransactionId) {
        self.pending_queries.insert(transaction_id, time::now_utc());
    }
}

impl Serialize for UdpNode {
    fn serialize(&self) -> messages::protobufs::Node {
        let mut message = messages::protobufs::Node::new();
        message.set_id(self.address.to_str());
        match self.socket_address.ip() {
            IpAddr::V4(ipv4_addr) => {
                message.set_ip_address(ipv4_addr.octets().iter().cloned().collect());
            }
            IpAddr::V6(ipv6_addr) => {
                // TODO ipv6 node support
            }
        }
        message.set_port(self.socket_address.port() as u32);
        message
    }
}

impl Addressable for UdpNode {
    fn get_address(&self) -> Address {
        self.address
    }
}

#[cfg(test)]
mod tests {
    use address::Address;
    use super::{Node, UdpNode, Serialize};

    #[test]
    fn test_received_response() {
        let address = Address::for_content("some string");
        let mut node = UdpNode::new(address, ("0.0.0.0", 9000));

        // When it's not expecting the response
        node.received_response(1);
        assert!(!node.has_ever_responded);

        // When it's expecting the response
        let last_seen_before = node.last_seen;
        let transaction_id = 2;
        node.sent_query(transaction_id);
        node.received_response(transaction_id);
        let last_seen_after = node.last_seen;
        assert!(last_seen_before < last_seen_after);
        assert!(node.has_ever_responded);
    }

    #[test]
    fn test_serialize() {
        use messages;
        use protobuf::Message;
        let mut protobuf = messages::protobufs::Node::new();
        protobuf.set_id("8b45e4bd1c6acb88bebf6407d16205f567e62a3e".to_string());
        protobuf.set_ip_address(vec![192, 168, 1, 2]);
        protobuf.set_port(9000);
        let address = Address::for_content("some string");
        let node = UdpNode::new(address, ("192.168.1.2", 9000));
        assert_eq!(node.serialize(), protobuf);
    }
}
<|endoftext|>


<commit_before>use crate::{
    command_line::{CommandLine, Entry},
    model::{Model, Msg},
    schema::{default_renderer, Field, Kind, Schema, ValidatorContext, SCHEMA},
    types::{Hash, Node, Selector},
};
use std::{collections::BTreeMap, rc::Rc};
use web_sys::HtmlInputElement;
use yew::prelude::*;

pub struct NodeComponent {
    input_node_ref: NodeRef,
    old_props: Option<NodeProperties>,
    // Memoize callbacks?
    ondelete: Callback<()>,
}

#[derive(Properties, PartialEq, Clone)]
pub struct NodeProperties {
    pub path: Vec<Selector>,
    pub model: Rc<Model>,
    #[prop_or_default]
    pub hash: Option<Hash>,
    #[prop_or_default]
    pub placeholder: String,
    // When a new value is typed in the text box.
    #[prop_or_default]
    pub oninput: Callback<String>,
    // When the node is selected (e.g. clicked).
    #[prop_or_default]
    pub onselect: Callback<Vec<Selector>>,
    #[prop_or_default]
    pub updatemodel: Callback<Msg>,
    #[prop_or_default]
    pub allowed_kinds: &'static [&'static str],
}

pub enum NodeMsg {
    Click,
}

pub const FIELD_CLASSES: &[&str] = &[
    "bg-blue-300",
    "inline-block",
    "p-0.5",
    "px-1",
    "border-blue-800",
    "border",
    "rounded",
];
pub const KIND_CLASSES: &[&str] = &[
    "kind",
    "bg-yellow-400",
    "border-yellow-800",
    "border-4",
    "inline-block",
    "p-0.5",
    "px-1",
];

impl Component for NodeComponent {
    type Message = NodeMsg;
    type Properties = NodeProperties;

    fn create(ctx: &Context<Self>) -> Self {
        log::debug!("creating node");
        let updatemodel = ctx.props().updatemodel.clone();
        Self {
            input_node_ref: NodeRef::default(),
            old_props: None,
            ondelete: Callback::from(move |()| updatemodel.emit(Msg::Parent)),
        }
    }

    fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
        let props = ctx.props();
        let path = props.path.clone();
        let cursor = props.model.cursor.clone();
        let selected = path == cursor;
        if selected {
            self.focus_input();
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let default_node = Node::default();
        let props = ctx.props();
        let hash = props.hash.clone().unwrap_or_default();
        let node = props.model.file.lookup_hash(&hash).unwrap_or(&default_node);
        if props.hash.is_none() {}
        let path = props.path.clone();
        let cursor = props.model.cursor.clone();
        let _oninput = props.oninput.clone();
        let selected = path == cursor;
        let kind = SCHEMA.get_kind(&node.kind);
        let inner = if props.hash.is_none() || node.kind.is_empty() {
            let onupdatemodel = ctx.props().updatemodel.clone();
            let path = path.clone();
            let entries: Vec<Entry> = props
                .allowed_kinds
                .iter()
                .map(|kind_id| Entry {
                    label: SCHEMA
                        .get_kind(kind_id)
                        .map(|k| k.name.clone())
                        .unwrap_or("INVALID")
                        .to_string(),
                    description: "".to_string(),
                    action: Msg::ReplaceNode(
                        path.clone(),
                        Node {
                            kind: kind_id.to_string(),
                            value: "".to_string(),
                            links: BTreeMap::new(),
                        },
                        false,
                    ),
                    valid_classes: KIND_CLASSES.iter().map(|v| v.to_string()).collect(),
                })
                .collect();
            let onenter = {
                let _path = path.clone();
                let onupdatemodel = onupdatemodel.clone();
                Callback::from(move |()| {
                    onupdatemodel.emit(Msg::Parent);
                })
            };
            let onupdatemodel0 = onupdatemodel.clone();
            let placeholder = if props.hash.is_none() {
                "***".to_string()
            } else {
                "".to_string()
            };
            html! {
              <CommandLine
                input_node_ref={ self.input_node_ref.clone() }
                entries={ entries }
                value={ node.value.clone() }
                placeholder={ placeholder }
                oninput={ Callback::from(move |v| {
                    onupdatemodel.emit(Msg::SetNodeValue(path.clone(), v));
                 }) }
                onselect={ ctx.props().updatemodel.clone() }
                ondelete={ Callback::from(move |()| {
                    onupdatemodel0.emit(Msg::DeleteItem);
                 }) }
                onenter={ onenter }
                enabled={ selected }
              />
            }
        } else {
            let renderer = if props.model.rich_render {
                kind.and_then(|k| k.renderer).unwrap_or(default_renderer)
            } else {
                default_renderer
            };
            let validator_context = ValidatorContext {
                model: props.model.clone(),
                path: path.clone(),
                node: node.clone(),
                onselect: props.onselect.clone(),
                updatemodel: props.updatemodel.clone(),
            };
            let content = renderer(&validator_context);
            let footer = if selected {
                let entries: Vec<Entry> = kind
                    .map(|k| {
                        let mut all_entries = vec![];
                        let mut field_entries = k
                            .get_fields()
                            .iter()
                            .map(|(field_id, field)| Entry {
                                label: field.name.to_string(),
                                description: "".to_string(),
                                action: Msg::AddField(path.to_vec(), **field_id),
                                valid_classes: FIELD_CLASSES
                                    .iter()
                                    .map(|v| v.to_string())
                                    .collect(),
                            })
                            .collect();
                        // TODO: macros
                        let mut macro_entries = vec![];
                        all_entries.append(&mut field_entries);
                        all_entries.append(&mut macro_entries);
                        all_entries
                    })
                    .unwrap_or_default();
                // Make it look like an actual field.
                let _onupdatemodel0 = ctx.props().updatemodel.clone();
                html! {

                        <CommandLine
                            input_node_ref={ self.input_node_ref.clone() }
                            entries={ entries }
                            value={ node.value.clone() }
                            onselect={ ctx.props().updatemodel.clone() }
                            ondelete={ self.ondelete.clone() }
                            enabled=true
                        />

                }
            } else {
                html! {}
            };
            html! {
              <>
                { content }
                { footer }
              </>
            }
        };
        let onselect = ctx.props().onselect.clone();
        let onclick = {
            let path = path;
            ctx.link().callback(move |e: MouseEvent| {
                e.stop_propagation();
                onselect.emit(path.clone());
                NodeMsg::Click
            })
        };
        let mut classes = vec![
            "node".to_string(),
            "align-top".to_string(),
            "flex-auto".to_string(),
        ];
        if selected {
            classes.push("selected".to_string());
        }
        html! {

              { inner }

        }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            NodeMsg::Click => {
                self.focus_input();
                true
            }
        }
    }

    fn changed(&mut self, ctx: &Context<Self>) -> bool {
        let same = self.old_props == Some(ctx.props().clone());
        log::debug!("Node changed");
        log::debug!("same props: {:?}", same);
        if let Some(old_props) = &self.old_props {
            log::debug!("same path: {:?}", old_props.path == ctx.props().path);
            log::debug!("same model: {:?}", old_props.model == ctx.props().model);
            log::debug!("same hash: {:?}", old_props.hash == ctx.props().hash);
            log::debug!(
                "same updatemodel: {:?}",
                old_props.updatemodel == ctx.props().updatemodel
            );
            return old_props.path != ctx.props().path
                || old_props.model != ctx.props().model
                || old_props.hash != ctx.props().hash;
        }
        self.old_props = Some(ctx.props().clone());
        !same
    }
}

impl NodeComponent {
    fn focus_input(&self) {
        log::info!("focusing input");
        let input_node = self.input_node_ref.clone();
        if let Some(i) = input_node.cast::<HtmlInputElement>() {
            i.focus().unwrap();
        }
    }
}
<commit_msg>Macros<commit_after>use crate::{
    command_line::{CommandLine, Entry},
    model::{Model, Msg},
    schema::{default_renderer, Field, Kind, Schema, ValidatorContext, RUST_FUNCTION_CALL, SCHEMA},
    types::{Hash, Node, Selector},
};
use std::{collections::BTreeMap, rc::Rc};
use web_sys::HtmlInputElement;
use yew::prelude::*;

pub struct NodeComponent {
    input_node_ref: NodeRef,
    old_props: Option<NodeProperties>,
    // Memoize callbacks?
    ondelete: Callback<()>,
}

#[derive(Properties, PartialEq, Clone)]
pub struct NodeProperties {
    pub path: Vec<Selector>,
    pub model: Rc<Model>,
    #[prop_or_default]
    pub hash: Option<Hash>,
    #[prop_or_default]
    pub placeholder: String,
    // When a new value is typed in the text box.
    #[prop_or_default]
    pub oninput: Callback<String>,
    // When the node is selected (e.g. clicked).
    #[prop_or_default]
    pub onselect: Callback<Vec<Selector>>,
    #[prop_or_default]
    pub updatemodel: Callback<Msg>,
    #[prop_or_default]
    pub allowed_kinds: &'static [&'static str],
}

pub enum NodeMsg {
    Click,
}

pub const FIELD_CLASSES: &[&str] = &[
    "bg-blue-300",
    "inline-block",
    "p-0.5",
    "px-1",
    "border-blue-800",
    "border",
    "rounded",
];
pub const KIND_CLASSES: &[&str] = &[
    "kind",
    "bg-yellow-400",
    "border-yellow-800",
    "border-4",
    "inline-block",
    "p-0.5",
    "px-1",
];

impl Component for NodeComponent {
    type Message = NodeMsg;
    type Properties = NodeProperties;

    fn create(ctx: &Context<Self>) -> Self {
        log::debug!("creating node");
        let updatemodel = ctx.props().updatemodel.clone();
        Self {
            input_node_ref: NodeRef::default(),
            old_props: None,
            ondelete: Callback::from(move |()| updatemodel.emit(Msg::Parent)),
        }
    }

    fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
        let props = ctx.props();
        let path = props.path.clone();
        let cursor = props.model.cursor.clone();
        let selected = path == cursor;
        if selected {
            self.focus_input();
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let default_node = Node::default();
        let props = ctx.props();
        let hash = props.hash.clone().unwrap_or_default();
        let node = props.model.file.lookup_hash(&hash).unwrap_or(&default_node);
        if props.hash.is_none() {}
        let path = props.path.clone();
        let cursor = props.model.cursor.clone();
        let _oninput = props.oninput.clone();
        let selected = path == cursor;
        let kind = SCHEMA.get_kind(&node.kind);
        let inner = if props.hash.is_none() || node.kind.is_empty() {
            let onupdatemodel = ctx.props().updatemodel.clone();
            let path = path.clone();
            let entries: Vec<Entry> = props
                .allowed_kinds
                .iter()
                .map(|kind_id| Entry {
                    label: SCHEMA
                        .get_kind(kind_id)
                        .map(|k| k.name.clone())
                        .unwrap_or("INVALID")
                        .to_string(),
                    description: "".to_string(),
                    action: Msg::ReplaceNode(
                        path.clone(),
                        Node {
                            kind: kind_id.to_string(),
                            value: "".to_string(),
                            links: BTreeMap::new(),
                        },
                        false,
                    ),
                    valid_classes: KIND_CLASSES.iter().map(|v| v.to_string()).collect(),
                })
                .collect();
            let onenter = {
                let _path = path.clone();
                let onupdatemodel = onupdatemodel.clone();
                Callback::from(move |()| {
                    onupdatemodel.emit(Msg::Parent);
                })
            };
            let onupdatemodel0 = onupdatemodel.clone();
            let placeholder = if props.hash.is_none() {
                "***".to_string()
            } else {
                "".to_string()
            };
            html! {
              <CommandLine
                input_node_ref={ self.input_node_ref.clone() }
                entries={ entries }
                value={ node.value.clone() }
                placeholder={ placeholder }
                oninput={ Callback::from(move |v| {
                    onupdatemodel.emit(Msg::SetNodeValue(path.clone(), v));
                 }) }
                onselect={ ctx.props().updatemodel.clone() }
                ondelete={ Callback::from(move |()| {
                    onupdatemodel0.emit(Msg::DeleteItem);
                 }) }
                onenter={ onenter }
                enabled={ selected }
              />
            }
        } else {
            let renderer = if props.model.rich_render {
                kind.and_then(|k| k.renderer).unwrap_or(default_renderer)
            } else {
                default_renderer
            };
            let validator_context = ValidatorContext {
                model: props.model.clone(),
                path: path.clone(),
                node: node.clone(),
                onselect: props.onselect.clone(),
                updatemodel: props.updatemodel.clone(),
            };
            let content = renderer(&validator_context);
            let footer = if selected {
                let entries: Vec<Entry> = kind
                    .map(|k| {
                        let mut all_entries = vec![];
                        let mut field_entries = k
                            .get_fields()
                            .iter()
                            .map(|(field_id, field)| Entry {
                                label: field.name.to_string(),
                                description: "".to_string(),
                                action: Msg::AddField(path.to_vec(), **field_id),
                                valid_classes: FIELD_CLASSES
                                    .iter()
                                    .map(|v| v.to_string())
                                    .collect(),
                            })
                            .collect();
                        // TODO: macros
                        let mut macro_entries = vec![
                            Entry {
                                label: "delete".to_string(),
                                description: "".to_string(),
                                action: Msg::DeleteItem,
                                valid_classes: vec![],
                            },
                            // TODO: should apply to literals too.
                            Entry {
                                label: "call".to_string(),
                                description: "".to_string(),
                                action: Msg::ReplaceNode(
                                    path.clone(),
                                    Node {
                                        kind: RUST_FUNCTION_CALL.to_string(),
                                        value: "".to_string(),
                                        links: maplit::btreemap! {
                                            0 => vec![hash.clone()],
                                        },
                                    },
                                    false,
                                ),
                                valid_classes: vec![],
                            },
                        ];
                        all_entries.append(&mut field_entries);
                        all_entries.append(&mut macro_entries);
                        all_entries
                    })
                    .unwrap_or_default();
                // Make it look like an actual field.
                let _onupdatemodel0 = ctx.props().updatemodel.clone();
                html! {

                        <CommandLine
                            input_node_ref={ self.input_node_ref.clone() }
                            entries={ entries }
                            value={ node.value.clone() }
                            onselect={ ctx.props().updatemodel.clone() }
                            ondelete={ self.ondelete.clone() }
                            enabled=true
                        />

                }
            } else {
                html! {}
            };
            html! {
              <>
                { content }
                { footer }
              </>
            }
        };
        let onselect = ctx.props().onselect.clone();
        let onclick = {
            let path = path;
            ctx.link().callback(move |e: MouseEvent| {
                e.stop_propagation();
                onselect.emit(path.clone());
                NodeMsg::Click
            })
        };
        let mut classes = vec![
            "node".to_string(),
            "align-top".to_string(),
            "flex-auto".to_string(),
        ];
        if selected {
            classes.push("selected".to_string());
        }
        html! {

              { inner }

        }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            NodeMsg::Click => {
                self.focus_input();
                true
            }
        }
    }

    fn changed(&mut self, ctx: &Context<Self>) -> bool {
        let same = self.old_props == Some(ctx.props().clone());
        log::debug!("Node changed");
        log::debug!("same props: {:?}", same);
        if let Some(old_props) = &self.old_props {
            log::debug!("same path: {:?}", old_props.path == ctx.props().path);
            log::debug!("same model: {:?}", old_props.model == ctx.props().model);
            log::debug!("same hash: {:?}", old_props.hash == ctx.props().hash);
            log::debug!(
                "same updatemodel: {:?}",
                old_props.updatemodel == ctx.props().updatemodel
            );
            return old_props.path != ctx.props().path
                || old_props.model != ctx.props().model
                || old_props.hash != ctx.props().hash;
        }
        self.old_props = Some(ctx.props().clone());
        !same
    }
}

impl NodeComponent {
    fn focus_input(&self) {
        log::info!("focusing input");
        let input_node = self.input_node_ref.clone();
        if let Some(i) = input_node.cast::<HtmlInputElement>() {
            i.focus().unwrap();
        }
    }
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;

use errors;
use testing;
use syntax::edition::Edition;
use syntax::source_map::DUMMY_SP;
use syntax::feature_gate::UnstableFeatures;

use crate::externalfiles::{LoadStringError, load_string};
use crate::config::{Options, RenderOptions};
use crate::html::escape::Escape;
use crate::html::markdown;
use crate::html::markdown::{ErrorCodes, IdMap, Markdown, MarkdownWithToc, find_testable_code};
use crate::test::{TestOptions, Collector};

/// Separate any lines at the start of the file that begin with `# ` or `%`.
fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
    let mut metadata = Vec::new();
    let mut count = 0;

    for line in s.lines() {
        if line.starts_with("# ") || line.starts_with("%") {
            // trim the whitespace after the symbol
            metadata.push(line[1..].trim_start());
            count += line.len() + 1;
        } else {
            return (metadata, &s[count..]);
        }
    }

    // if we're here, then all lines were metadata `# ` or `%` lines.
    (metadata, "")
}

/// Render `input` (e.g., "foo.md") into an HTML file in `output`
/// (e.g., output = "bar" => "bar/foo.html").
pub fn render(
    input: PathBuf,
    options: RenderOptions,
    diag: &errors::Handler,
    edition: Edition
) -> i32 {
    let mut output = options.output;
    output.push(input.file_stem().unwrap());
    output.set_extension("html");

    let mut css = String::new();
    for name in &options.markdown_css {
        let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
        css.push_str(&s)
    }

    let input_str = match load_string(&input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };
    let playground_url = options.markdown_playground_url
                            .or(options.playground_url);
    let playground = playground_url.map(|url| markdown::Playground {
        crate_name: None,
        url,
    });

    let mut out = match File::create(&output) {
        Err(e) => {
            diag.struct_err(&format!("{}: {}", output.display(), e)).emit();
            return 4;
        }
        Ok(f) => f,
    };

    let (metadata, text) = extract_leading_metadata(&input_str);
    if metadata.is_empty() {
        diag.struct_err("invalid markdown file: no initial lines starting with `# ` or `%`").emit();
        return 5;
    }
    let title = metadata[0];

    let mut ids = IdMap::new();
    let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
    let text = if !options.markdown_no_toc {
        MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
    } else {
        Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
    };

    let err = write!(
        &mut out,
        r#"<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="rustdoc">
    <title>{title}</title>

    {css}
    {in_header}

    {before_content}

{title}

    {text}
    {after_content}

"#,
        title = Escape(title),
        css = css,
        in_header = options.external_html.in_header,
        before_content = options.external_html.before_content,
        text = text,
        after_content = options.external_html.after_content,
    );

    match err {
        Err(e) => {
            diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
            6
        }
        Ok(_) => 0,
    }
}

/// Runs any tests/code examples in the markdown file `input`.
pub fn test(mut options: Options, diag: &errors::Handler) -> i32 {
    let input_str = match load_string(&options.input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };

    let mut opts = TestOptions::default();
    opts.no_crate_inject = true;
    opts.display_warnings = options.display_warnings;
    let mut collector = Collector::new(options.input.display().to_string(), options.clone(),
                                       true, opts, None, Some(options.input));
    collector.set_position(DUMMY_SP);
    let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());

    find_testable_code(&input_str, &mut collector, codes);

    options.test_args.insert(0, "rustdoctest".to_string());
    testing::test_main(&options.test_args, collector.tests,
                       Some(testing::Options::new().display_output(options.display_warnings)));
    0
}
<commit_msg>Replace file_stem by file_name in rustdoc markdown<commit_after>use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;

use errors;
use testing;
use syntax::edition::Edition;
use syntax::source_map::DUMMY_SP;
use syntax::feature_gate::UnstableFeatures;

use crate::externalfiles::{LoadStringError, load_string};
use crate::config::{Options, RenderOptions};
use crate::html::escape::Escape;
use crate::html::markdown;
use crate::html::markdown::{ErrorCodes, IdMap, Markdown, MarkdownWithToc, find_testable_code};
use crate::test::{TestOptions, Collector};

/// Separate any lines at the start of the file that begin with `# ` or `%`.
fn extract_leading_metadata(s: &str) -> (Vec<&str>, &str) {
    let mut metadata = Vec::new();
    let mut count = 0;

    for line in s.lines() {
        if line.starts_with("# ") || line.starts_with("%") {
            // trim the whitespace after the symbol
            metadata.push(line[1..].trim_start());
            count += line.len() + 1;
        } else {
            return (metadata, &s[count..]);
        }
    }

    // if we're here, then all lines were metadata `# ` or `%` lines.
    (metadata, "")
}

/// Render `input` (e.g., "foo.md") into an HTML file in `output`
/// (e.g., output = "bar" => "bar/foo.html").
pub fn render(
    input: PathBuf,
    options: RenderOptions,
    diag: &errors::Handler,
    edition: Edition
) -> i32 {
    let mut output = options.output;
    output.push(input.file_name().unwrap());
    output.set_extension("html");

    let mut css = String::new();
    for name in &options.markdown_css {
        let s = format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">\n", name);
        css.push_str(&s)
    }

    let input_str = match load_string(&input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };
    let playground_url = options.markdown_playground_url
                            .or(options.playground_url);
    let playground = playground_url.map(|url| markdown::Playground {
        crate_name: None,
        url,
    });

    let mut out = match File::create(&output) {
        Err(e) => {
            diag.struct_err(&format!("{}: {}", output.display(), e)).emit();
            return 4;
        }
        Ok(f) => f,
    };

    let (metadata, text) = extract_leading_metadata(&input_str);
    if metadata.is_empty() {
        diag.struct_err("invalid markdown file: no initial lines starting with `# ` or `%`").emit();
        return 5;
    }
    let title = metadata[0];

    let mut ids = IdMap::new();
    let error_codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
    let text = if !options.markdown_no_toc {
        MarkdownWithToc(text, &mut ids, error_codes, edition, &playground).to_string()
    } else {
        Markdown(text, &[], &mut ids, error_codes, edition, &playground).to_string()
    };

    let err = write!(
        &mut out,
        r#"<!DOCTYPE html>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="generator" content="rustdoc">
    <title>{title}</title>

    {css}
    {in_header}

    {before_content}

{title}

    {text}
    {after_content}

"#,
        title = Escape(title),
        css = css,
        in_header = options.external_html.in_header,
        before_content = options.external_html.before_content,
        text = text,
        after_content = options.external_html.after_content,
    );

    match err {
        Err(e) => {
            diag.struct_err(&format!("cannot write to `{}`: {}", output.display(), e)).emit();
            6
        }
        Ok(_) => 0,
    }
}

/// Runs any tests/code examples in the markdown file `input`.
pub fn test(mut options: Options, diag: &errors::Handler) -> i32 {
    let input_str = match load_string(&options.input, diag) {
        Ok(s) => s,
        Err(LoadStringError::ReadFail) => return 1,
        Err(LoadStringError::BadUtf8) => return 2,
    };

    let mut opts = TestOptions::default();
    opts.no_crate_inject = true;
    opts.display_warnings = options.display_warnings;
    let mut collector = Collector::new(options.input.display().to_string(), options.clone(),
                                       true, opts, None, Some(options.input));
    collector.set_position(DUMMY_SP);
    let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());

    find_testable_code(&input_str, &mut collector, codes);

    options.test_args.insert(0, "rustdoctest".to_string());
    testing::test_main(&options.test_args, collector.tests,
                       Some(testing::Options::new().display_output(options.display_warnings)));
    0
}
<|endoftext|>


<commit_before>use std::fmt;
use std::io::Write;
use std::io::Read;
use std::io::BufReader;
use std::io::BufRead;
use std::io::BufWriter;
use std::fs::File;
use std::env;

const ROW_SIZE: usize = 9;

#[derive(Debug)]
pub struct Sudoku([[u32; ROW_SIZE]; ROW_SIZE]);

impl Sudoku {
    fn new(sudoku: &str) -> Self {
        let mut board = [[0; ROW_SIZE]; ROW_SIZE];
        let mut row = 0;
        let mut col = 0;
        for c in sudoku.chars() {
            board[row][col] = c.to_digit(10).unwrap();
            col += 1;
            if col == ROW_SIZE {
                col = 0;
                row += 1;
            }
        }
        Sudoku(board)
    }

    fn can_put(&self, row: usize, column: usize, val: u32) -> bool {
        for x in 0..ROW_SIZE {
            if self.0[row][x] == val || self.0[x][column] == val {
                return false;
            }
        }
        let a = row-(row%3);
        let b = column-(column%3);
        for x in a..a+3 {
            for y in b..b+3 {
                if self.0[x][y] == val { return false; }
            }
        }
        true
    }

    fn next_empty_spot(&self) -> Option<(usize, usize)> {
        for row in 0..ROW_SIZE {
            for column in 0..ROW_SIZE {
                if self.0[row][column] == 0 {
                    return Some((row, column));
                }
            }
        }
        None
    }

    fn solve(&mut self) {
        let (row, column) = match self.next_empty_spot() {
            Some(m) => m,
            None => return,
        };
        for val in 1..10 {
            if self.can_put(row, column, val) {
                self.0[row][column] = val;
                self.solve();
                if self.next_empty_spot().is_none() {
                    return;
                }
            }
        }
        self.0[row][column] = 0;
    }
}

impl fmt::Display for Sudoku {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut s: String = String::new();
        for row in self.0.iter() {
            s.push_str(
                &row.iter().map(|&x| x.to_string()).collect::<Vec<String>>().join(" "));
            s.push_str("\n");
        }
        write!(f, "{}", s)
    }
}

pub fn process<R, W>(input: R, output: &mut W) where W: Write, R: Read {
    let reader = BufReader::new(input);
    let mut writer = BufWriter::new(output);
    for line in reader.lines() {
        let mut sudoku = Sudoku::new(&line.unwrap());
        sudoku.solve();
        write!(writer, "{}\n", sudoku).unwrap();
    }
}

fn main() {
    let filename = env::args().nth(1).expect("input file name is necessary");
    let input = File::open(&filename).ok().expect("file not found");
    let mut output_name = String::from("solved_");
    output_name.push_str(&filename);
    let mut output = File::create(output_name).ok().expect("cannot create output file");
    process(input, &mut output);
}

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

    const SUDOKU_EXAMPLE: & 'static str = "200000060000075030048090100000300000300010009000008000001020570080730000090000004";
    const SUDOKU_SOLUTION: & 'static str = "2 7 3 4 8 1 9 6 5
9 1 6 2 7 5 4 3 8
5 4 8 6 9 3 1 2 7
8 5 9 3 4 7 6 1 2
3 6 7 5 1 2 8 4 9
1 2 4 9 6 8 7 5 3
4 3 1 8 2 9 5 7 6
6 8 5 7 3 4 2 9 1
7 9 2 1 5 6 3 8 4
";

    #[test]
    fn test_create_sudoku() {
        assert_eq!("2 0 0 0 0 0 0 6 0
0 0 0 0 7 5 0 3 0
0 4 8 0 9 0 1 0 0
0 0 0 3 0 0 0 0 0
3 0 0 0 1 0 0 0 9
0 0 0 0 0 8 0 0 0
0 0 1 0 2 0 5 7 0
0 8 0 7 3 0 0 0 0
0 9 0 0 0 0 0 0 4
",  Sudoku::new(SUDOKU_EXAMPLE).to_string());
    }

    #[test]
    fn test_next_empty() {
        let board = Sudoku::new(SUDOKU_EXAMPLE);
        assert_eq!(Some((0, 1)), board.next_empty_spot());
    }

    #[test]
    fn test_can_put() {
        let board = Sudoku::new(SUDOKU_EXAMPLE);
        assert!(board.can_put(0, 1, 7));
    }

    #[test]
    fn test_solve() {
        let mut board = Sudoku::new(SUDOKU_EXAMPLE);
        board.solve();
        assert_eq!(SUDOKU_SOLUTION, board.to_string());
    }

     #[test]
     fn test_process() {
         use std::io::Cursor;
         let input = Cursor::new(SUDOKU_EXAMPLE);
         let mut output = Cursor::new(Vec::<u8>::new());
         process(input, &mut output);
         assert_eq!(SUDOKU_SOLUTION.trim(), String::from_utf8(output.into_inner()).unwrap().trim());
     }
}
<commit_msg>relative path of file input<commit_after>use std::fmt;
use std::io::Write;
use std::io::Read;
use std::io::BufReader;
use std::io::BufRead;
use std::io::BufWriter;
use std::fs::File;
use std::env;
use std::path::Path;

const ROW_SIZE: usize = 9;

#[derive(Debug)]
pub struct Sudoku([[u32; ROW_SIZE]; ROW_SIZE]);

impl Sudoku {
    fn new(sudoku: &str) -> Self {
        let mut board = [[0; ROW_SIZE]; ROW_SIZE];
        let mut row = 0;
        let mut col = 0;
        for c in sudoku.chars() {
            board[row][col] = c.to_digit(10).unwrap();
            col += 1;
            if col == ROW_SIZE {
                col = 0;
                row += 1;
            }
        }
        Sudoku(board)
    }

    fn can_put(&self, row: usize, column: usize, val: u32) -> bool {
        for x in 0..ROW_SIZE {
            if self.0[row][x] == val || self.0[x][column] == val {
                return false;
            }
        }
        let a = row-(row%3);
        let b = column-(column%3);
        for x in a..a+3 {
            for y in b..b+3 {
                if self.0[x][y] == val { return false; }
            }
        }
        true
    }

    fn next_empty_spot(&self) -> Option<(usize, usize)> {
        for row in 0..ROW_SIZE {
            for column in 0..ROW_SIZE {
                if self.0[row][column] == 0 {
                    return Some((row, column));
                }
            }
        }
        None
    }

    fn solve(&mut self) {
        let (row, column) = match self.next_empty_spot() {
            Some(m) => m,
            None => return,
        };
        for val in 1..10 {
            if self.can_put(row, column, val) {
                self.0[row][column] = val;
                self.solve();
                if self.next_empty_spot().is_none() {
                    return;
                }
            }
        }
        self.0[row][column] = 0;
    }
}

impl fmt::Display for Sudoku {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut s: String = String::new();
        for row in self.0.iter() {
            s.push_str(
                &row.iter().map(|&x| x.to_string()).collect::<Vec<String>>().join(" "));
            s.push_str("\n");
        }
        write!(f, "{}", s)
    }
}

pub fn process<R, W>(input: R, output: &mut W) where W: Write, R: Read {
    let reader = BufReader::new(input);
    let mut writer = BufWriter::new(output);
    for line in reader.lines() {
        let mut sudoku = Sudoku::new(&line.unwrap());
        sudoku.solve();
        write!(writer, "{}\n", sudoku).unwrap();
    }
}

fn main() {
    let filename = env::args().nth(1).expect("input file name is necessary");
    let input = File::open(&filename).ok().expect("file not found");
    let mut output_name = String::from("solved_");
    output_name.push_str(Path::new(&filename).file_name().and_then(|x| x.to_str()).unwrap());
    let mut output = File::create(output_name).ok().expect("cannot create output file");
    process(input, &mut output);
}

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

    const SUDOKU_EXAMPLE: & 'static str = "200000060000075030048090100000300000300010009000008000001020570080730000090000004";
    const SUDOKU_SOLUTION: & 'static str = "2 7 3 4 8 1 9 6 5
9 1 6 2 7 5 4 3 8
5 4 8 6 9 3 1 2 7
8 5 9 3 4 7 6 1 2
3 6 7 5 1 2 8 4 9
1 2 4 9 6 8 7 5 3
4 3 1 8 2 9 5 7 6
6 8 5 7 3 4 2 9 1
7 9 2 1 5 6 3 8 4
";

    #[test]
    fn test_create_sudoku() {
        assert_eq!("2 0 0 0 0 0 0 6 0
0 0 0 0 7 5 0 3 0
0 4 8 0 9 0 1 0 0
0 0 0 3 0 0 0 0 0
3 0 0 0 1 0 0 0 9
0 0 0 0 0 8 0 0 0
0 0 1 0 2 0 5 7 0
0 8 0 7 3 0 0 0 0
0 9 0 0 0 0 0 0 4
",  Sudoku::new(SUDOKU_EXAMPLE).to_string());
    }

    #[test]
    fn test_next_empty() {
        let board = Sudoku::new(SUDOKU_EXAMPLE);
        assert_eq!(Some((0, 1)), board.next_empty_spot());
    }

    #[test]
    fn test_can_put() {
        let board = Sudoku::new(SUDOKU_EXAMPLE);
        assert!(board.can_put(0, 1, 7));
    }

    #[test]
    fn test_solve() {
        let mut board = Sudoku::new(SUDOKU_EXAMPLE);
        board.solve();
        assert_eq!(SUDOKU_SOLUTION, board.to_string());
    }

     #[test]
     fn test_process() {
         use std::io::Cursor;
         let input = Cursor::new(SUDOKU_EXAMPLE);
         let mut output = Cursor::new(Vec::<u8>::new());
         process(input, &mut output);
         assert_eq!(SUDOKU_SOLUTION.trim(), String::from_utf8(output.into_inner()).unwrap().trim());
     }
}
<|endoftext|>


<commit_before>//! High level, typed wrapper for `jsonrpc_core`.
//!
//! Enables creation of "Service" objects groupping a set of RPC methods together in a typed manner.
//!
//! Example
//!
//! ```
//! extern crate jsonrpc_core;
//! #[macro_use] extern crate jsonrpc_macros;
//! use jsonrpc_core::{IoHandler, Error};
//! use jsonrpc_core::futures::{self, BoxFuture, Future};
//! build_rpc_trait! {
//! 	pub trait Rpc {
//! 		/// Returns a protocol version
//! 		#[rpc(name = "protocolVersion")]
//! 		fn protocol_version(&self) -> Result<String, Error>;
//!
//! 		/// Adds two numbers and returns a result
//! 		#[rpc(name = "add")]
//! 		fn add(&self, u64, u64) -> Result<u64, Error>;
//!
//! 		/// Performs asynchronous operation
//! 		#[rpc(async, name = "callAsync")]
//! 		fn call(&self, u64) -> BoxFuture<String, Error>;
//! 	}
//! }
//! struct RpcImpl;
//! impl Rpc for RpcImpl {
//! 	fn protocol_version(&self) -> Result<String, Error> {
//! 		Ok("version1".into())
//! 	}
//!
//! 	fn add(&self, a: u64, b: u64) -> Result<u64, Error> {
//! 		Ok(a + b)
//! 	}
//!
//! 	fn call(&self, _: u64) -> BoxFuture<String, Error> {
//! 		futures::finished("OK".to_owned()).boxed()
//! 	}
//! }
//!
//! fn main() {
//!	  let mut io = IoHandler::new();
//!	  let rpc = RpcImpl;
//!
//!	  io.extend_with(rpc.to_delegate());
//! }
//! ```

#![warn(missing_docs)]

pub extern crate jsonrpc_core;
extern crate jsonrpc_pubsub;
extern crate serde;

mod auto_args;
mod delegates;
mod util;

pub mod pubsub;

#[doc(hidden)]
pub use auto_args::{Wrap, WrapAsync, WrapMeta, WrapSubscribe};
pub use auto_args::Trailing;
pub use delegates::IoDelegate;
pub use util::to_value;
<commit_msg>Fix typo. (#174)<commit_after>//! High level, typed wrapper for `jsonrpc_core`.
//!
//! Enables creation of "Service" objects grouping a set of RPC methods together in a typed manner.
//!
//! Example
//!
//! ```
//! extern crate jsonrpc_core;
//! #[macro_use] extern crate jsonrpc_macros;
//! use jsonrpc_core::{IoHandler, Error};
//! use jsonrpc_core::futures::{self, BoxFuture, Future};
//! build_rpc_trait! {
//! 	pub trait Rpc {
//! 		/// Returns a protocol version
//! 		#[rpc(name = "protocolVersion")]
//! 		fn protocol_version(&self) -> Result<String, Error>;
//!
//! 		/// Adds two numbers and returns a result
//! 		#[rpc(name = "add")]
//! 		fn add(&self, u64, u64) -> Result<u64, Error>;
//!
//! 		/// Performs asynchronous operation
//! 		#[rpc(async, name = "callAsync")]
//! 		fn call(&self, u64) -> BoxFuture<String, Error>;
//! 	}
//! }
//! struct RpcImpl;
//! impl Rpc for RpcImpl {
//! 	fn protocol_version(&self) -> Result<String, Error> {
//! 		Ok("version1".into())
//! 	}
//!
//! 	fn add(&self, a: u64, b: u64) -> Result<u64, Error> {
//! 		Ok(a + b)
//! 	}
//!
//! 	fn call(&self, _: u64) -> BoxFuture<String, Error> {
//! 		futures::finished("OK".to_owned()).boxed()
//! 	}
//! }
//!
//! fn main() {
//!	  let mut io = IoHandler::new();
//!	  let rpc = RpcImpl;
//!
//!	  io.extend_with(rpc.to_delegate());
//! }
//! ```

#![warn(missing_docs)]

pub extern crate jsonrpc_core;
extern crate jsonrpc_pubsub;
extern crate serde;

mod auto_args;
mod delegates;
mod util;

pub mod pubsub;

#[doc(hidden)]
pub use auto_args::{Wrap, WrapAsync, WrapMeta, WrapSubscribe};
pub use auto_args::Trailing;
pub use delegates::IoDelegate;
pub use util::to_value;
<|endoftext|>


<commit_before>// Copyright 2013-2014 Jeffery Olson
//
// Licensed under the 3-Clause BSD License, see LICENSE.txt
// at the top-level of this repository.
// This file may not be copied, modified, or distributed
// except according to those terms.
use sdl2::event::Event;
use sdl2::event::Event::{TextInput, TextEditing, KeyDown};
use sdl2::keyboard::{start_text_input, stop_text_input};
use sdl2::keycode::*;

use super::super::ui::{UiFont, UiBox};
use super::{ActiveView, PassiveView, DisplayViewContext};

pub struct TextInputDialogView<'a, TFont:'a, TBox:'a> {
    input_state: String,
    preface: &'a [String],
    prompt: String,
    cursor: String,
    bg_color: (u8, u8, u8),
    coords: (int, int),
    box_size: (uint, uint),
    text_gap: uint,
    ui_font: &'a TFont,
    ui_box: &'a TBox,
    started: bool
}

pub struct DisplayClearerPassiveView {
    bg_color: (u8, u8, u8)
}

impl<'a, TFont: UiFont, TBox: UiBox> TextInputDialogView<'a, TFont, TBox> {
    pub fn new(
        ui_font: &'a TFont,
        ui_box: &'a TBox,
        seed_state: Option<String>,
        preface: &'a [String],
        prompt: String,
        cursor: String,
        bg_color: (u8,u8,u8),
        coords: (int, int),
        text_gap: uint)
        -> TextInputDialogView<'a, TFont, TBox> {
            TextInputDialogView {
            input_state: match seed_state { Some(i) => i, None => "".to_string() },
            preface: preface,
            prompt: prompt,
            cursor: cursor,
            bg_color: bg_color,
            coords: coords,
            box_size: (0,0),
            text_gap: text_gap,
            ui_font: ui_font,
            ui_box: ui_box,
            started: false
        }
    }
}
impl<'a, TViewCtx: DisplayViewContext, TFont: UiFont, TBox: UiBox>
        ActiveView<TViewCtx, String> for TextInputDialogView<'a, TFont, TBox> {
    fn active_update<'a>(
        &'a mut self,
        _ctx: &TViewCtx,
        events: &[Event],
        _ms_time: u64,
        _passives: & mut Vec<&mut PassiveView<TViewCtx> >) -> Option<String> {
        //
        if !self.started {
            self.started = true;
            // call into SDL2 start text editing stuff
            start_text_input();
        }
        let mut out = None;
        for event in events.iter() {
            match *event {
                TextInput(_, _, ref txt) => {
                    out = None;
                    println!("TextInputEvent: {}", txt);
                    break;
                },
                TextEditing(_, _, ref txt, start, length) => {
                    out = None;
                    println!("TextEditingEvent: {}", txt);
                    break;
                },
                KeyDown(_, _, key, _, _) =>
                    match key {
                        Return => {
                            out = Some("YOLO".to_string());
                            println!("done editing text?");
                            stop_text_input();
                            break;
                        },
                        key => {
                            println!("pressed {} key", key);
                            out = None;
                            break;
                        }
                    },
                _ => {}
            }
        }
        out
    }
}

impl<'a, TViewCtx: DisplayViewContext, TFont: UiFont, TBox: UiBox>
        PassiveView<TViewCtx> for TextInputDialogView<'a, TFont, TBox> {
    fn passive_update(&mut self, _ctx: &TViewCtx, _t: u64) {
        // should never be passive, because the active_update() impl
        // doesn't yield_to() any other views, so we can put the logic
        // there
    }
}

impl DisplayClearerPassiveView {
    pub fn new(bgc: (u8, u8, u8)) -> DisplayClearerPassiveView {
        DisplayClearerPassiveView { bg_color: bgc }
    }
}

impl<TViewCtx: DisplayViewContext> PassiveView<TViewCtx> for DisplayClearerPassiveView {
    fn passive_update(&mut self, ctx: &TViewCtx, _time: u64) {
        let display = ctx.get_display();
        display.set_draw_color(self.bg_color);
        match display.renderer.clear() {
            Err(e) => panic!("Display Clearer.update(): failed to clear display: {}", e),
            _ => {}
        }
    }
}

impl<TViewCtx: DisplayViewContext> ActiveView<TViewCtx, ()>
        for DisplayClearerPassiveView {
    fn active_update<'a>(&'a mut self, _ctx: &TViewCtx, _e: &[Event], _t: u64,
              _p: &mut Vec<&mut PassiveView<TViewCtx> >)
        -> Option<()> {
            panic!("this should never be called.");
    }
}
<commit_msg>further flushing out of TextInputDialogView<commit_after>// Copyright 2013-2014 Jeffery Olson
//
// Licensed under the 3-Clause BSD License, see LICENSE.txt
// at the top-level of this repository.
// This file may not be copied, modified, or distributed
// except according to those terms.
use sdl2::event::Event;
use sdl2::event::Event::{TextInput, TextEditing, KeyDown};
use sdl2::keyboard::{start_text_input, stop_text_input, is_text_input_active};
use sdl2::keycode::*;

use ui::{compute_text_box_bounds, draw_text_box};

use super::super::ui::{UiFont, UiBox};
use super::{ActiveView, PassiveView, DisplayViewContext};

pub struct TextInputDialogView<'a, TFont:'a, TBox:'a> {
    input_state: String,
    previous_state: String,
    prompt: String,
    cursor: String,
    bg_color: (u8, u8, u8),
    coords: (int, int),
    box_size: (uint, uint),
    text_gap: uint,
    ui_font: &'a TFont,
    ui_box: &'a TBox,
    started: bool,
    box_content: Vec<String>
}

pub struct DisplayClearerPassiveView {
    bg_color: (u8, u8, u8)
}

impl<'a, TFont: UiFont, TBox: UiBox> TextInputDialogView<'a, TFont, TBox> {
    pub fn new(
        ui_font: &'a TFont,
        ui_box: &'a TBox,
        seed_state: Option<String>,
        preface: &'a [String],
        prompt: String,
        cursor: String,
        bg_color: (u8,u8,u8),
        coords: (int, int),
        text_gap: uint)
            -> TextInputDialogView<'a, TFont, TBox> {
        let mut bc = Vec::new();
        bc.push_all(preface);
        bc.push("".to_string());

        let mut ret = TextInputDialogView {
            input_state: match seed_state { Some(i) => i, None => "".to_string() },
            previous_state: "".to_string(),
            prompt: prompt,
            cursor: cursor,
            bg_color: bg_color,
            coords: coords,
            box_size: (15,4),
            text_gap: text_gap,
            ui_font: ui_font,
            ui_box: ui_box,
            started: false,
            box_content: bc
        };
        ret.update_content();
        ret
    }

    fn update_content(&mut self) {
        self.box_content.pop();
        self.box_content.push("".to_string());
        let bc_last = self.box_content.len() - 1;
        self.box_content[bc_last].push_str(self.prompt.as_slice());
        self.box_content[bc_last].push_str(" ");
        let is_last = self.input_state.len();
        self.box_content[bc_last].push_str(self.input_state.slice(0, is_last));
        self.box_content[bc_last].push_str(self.cursor.as_slice());
    }
}

impl<'a, TViewCtx: DisplayViewContext, TFont: UiFont, TBox: UiBox>
        ActiveView<TViewCtx, String> for TextInputDialogView<'a, TFont, TBox> {
    fn active_update<'a>(
        &'a mut self,
        ctx: &TViewCtx,
        events: &[Event],
        _ms_time: u64,
        _passives: & mut Vec<&mut PassiveView<TViewCtx> >) -> Option<String> {
        //
        if !self.started {
            self.started = true;
            // call into SDL2 start text editing stuff
            println!("call to start_text_input()");
            start_text_input();
        }
        self.previous_state = self.input_state.clone();
        let mut out = None;
        for event in events.iter() {
            match *event {
                KeyDown(_, _, key, _, _) =>
                    match key {
                        Return => {
                            out = Some(self.input_state.clone());
                            stop_text_input();
                            break;
                        },
                        Backspace => {
                            self.input_state.pop();
                        }
                        Escape => {
                            out = Some("".to_string());
                            stop_text_input();
                            break;
                        },
                        _ => {}
                    },
                TextInput(_, _, ref txt) => {
                    out = None;
                    self.input_state.push_str(txt.as_slice());
                },
                _ => {}
            }
        }
        if self.previous_state != self.input_state {
            self.update_content();
        }
        draw_text_box(ctx.get_display(), self.coords, self.box_size, self.bg_color, self.box_content.as_slice(), self.ui_font,
                      self.ui_box, self.text_gap);
        out
    }
}

impl<'a, TViewCtx: DisplayViewContext, TFont: UiFont, TBox: UiBox>
        PassiveView<TViewCtx> for TextInputDialogView<'a, TFont, TBox> {
    fn passive_update(&mut self, _ctx: &TViewCtx, _t: u64) {
        // should never be passive, because the active_update() impl
        // doesn't yield_to() any other views, so we can put the logic
        // there
    }
}

impl DisplayClearerPassiveView {
    pub fn new(bgc: (u8, u8, u8)) -> DisplayClearerPassiveView {
        DisplayClearerPassiveView { bg_color: bgc }
    }
}

impl<TViewCtx: DisplayViewContext> PassiveView<TViewCtx> for DisplayClearerPassiveView {
    fn passive_update(&mut self, ctx: &TViewCtx, _time: u64) {
        println!("in passive_update for DisplayClearer");
        let display = ctx.get_display();
        display.set_draw_color(self.bg_color);
        match display.renderer.clear() {
            Err(e) => panic!("Display Clearer.update(): failed to clear display: {}", e),
            _ => {}
        }
    }
}

impl<TViewCtx: DisplayViewContext> ActiveView<TViewCtx, ()>
        for DisplayClearerPassiveView {
    fn active_update<'a>(&'a mut self, _ctx: &TViewCtx, _e: &[Event], _t: u64,
              _p: &mut Vec<&mut PassiveView<TViewCtx> >)
        -> Option<()> {
            panic!("this should never be called.");
    }
}
<|endoftext|>


<commit_before>// DOCS

#![allow(unstable)]
#![feature(plugin)]
#[plugin] extern crate peg_syntax_ext;

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};

pub struct Property {
    /// Parameters.
    pub params: HashMap<String, String>,

    /// Value as unparsed string.
    pub raw_value: String,

    /// Property group. E.g. a contentline like `foo.FN:Markus` would result in the group being
    /// `"foo"`.
    pub prop_group: Option<String>
}

impl Property {
    pub fn new(params: HashMap<String, String>, raw_value: String, prop_group: Option<String>) -> Property {
        Property {
            params: params,
            raw_value: raw_value,
            prop_group: prop_group
        }
    }

    /// Create property from unescaped string.
    pub fn new_from_string(s: &str) -> Property {
        Property::new(HashMap::new(), escape_chars(s), None)
    }

    /// Get value as unescaped string.
    pub fn value_as_string(&self) -> String {
        unescape_chars(self.raw_value.as_slice())
    }
}

pub struct Component {
    /// The name of the component, such as `VCARD` or `VEVENT`.
    pub name: String,

    /// The component's properties.
    pub props: HashMap<String, Vec<Property>>,

    /// The component's child- or sub-components.
    pub subcomponents: Vec<Component>
}

impl Component {
    pub fn new(name: String) -> Component {
        Component {
            name: name,
            props: HashMap::new(),
            subcomponents: vec![]
        }
    }

    /// Retrieve one property (from many) by key. Returns `None` if nothing is found.
    pub fn single_prop(&self, key: &str) -> Option<&Property> {
        match self.props.get(key) {
            Some(x) => {
                match x.len() {
                    1 => Some(&x[0]),
                    _ => None
                }
            },
            None => None
        }
    }

    /// Retrieve a mutable vector of properties for this key. Creates one (and inserts it into the
    /// component) if none exists.
    pub fn all_props_mut(&mut self, key: &str) -> &mut Vec<Property> {
        match self.props.entry(String::from_str(key)) {
            Occupied(values) => values.into_mut(),
            Vacant(values) => values.insert(vec![])
        }
    }

    /// Retrieve properties by key. Returns an empty slice if key doesn't exist.
    pub fn all_props(&self, key: &str) -> &[Property] {
        static EMPTY: &'static [Property] = &[];
        match self.props.get(key) {
            Some(values) => values.as_slice(),
            None => EMPTY
        }
    }
}

peg! parser(r#"
use super::{Component,Property};
use std::collections::HashMap;

#[pub]
component -> Component
    = name:component_begin
      ps:props
      cs:components?
      component_end {
        let mut rv = Component::new(name);

        match cs {
            Some(components) => { rv.subcomponents = components; },
            None => ()
        };

        for (k, v) in ps.into_iter() {
            rv.all_props_mut(k).push(v);
        };

        rv
    }

component_begin -> String
    = "BEGIN:" v:value __ { v.to_string() }

component_end -> String
    = "END:" v:value __ { v.to_string() }

components -> Vec<Component>
    = cs:component ++ eols __ { cs }

props -> Vec<(&'input str, Property)>
    = ps:prop ++ eols __ { ps }

prop -> (&'input str, Property)
    = !"BEGIN:" !"END:" g:group? k:name p:params ":" v:value {
        (k, Property::new(p, v, g))
    }

group -> String
    = g:group_name "." { g }

group_name -> String
    = group_char+ { match_str.to_string() }

name -> &'input str
    = iana_token+ { match_str }

params -> HashMap<String, String>
    = ps:(";" p:param {p})* {
        let mut rv: HashMap<String, String> = HashMap::with_capacity(ps.len());
        for (k, v) in ps.into_iter() {
            rv.insert(k.to_string(), v.to_string());
        };
        rv
    }

param -> (&'input str, &'input str)
    // FIXME: Doesn't handle comma-separated values
    = k:param_name v:("=" v:param_value { v })? {
        (k, match v {
            Some(x) => x,
            None => ""
        })
    }

param_name -> &'input str
    = iana_token+ { match_str }

param_value -> &'input str
    = x:(quoted_string / param_text) { x }

param_text -> &'input str
    = safe_char* { match_str }

value -> String
    = value_char+ { match_str.to_string() }

quoted_string -> &'input str
    = dquote x:quoted_content dquote { x }

quoted_content -> &'input str
    = qsafe_char* { match_str }

iana_token = ([a-zA-Z0-9] / "-")+
group_char = ([a-zA-Z0-9] / "-")
qsafe_char = !dquote !ctl value_char
safe_char = !";" !":" qsafe_char

value_char = !eol .

eol = "\r\n" / "\n" / "\r"
dquote = "\""
eols = eol+

// Taken from vCard. vCalendar's is a subset. Together with the definition of "qsafe_char" this
// might reject a bunch of valid iCalendars, but I can't imagine one.
ctl = [\u{00}-\u{1F}] / "\u{7F}"

whitespace = " " / "\t"
__ = (eol / whitespace)*

"#);

/// Parse a component. The error value is a human-readable message.
pub fn parse_component(s: &str) -> Result<Component, String> {
    // XXX: The unfolding should be worked into the PEG
    // See feature request: https://github.com/kevinmehall/rust-peg/issues/26
    let unfolded = unfold_lines(s);
    parser::component(unfolded.as_slice())
}

/// Write a component. The error value is a human-readable message.
pub fn write_component(c: &Component) -> String {
    fn inner(buf: &mut String, c: &Component) {
        buf.push_str("BEGIN:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");

        for (prop_name, props) in c.props.iter() {
            for prop in props.iter() {
                match prop.prop_group {
                    Some(ref x) => { buf.push_str(x.as_slice()); buf.push('.'); },
                    None => ()
                };
                buf.push_str(prop_name.as_slice());
                for (param_key, param_value) in prop.params.iter() {
                    buf.push(';');
                    buf.push_str(param_key.as_slice());
                    buf.push('=');
                    buf.push_str(param_value.as_slice());
                };
                buf.push(':');
                buf.push_str(fold_line(prop.raw_value.as_slice()).as_slice());
                buf.push_str("\r\n");
            };
        };

        for subcomponent in c.subcomponents.iter() {
            inner(buf, subcomponent);
        };

        buf.push_str("END:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");
    }

    let mut buf = String::new();
    inner(&mut buf, c);
    buf
}

/// Escape text for a VObject property value.
pub fn escape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\n")
        .replace("\\", "\\\\")
        .replace(";", "\\;")
        .replace(",", "\\,")
        .replace("\r\n", "\\n")
        .replace("\n", "\\n")
}

/// Unescape text from a VObject property value.
pub fn unescape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\\n")
        .replace("\r\n", "\n")
        .replace("\\n", "\n")
        .replace("\\,", ",")
        .replace("\\;", ";")
        .replace("\\\\", "\\")
}

/// Unfold contentline.
pub fn unfold_lines(s: &str) -> String {
    s
        .replace("\r\n ", "").replace("\r\n\t", "")
        .replace("\n ", "").replace("\n\t", "")
        .replace("\r ", "").replace("\r\t", "")
}

/// Fold contentline to 75 chars. This function assumes the input to be unfolded, which means no
/// '\n' or '\r' in it.
pub fn fold_line(s: &str) -> String {
    let mut rv = String::new();
    for (i, c) in s.chars().enumerate() {
        rv.push(c);
        if i != 0 && i % 75 == 0 {
            rv.push_str("\r\n ");
        };
    };
    rv
}
<commit_msg>Simplify prop param insertion<commit_after>// DOCS

#![allow(unstable)]
#![feature(plugin)]
#[plugin] extern crate peg_syntax_ext;

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};

pub struct Property {
    /// Parameters.
    pub params: HashMap<String, String>,

    /// Value as unparsed string.
    pub raw_value: String,

    /// Property group. E.g. a contentline like `foo.FN:Markus` would result in the group being
    /// `"foo"`.
    pub prop_group: Option<String>
}

impl Property {
    pub fn new(params: HashMap<String, String>, raw_value: String, prop_group: Option<String>) -> Property {
        Property {
            params: params,
            raw_value: raw_value,
            prop_group: prop_group
        }
    }

    /// Create property from unescaped string.
    pub fn new_from_string(s: &str) -> Property {
        Property::new(HashMap::new(), escape_chars(s), None)
    }

    /// Get value as unescaped string.
    pub fn value_as_string(&self) -> String {
        unescape_chars(self.raw_value.as_slice())
    }
}

pub struct Component {
    /// The name of the component, such as `VCARD` or `VEVENT`.
    pub name: String,

    /// The component's properties.
    pub props: HashMap<String, Vec<Property>>,

    /// The component's child- or sub-components.
    pub subcomponents: Vec<Component>
}

impl Component {
    pub fn new(name: String) -> Component {
        Component {
            name: name,
            props: HashMap::new(),
            subcomponents: vec![]
        }
    }

    /// Retrieve one property (from many) by key. Returns `None` if nothing is found.
    pub fn single_prop(&self, key: &str) -> Option<&Property> {
        match self.props.get(key) {
            Some(x) => {
                match x.len() {
                    1 => Some(&x[0]),
                    _ => None
                }
            },
            None => None
        }
    }

    /// Retrieve a mutable vector of properties for this key. Creates one (and inserts it into the
    /// component) if none exists.
    pub fn all_props_mut(&mut self, key: &str) -> &mut Vec<Property> {
        match self.props.entry(String::from_str(key)) {
            Occupied(values) => values.into_mut(),
            Vacant(values) => values.insert(vec![])
        }
    }

    /// Retrieve properties by key. Returns an empty slice if key doesn't exist.
    pub fn all_props(&self, key: &str) -> &[Property] {
        static EMPTY: &'static [Property] = &[];
        match self.props.get(key) {
            Some(values) => values.as_slice(),
            None => EMPTY
        }
    }
}

peg! parser(r#"
use super::{Component,Property};
use std::collections::HashMap;

#[pub]
component -> Component
    = name:component_begin
      ps:props
      cs:components?
      component_end {
        let mut rv = Component::new(name);

        match cs {
            Some(components) => { rv.subcomponents = components; },
            None => ()
        };

        for (k, v) in ps.into_iter() {
            rv.all_props_mut(k).push(v);
        };

        rv
    }

component_begin -> String
    = "BEGIN:" v:value __ { v.to_string() }

component_end -> String
    = "END:" v:value __ { v.to_string() }

components -> Vec<Component>
    = cs:component ++ eols __ { cs }

props -> Vec<(&'input str, Property)>
    = ps:prop ++ eols __ { ps }

prop -> (&'input str, Property)
    = !"BEGIN:" !"END:" g:group? k:name p:params ":" v:value {
        (k, Property::new(p, v, g))
    }

group -> String
    = g:group_name "." { g }

group_name -> String
    = group_char+ { match_str.to_string() }

name -> &'input str
    = iana_token+ { match_str }

params -> HashMap<String, String>
    = ps:(";" p:param {p})* {
        let mut rv: HashMap<String, String> = HashMap::with_capacity(ps.len());
        rv.extend(ps.into_iter().map(|(k, v)| (k.to_string(), v.to_string())));
        rv
    }

param -> (&'input str, &'input str)
    // FIXME: Doesn't handle comma-separated values
    = k:param_name v:("=" v:param_value { v })? {
        (k, match v {
            Some(x) => x,
            None => ""
        })
    }

param_name -> &'input str
    = iana_token+ { match_str }

param_value -> &'input str
    = x:(quoted_string / param_text) { x }

param_text -> &'input str
    = safe_char* { match_str }

value -> String
    = value_char+ { match_str.to_string() }

quoted_string -> &'input str
    = dquote x:quoted_content dquote { x }

quoted_content -> &'input str
    = qsafe_char* { match_str }

iana_token = ([a-zA-Z0-9] / "-")+
group_char = ([a-zA-Z0-9] / "-")
qsafe_char = !dquote !ctl value_char
safe_char = !";" !":" qsafe_char

value_char = !eol .

eol = "\r\n" / "\n" / "\r"
dquote = "\""
eols = eol+

// Taken from vCard. vCalendar's is a subset. Together with the definition of "qsafe_char" this
// might reject a bunch of valid iCalendars, but I can't imagine one.
ctl = [\u{00}-\u{1F}] / "\u{7F}"

whitespace = " " / "\t"
__ = (eol / whitespace)*

"#);

/// Parse a component. The error value is a human-readable message.
pub fn parse_component(s: &str) -> Result<Component, String> {
    // XXX: The unfolding should be worked into the PEG
    // See feature request: https://github.com/kevinmehall/rust-peg/issues/26
    let unfolded = unfold_lines(s);
    parser::component(unfolded.as_slice())
}

/// Write a component. The error value is a human-readable message.
pub fn write_component(c: &Component) -> String {
    fn inner(buf: &mut String, c: &Component) {
        buf.push_str("BEGIN:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");

        for (prop_name, props) in c.props.iter() {
            for prop in props.iter() {
                match prop.prop_group {
                    Some(ref x) => { buf.push_str(x.as_slice()); buf.push('.'); },
                    None => ()
                };
                buf.push_str(prop_name.as_slice());
                for (param_key, param_value) in prop.params.iter() {
                    buf.push(';');
                    buf.push_str(param_key.as_slice());
                    buf.push('=');
                    buf.push_str(param_value.as_slice());
                };
                buf.push(':');
                buf.push_str(fold_line(prop.raw_value.as_slice()).as_slice());
                buf.push_str("\r\n");
            };
        };

        for subcomponent in c.subcomponents.iter() {
            inner(buf, subcomponent);
        };

        buf.push_str("END:");
        buf.push_str(c.name.as_slice());
        buf.push_str("\r\n");
    }

    let mut buf = String::new();
    inner(&mut buf, c);
    buf
}

/// Escape text for a VObject property value.
pub fn escape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\n")
        .replace("\\", "\\\\")
        .replace(";", "\\;")
        .replace(",", "\\,")
        .replace("\r\n", "\\n")
        .replace("\n", "\\n")
}

/// Unescape text from a VObject property value.
pub fn unescape_chars(s: &str) -> String {
    // Order matters! Lifted from icalendar.parser
    // https://github.com/collective/icalendar/
    s
        .replace("\\N", "\\n")
        .replace("\r\n", "\n")
        .replace("\\n", "\n")
        .replace("\\,", ",")
        .replace("\\;", ";")
        .replace("\\\\", "\\")
}

/// Unfold contentline.
pub fn unfold_lines(s: &str) -> String {
    s
        .replace("\r\n ", "").replace("\r\n\t", "")
        .replace("\n ", "").replace("\n\t", "")
        .replace("\r ", "").replace("\r\t", "")
}

/// Fold contentline to 75 chars. This function assumes the input to be unfolded, which means no
/// '\n' or '\r' in it.
pub fn fold_line(s: &str) -> String {
    let mut rv = String::new();
    for (i, c) in s.chars().enumerate() {
        rv.push(c);
        if i != 0 && i % 75 == 0 {
            rv.push_str("\r\n ");
        };
    };
    rv
}
<|endoftext|>


<commit_before>extern crate racco;

use racco::command::service;
use racco::config;

#[test]
fn service_deploy() {

  let conf = config::command::Config::from_file("fixtures/configs/service_deploy.yml", None).unwrap();
  let cmd = service::deploy::Command::new(&conf, "racco-test-web");

  // first time
  let res1 = cmd.run();
  assert!(res1.is_ok());

  // second time
  let res2 = cmd.run();
  assert!(res2.is_ok());

  // TODO: assert http response from nginx container
}

<commit_msg>Fix service test<commit_after>extern crate racco;

use racco::command::service;
use racco::config;

#[test]
fn service_deploy() {

  let conf = config::command::Config::from_file("fixtures/configs/service_deploy.yml", None).unwrap();
  let cmd = service::deploy::Command::new(&conf, "racco-test-web", false);

  // first time
  let res1 = cmd.run();
  assert!(res1.is_ok());

  // second time
  let res2 = cmd.run();
  assert!(res2.is_ok());

  // TODO: assert http response from nginx container
}

<|endoftext|>


<commit_before>//! rustdoc handler

use super::pool::Pool;
use super::file::File;
use super::MetaData;
use iron::prelude::*;
use iron::{status, Url};
use iron::modifiers::Redirect;
use router::Router;
use super::match_version;
use super::error::Nope;
use super::page::Page;
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
use iron::headers::{Expires, HttpDate};
use time;

#[derive(Debug)]
struct RustdocPage {
    pub head: String,
    pub body: String,
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub metadata: Option<MetaData>,
    pub platforms: Option<Json>
}

impl Default for RustdocPage {
    fn default() -> RustdocPage {
        RustdocPage {
            head: String::new(),
            body: String::new(),
            name: String::new(),
            version: String::new(),
            description: None,
            metadata: None,
            platforms: None,
        }
    }
}

impl ToJson for RustdocPage {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("rustdoc_head".to_string(), self.head.to_json());
        m.insert("rustdoc_body".to_string(), self.body.to_json());
        m.insert("rustdoc_status".to_string(), true.to_json());
        m.insert("name".to_string(), self.name.to_json());
        m.insert("version".to_string(), self.version.to_json());
        m.insert("description".to_string(), self.description.to_json());
        m.insert("metadata".to_string(), self.metadata.to_json());
        m.insert("platforms".to_string(), self.platforms.to_json());
        m.to_json()
    }
}

pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {

    fn redirect_to_doc(req: &Request,
                       name: &str,
                       vers: &str,
                       target_name: &str)
                       -> IronResult<Response> {
        let url = Url::parse(&format!("{}://{}:{}/{}/{}/{}/",
                                      req.url.scheme,
                                      req.url.host,
                                      req.url.port,
                                      name,
                                      vers,
                                      target_name)[..])
            .unwrap();
        let mut resp = Response::with((status::Found, Redirect(url)));
        resp.headers.set(Expires(HttpDate(time::now())));

        Ok(resp)
    }

    // this handler should never called without crate pattern
    let crate_name = req.extensions.get::<Router>().unwrap().find("crate").unwrap();
    let req_version = req.extensions.get::<Router>().unwrap().find("version");

    let conn = req.extensions.get::<Pool>().unwrap();

    let version = match match_version(&conn, &crate_name, req_version) {
        Some(v) => v,
        None => return Err(IronError::new(Nope::CrateNotFound, status::NotFound)),
    };

    // get target name
    // FIXME: This is a bit inefficient but allowing us to use less code in general
    let target_name: String = conn.query("SELECT target_name FROM releases INNER JOIN crates ON \
                                          crates.id = releases.crate_id WHERE crates.name = $1 \
                                          AND releases.version = $2",
               &[&crate_name, &version])
        .unwrap()
        .get(0)
        .get(0);

    redirect_to_doc(req, &crate_name, &version, &target_name)
}

pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {

    let name = req.extensions.get::<Router>().unwrap().find("crate").unwrap_or("").to_string();
    let version = req.extensions
        .get::<Router>()
        .unwrap()
        .find("version");
    let conn = req.extensions.get::<Pool>().unwrap();
    let version = try!(match_version(&conn, &name, version)
        .ok_or(IronError::new(Nope::ResourceNotFound, status::NotFound)));

    // remove name and version from path
    for _ in 0..2 {
        req.url.path.remove(0);
    }

    // docs have "rustdoc" prefix in database
    req.url.path.insert(0, "rustdoc".to_owned());

    // add crate name and version
    req.url.path.insert(1, name.clone());
    req.url.path.insert(2, version.clone());

    let path = {
        let mut path = req.url.path.join("/");
        if path.ends_with("/") {
            path.push_str("index.html");
            req.url.path.push("index.html".to_owned());
        }
        path
    };

    // don't touch anything other than html files
    if !path.ends_with(".html") {
        return Err(IronError::new(Nope::ResourceNotFound, status::NotFound));
    }

    let file = match File::from_path(&conn, &path) {
        Some(f) => f,
        None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
    };

    let (mut in_head, mut in_body) = (false, false);

    let mut content = RustdocPage::default();

    let file_content = String::from_utf8(file.content).unwrap();

    for line in file_content.lines() {

        if line.starts_with("<head") {
            in_head = true;
            continue;
        } else if line.starts_with("</head") {
            in_head = false;
        } else if line.starts_with("<body") {
            in_body = true;
            continue;
        } else if line.starts_with("</body") {
            in_body = false;
        }

        if in_head {
            content.head.push_str(&line[..]);
            content.head.push('\n');
        } else if in_body {
            content.body.push_str(&line[..]);
            content.body.push('\n');
        }
    }

    // content.metadata = MetaData::from_crate(&conn, &name, &version);
    let (metadata, platforms) = {
        let rows = conn.query("SELECT crates.name,
                                      releases.version,
                                      releases.description,
                                      releases.target_name,
                                      releases.rustdoc_status,
                                      doc_targets
                               FROM releases
                               INNER JOIN crates ON crates.id = releases.crate_id
                               WHERE crates.name = $1 AND releases.version = $2",
                              &[&name, &version]).unwrap();

        let metadata = MetaData {
            name: rows.get(0).get(0),
            version: rows.get(0).get(1),
            description: rows.get(0).get(2),
            target_name: rows.get(0).get(3),
            rustdoc_status: rows.get(0).get(4),
        };
        let platforms: Json = rows.get(0).get(5);
        (Some(metadata), platforms)
    };

    content.metadata = metadata;
    content.platforms = Some(platforms);

    Page::new(content)
        .set_true("show_package_navigation")
        .set_true("package_navigation_documentation_tab")
        .set_true("package_navigation_show_platforms_tab")
        .to_resp("rustdoc")
}

pub fn badge_handler(req: &mut Request) -> IronResult<Response> {
    use iron::headers::ContentType;
    use params::{Params, Value};
    use badge::{Badge, BadgeOptions};

    let version = {
        let params = req.get_ref::<Params>().unwrap();
        match params.find(&["version"]) {
            Some(&Value::String(ref version)) => version.clone(),
            _ => "*".to_owned(),
        }
    };

    let name = req.extensions.get::<Router>().unwrap().find("crate").unwrap();
    let conn = req.extensions.get::<Pool>().unwrap();

    let options = match match_version(&conn, &name, Some(&version)) {
        Some(version) => {
            let rows = conn.query("SELECT rustdoc_status
                                   FROM releases
                                   INNER JOIN crates ON crates.id = releases.crate_id
                                   WHERE crates.name = $1 AND releases.version = $2",
                                  &[&name, &version]).unwrap();
            if rows.len() > 0 && rows.get(0).get(0) {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#4d76ae".to_owned(),
                }
            } else {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#e05d44".to_owned(),
                }
            }
        },
        None => {
            BadgeOptions {
                subject: "docs".to_owned(),
                status: "no builds".to_owned(),
                color: "#e05d44".to_owned(),
            }
        },
    };

    let mut resp = Response::with((status::Ok, Badge::new(options).unwrap().to_svg()));
    resp.headers.set(ContentType("image/svg+xml".parse().unwrap()));
    resp.headers.set(Expires(HttpDate(time::now())));
    Ok(resp)
}
<commit_msg>Set CacheControl headers for badge handler<commit_after>//! rustdoc handler

use super::pool::Pool;
use super::file::File;
use super::MetaData;
use iron::prelude::*;
use iron::{status, Url};
use iron::modifiers::Redirect;
use router::Router;
use super::match_version;
use super::error::Nope;
use super::page::Page;
use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
use iron::headers::{Expires, HttpDate, CacheControl, CacheDirective};
use time;

#[derive(Debug)]
struct RustdocPage {
    pub head: String,
    pub body: String,
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    pub metadata: Option<MetaData>,
    pub platforms: Option<Json>
}

impl Default for RustdocPage {
    fn default() -> RustdocPage {
        RustdocPage {
            head: String::new(),
            body: String::new(),
            name: String::new(),
            version: String::new(),
            description: None,
            metadata: None,
            platforms: None,
        }
    }
}

impl ToJson for RustdocPage {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("rustdoc_head".to_string(), self.head.to_json());
        m.insert("rustdoc_body".to_string(), self.body.to_json());
        m.insert("rustdoc_status".to_string(), true.to_json());
        m.insert("name".to_string(), self.name.to_json());
        m.insert("version".to_string(), self.version.to_json());
        m.insert("description".to_string(), self.description.to_json());
        m.insert("metadata".to_string(), self.metadata.to_json());
        m.insert("platforms".to_string(), self.platforms.to_json());
        m.to_json()
    }
}

pub fn rustdoc_redirector_handler(req: &mut Request) -> IronResult<Response> {

    fn redirect_to_doc(req: &Request,
                       name: &str,
                       vers: &str,
                       target_name: &str)
                       -> IronResult<Response> {
        let url = Url::parse(&format!("{}://{}:{}/{}/{}/{}/",
                                      req.url.scheme,
                                      req.url.host,
                                      req.url.port,
                                      name,
                                      vers,
                                      target_name)[..])
            .unwrap();
        let mut resp = Response::with((status::Found, Redirect(url)));
        resp.headers.set(Expires(HttpDate(time::now())));

        Ok(resp)
    }

    // this handler should never called without crate pattern
    let crate_name = req.extensions.get::<Router>().unwrap().find("crate").unwrap();
    let req_version = req.extensions.get::<Router>().unwrap().find("version");

    let conn = req.extensions.get::<Pool>().unwrap();

    let version = match match_version(&conn, &crate_name, req_version) {
        Some(v) => v,
        None => return Err(IronError::new(Nope::CrateNotFound, status::NotFound)),
    };

    // get target name
    // FIXME: This is a bit inefficient but allowing us to use less code in general
    let target_name: String = conn.query("SELECT target_name FROM releases INNER JOIN crates ON \
                                          crates.id = releases.crate_id WHERE crates.name = $1 \
                                          AND releases.version = $2",
               &[&crate_name, &version])
        .unwrap()
        .get(0)
        .get(0);

    redirect_to_doc(req, &crate_name, &version, &target_name)
}

pub fn rustdoc_html_server_handler(req: &mut Request) -> IronResult<Response> {

    let name = req.extensions.get::<Router>().unwrap().find("crate").unwrap_or("").to_string();
    let version = req.extensions
        .get::<Router>()
        .unwrap()
        .find("version");
    let conn = req.extensions.get::<Pool>().unwrap();
    let version = try!(match_version(&conn, &name, version)
        .ok_or(IronError::new(Nope::ResourceNotFound, status::NotFound)));

    // remove name and version from path
    for _ in 0..2 {
        req.url.path.remove(0);
    }

    // docs have "rustdoc" prefix in database
    req.url.path.insert(0, "rustdoc".to_owned());

    // add crate name and version
    req.url.path.insert(1, name.clone());
    req.url.path.insert(2, version.clone());

    let path = {
        let mut path = req.url.path.join("/");
        if path.ends_with("/") {
            path.push_str("index.html");
            req.url.path.push("index.html".to_owned());
        }
        path
    };

    // don't touch anything other than html files
    if !path.ends_with(".html") {
        return Err(IronError::new(Nope::ResourceNotFound, status::NotFound));
    }

    let file = match File::from_path(&conn, &path) {
        Some(f) => f,
        None => return Err(IronError::new(Nope::ResourceNotFound, status::NotFound)),
    };

    let (mut in_head, mut in_body) = (false, false);

    let mut content = RustdocPage::default();

    let file_content = String::from_utf8(file.content).unwrap();

    for line in file_content.lines() {

        if line.starts_with("<head") {
            in_head = true;
            continue;
        } else if line.starts_with("</head") {
            in_head = false;
        } else if line.starts_with("<body") {
            in_body = true;
            continue;
        } else if line.starts_with("</body") {
            in_body = false;
        }

        if in_head {
            content.head.push_str(&line[..]);
            content.head.push('\n');
        } else if in_body {
            content.body.push_str(&line[..]);
            content.body.push('\n');
        }
    }

    // content.metadata = MetaData::from_crate(&conn, &name, &version);
    let (metadata, platforms) = {
        let rows = conn.query("SELECT crates.name,
                                      releases.version,
                                      releases.description,
                                      releases.target_name,
                                      releases.rustdoc_status,
                                      doc_targets
                               FROM releases
                               INNER JOIN crates ON crates.id = releases.crate_id
                               WHERE crates.name = $1 AND releases.version = $2",
                              &[&name, &version]).unwrap();

        let metadata = MetaData {
            name: rows.get(0).get(0),
            version: rows.get(0).get(1),
            description: rows.get(0).get(2),
            target_name: rows.get(0).get(3),
            rustdoc_status: rows.get(0).get(4),
        };
        let platforms: Json = rows.get(0).get(5);
        (Some(metadata), platforms)
    };

    content.metadata = metadata;
    content.platforms = Some(platforms);

    Page::new(content)
        .set_true("show_package_navigation")
        .set_true("package_navigation_documentation_tab")
        .set_true("package_navigation_show_platforms_tab")
        .to_resp("rustdoc")
}

pub fn badge_handler(req: &mut Request) -> IronResult<Response> {
    use iron::headers::ContentType;
    use params::{Params, Value};
    use badge::{Badge, BadgeOptions};

    let version = {
        let params = req.get_ref::<Params>().unwrap();
        match params.find(&["version"]) {
            Some(&Value::String(ref version)) => version.clone(),
            _ => "*".to_owned(),
        }
    };

    let name = req.extensions.get::<Router>().unwrap().find("crate").unwrap();
    let conn = req.extensions.get::<Pool>().unwrap();

    let options = match match_version(&conn, &name, Some(&version)) {
        Some(version) => {
            let rows = conn.query("SELECT rustdoc_status
                                   FROM releases
                                   INNER JOIN crates ON crates.id = releases.crate_id
                                   WHERE crates.name = $1 AND releases.version = $2",
                                  &[&name, &version]).unwrap();
            if rows.len() > 0 && rows.get(0).get(0) {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#4d76ae".to_owned(),
                }
            } else {
                BadgeOptions {
                    subject: "docs".to_owned(),
                    status: version,
                    color: "#e05d44".to_owned(),
                }
            }
        },
        None => {
            BadgeOptions {
                subject: "docs".to_owned(),
                status: "no builds".to_owned(),
                color: "#e05d44".to_owned(),
            }
        },
    };

    let mut resp = Response::with((status::Ok, Badge::new(options).unwrap().to_svg()));
    resp.headers.set(ContentType("image/svg+xml".parse().unwrap()));
    resp.headers.set(Expires(HttpDate(time::now())));
    resp.headers.set(CacheControl(vec![CacheDirective::NoCache,
                                       CacheDirective::NoStore,
                                       CacheDirective::MustRevalidate]));
    Ok(resp)
}
<|endoftext|>