squark 0.7.1

Virtual DOM implemention and application definition inspired from HyperApp
Documentation
use rustc_hash::{FxHashMap, FxHashSet};
use std::iter::FromIterator;

pub use serde_json::Value as HandlerArg;

type Attribute = (String, AttributeValue);

fn diff_attributes(a: &mut Vec<Attribute>, b: &[Attribute]) -> Vec<Diff> {
    let mut result = vec![];

    let mut old_map = FxHashMap::<String, AttributeValue>::from_iter(a.drain(..));
    for &(ref new_key, ref new_val) in b {
        match old_map.remove(new_key) {
            Some(old_val) => {
                if &old_val != new_val {
                    result.push(Diff::SetAttribute(new_key.to_owned(), new_val.to_owned()))
                }
            }
            None => result.push(Diff::SetAttribute(new_key.to_owned(), new_val.to_owned())),
        }
    }

    for (old_key, _) in old_map.drain() {
        result.push(Diff::RemoveAttribute(old_key));
    }

    result
}

pub(crate) type HandlerFunction<A> = Box<Fn(HandlerArg) -> Option<A>>;
type Handler = (String, String);

fn diff_handlers(a: &mut Vec<Handler>, b: &[Handler]) -> Vec<Diff> {
    let mut result = vec![];

    let mut old_map = FxHashMap::<String, String>::from_iter(a.drain(..));
    for &(ref new_key, ref new_id) in b {
        old_map.remove(new_key);
        result.push(Diff::SetHandler(new_key.to_owned(), new_id.to_owned()));
    }

    for (old_key, old_id) in old_map.drain() {
        result.push(Diff::RemoveHandler(old_key, old_id));
    }

    result
}

#[derive(Clone, Debug)]
pub enum Node {
    Text(String),
    Element(Element),
    Null,
}

impl Node {
    pub(crate) fn diff(a: &mut Node, b: &Node, i: &mut usize) -> Option<Diff> {
        match (a, b) {
            (&mut Node::Element(ref mut a), &Node::Element(ref b)) => {
                match Element::diff(a, b, *i) {
                    Some(diff) => Some(diff),
                    None => None,
                }
            }
            (&mut Node::Text(ref mut text_a), &Node::Text(ref text_b)) => {
                if text_a == text_b {
                    return None;
                }
                Some(Diff::ReplaceChild(*i, b.to_owned()))
            }
            (&mut Node::Null, &Node::Null) => None,
            (&mut Node::Null, _) => Some(Diff::AddChild(*i, b.to_owned())),
            (_, &Node::Null) => Some(Diff::RemoveChild(*i)),
            _ => Some(Diff::ReplaceChild(*i, b.to_owned())),
        }
    }

    fn get_key(&self) -> Option<String> {
        match self {
            Node::Element(ref el) => el.get_key(),
            _ => None,
        }
    }
}

fn get_nodelist_key_set(nodelist: &[Node]) -> FxHashSet<String> {
    FxHashSet::from_iter(nodelist.iter().filter_map(|c| c.get_key()))
}

fn diff_children(a: &mut Vec<Node>, b: &[Node], i: &mut usize) -> Vec<Diff> {
    let mut result = vec![];
    let b_key_set = get_nodelist_key_set(b);
    let survived = a
        .drain(..)
        .filter(|c| match c.get_key() {
            Some(k) => {
                let is_survived = b_key_set.contains(&k);
                if !is_survived {
                    result.push(Diff::RemoveChild(*i));
                    return false;
                }
                *i += 1;
                true
            }
            None => {
                *i += 1;
                true
            }
        })
        .collect();
    *a = survived;

    let mut i = 0;
    a.reverse();
    for new_child in b.iter() {
        match a.pop() {
            None => {
                result.push(Diff::AddChild(i, new_child.to_owned()));
                i += 1;
            }
            Some(mut old_child) => {
                if let Some(diff) = Node::diff(&mut old_child, new_child, &mut i) {
                    result.push(diff.to_owned());
                    if let Diff::RemoveChild(_) = diff {
                        continue;
                    }
                }
            }
        }
        i += 1;
    }

    for _ in a.iter() {
        result.push(Diff::RemoveChild(i));
    }

    result
}

#[derive(Clone, Debug)]
pub struct Element {
    name: String,
    attributes: Vec<Attribute>,
    handlers: Vec<Handler>,
    children: Vec<Node>,
}

impl Element {
    fn new(
        name: String,
        attributes: Vec<Attribute>,
        handlers: Vec<Handler>,
        children: Vec<Node>,
    ) -> Element {
        Element {
            name,
            attributes,
            handlers,
            children,
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn attributes(&self) -> &[Attribute] {
        &self.attributes
    }

    pub fn handlers(&self) -> &[Handler] {
        &self.handlers
    }

    pub fn children(&self) -> &[Node] {
        &self.children
    }

    fn diff(a: &mut Element, b: &Element, i: usize) -> Option<Diff> {
        if let (Some(a_key), Some(b_key)) = (a.get_key(), b.get_key()) {
            if a_key != b_key {
                return Some(Diff::ReplaceChild(i, Node::Element(b.to_owned())));
            }
        }

        if a.name != b.name {
            return Some(Diff::ReplaceChild(i, Node::Element(b.to_owned())));
        }

        let mut result = vec![];

        result.append(&mut diff_attributes(&mut a.attributes, &b.attributes));
        result.append(&mut diff_handlers(&mut a.handlers, &b.handlers));
        result.append(&mut diff_children(&mut a.children, &b.children, &mut 0));

        if result.is_empty() {
            return None;
        }
        Some(Diff::PatchChild(i, result))
    }

    fn get_key(&self) -> Option<String> {
        self.attributes
            .iter()
            .find(|&&(ref k, _)| k == "key")
            .and_then(|&(_, ref v)| match v {
                AttributeValue::String(ref s) => Some(s.to_owned()),
                AttributeValue::Bool(_) => None,
            })
    }
}

#[derive(Debug, Clone)]
pub enum Diff {
    SetAttribute(String, AttributeValue),
    RemoveAttribute(String),
    AddChild(usize, Node),
    ReplaceChild(usize, Node),
    RemoveChild(usize),
    PatchChild(usize, Vec<Diff>),
    SetHandler(String, String),
    RemoveHandler(String, String),
}

#[derive(Clone, Debug, PartialEq)]
pub enum AttributeValue {
    String(String),
    Bool(bool),
}

impl From<String> for AttributeValue {
    fn from(s: String) -> AttributeValue {
        AttributeValue::String(s)
    }
}

impl<'a> From<&'a str> for AttributeValue {
    fn from(s: &'a str) -> AttributeValue {
        AttributeValue::String(s.to_owned())
    }
}

impl From<bool> for AttributeValue {
    fn from(b: bool) -> AttributeValue {
        AttributeValue::Bool(b)
    }
}

pub(crate) type HandlerMap<A> = FxHashMap<String, HandlerFunction<A>>;

pub struct View<A> {
    pub(crate) node: Node,
    pub(crate) handler_map: HandlerMap<A>,
}

pub enum Child<A> {
    View(View<A>),
    ViewList(Vec<View<A>>),
}

impl<A, T> From<T> for Child<A>
where
    T: Into<View<A>> + Sized,
{
    fn from(v: T) -> Child<A> {
        Child::View(v.into())
    }
}

impl<A> FromIterator<View<A>> for Child<A> {
    fn from_iter<I>(iter: I) -> Child<A>
    where
        I: IntoIterator<Item = View<A>>,
    {
        Child::ViewList(iter.into_iter().collect())
    }
}

impl<A> View<A> {
    pub fn new(
        name: String,
        attributes: Vec<Attribute>,
        handlers: Vec<(String, (String, HandlerFunction<A>))>,
        children: Vec<Child<A>>,
    ) -> View<A> {
        let mut handler_map = FxHashMap::default();
        let handlers = handlers
            .into_iter()
            .map(|(kind, (id, f))| {
                let handler = (kind, id.to_owned());
                handler_map.insert(id, f);
                handler
            })
            .collect();

        let mut children_vec = vec![];
        for child in children {
            match child {
                Child::View(v) => {
                    handler_map.extend(v.handler_map);
                    children_vec.push(v.node);
                }
                Child::ViewList(child_vec) => {
                    for v in child_vec {
                        handler_map.extend(v.handler_map);
                        children_vec.push(v.node);
                    }
                }
            }
        }

        View {
            node: Node::Element(Element::new(name, attributes, handlers, children_vec)),
            handler_map,
        }
    }

    pub fn text(s: String) -> View<A> {
        View {
            node: Node::Text(s),
            handler_map: FxHashMap::default(),
        }
    }

    pub fn null() -> View<A> {
        View {
            node: Node::Null,
            handler_map: FxHashMap::default(),
        }
    }
}

impl<A> From<()> for View<A> {
    fn from(_: ()) -> View<A> {
        View::null()
    }
}

impl<A> From<String> for View<A> {
    fn from(s: String) -> View<A> {
        View::text(s)
    }
}

impl<'a, A> From<&'a str> for View<A> {
    fn from(s: &'a str) -> View<A> {
        View::text(s.to_owned())
    }
}

impl<A, T> From<Option<T>> for View<A>
where
    T: Into<View<A>>,
{
    fn from(option: Option<T>) -> View<A> {
        option.map_or_else(View::null, |v| v.into())
    }
}