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

pub struct NodeComponent {
    input_node_ref: NodeRef,
}

#[derive(Properties, PartialEq)]
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 validators: &'static [FieldValidator],
}

pub enum NodeMsg {
    Click,
}

pub const FIELD_CLASSES: &[&str] = &[
    "bg-blue-300",
    "inline-block",
    "p-0.5",
    "px-1",
    "border-blue-800",
    "border",
    "rounded",
];
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");
        Self {
            input_node_ref: NodeRef::default(),
        }
    }

    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 {
        log::debug!("rendering node");
        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);
        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 node.kind.is_empty() {
            let onupdatemodel = ctx.props().updatemodel.clone();
            let path = path.clone();
            let entries: Vec<Entry> = props
                .validators
                .iter()
                .flat_map(|v| match v {
                    FieldValidator::Kind(k) => Some(Entry {
                        label: k.to_string(),
                        description: "".to_string(),
                        action: Msg::ReplaceNode(
                            path.clone(),
                            Node {
                                kind: k.to_string(),
                                value: "".to_string(),
                                children: BTreeMap::new(),
                            },
                            false,
                        ),
                        valid_classes: KIND_CLASSES.iter().map(|v| v.to_string()).collect(),
                    }),
                    FieldValidator::Literal(v) => None,
                })
                .collect();
            log::info!("{:?}", entries);
            let onenter = {
                let path = path.clone();
                let onupdatemodel = onupdatemodel.clone();
                Callback::from(move |()| {
                    onupdatemodel.emit(Msg::Parent);
                })
            };
            html! {
              <CommandLine
                input_node_ref={ self.input_node_ref.clone() }
                entries={ entries }
                value={ node.value.clone() }
                oninput={ Callback::from(move |v| {
                    onupdatemodel.emit(Msg::SetNodeValue(path.clone(), v));
                 }) }
                onselect={ ctx.props().updatemodel.clone() }
                onenter={ onenter }
                enabled={ selected }
              />
            }
        } else {
            let renderer = kind
                .map(|r| {
                    let KindValue::Struct { renderer, .. } = r.value;
                    renderer
                })
                .unwrap_or(default_renderer);
            // TODO: Disable default renderer.
            let renderer = 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);
            /*
            // Node.
            // https://codepen.io/xotonic/pen/JRLAOR
            let children: Vec<_> = if false {
                node.children
                    .iter()
                    .map(|(field_name, hashes)| {
                        let field_schema = kind.and_then(|k| k.get_field(field_name));
                        let validators = field_schema
                            .map(|v| v.validators.clone())
                            .unwrap_or_default();
                        let values = hashes.iter().enumerate().map(|(i, h)| {
                            let child_path = append(
                                &path,
                                Selector {
                                    field: field_name.clone(),
                                    index: i,
                                },
                            );
                            html! {

                                    <NodeComponent
                                      file={ ctx.props().file.clone() }
                                      hash={ h.clone() }
                                      cursor={ ctx.props().cursor.clone() }
                                      onselect={ ctx.props().onselect.clone() }
                                      path={ child_path }
                                      updatemodel={ ctx.props().updatemodel.clone() }
                                      validators={ validators }
                                    />

                            }
                        });
                        html! {

                                    { field_name }

{ for values }

                        }
                    })
                    .collect()
            } else {
                node.children
                    .iter()
                    .flat_map(|(field_name, hashes)| {
                        let field_schema = kind.and_then(|k| k.get_field(field_name));
                        let validators = field_schema
                            .map(|v| v.validators.clone())
                            .unwrap_or_default();
                        let path = path.clone();
                        hashes.iter().enumerate().map(move |(i, h)| {
                            let child_path = append(
                                &path,
                                Selector {
                                    field: field_name.clone(),
                                    index: i,
                                },
                            );
                            let onclick = {
                                let onselect = props.onselect.clone();
                                let child_path = child_path.clone();
                                ctx.link().callback(move |e: MouseEvent| {
                                    log::info!("click");
                                    e.stop_propagation();
                                    onselect.emit(child_path.clone());
                                    NodeMsg::Noop
                                })
                            };
                            // TODO: Sticky field headers.
                            html! {

                                        { field_name }

                                        { ":" }

                                    <NodeComponent
                                      file={ ctx.props().file.clone() }
                                      hash={ h.clone() }
                                      cursor={ ctx.props().cursor.clone() }
                                      onselect={ ctx.props().onselect.clone() }
                                      path={ child_path }
                                      updatemodel={ ctx.props().updatemodel.clone() }
                                      validators={ validators }
                                    />

                            }
                        })
                    })
                    .collect()
            };
            let content = html! {

                    { for children }

            };
            */
            let header = html! {

                        { node.kind.clone() }

                        { ctx.props().hash.clone().unwrap_or("-".to_string()) }

            };
            let footer = if selected {
                let entries: Vec<Entry> = kind
                    .map(|k| {
                        let KindValue::Struct { fields, .. } = k.value;
                        fields
                            .iter()
                            .map(|f| Entry {
                                label: f.name.to_string(),
                                description: "".to_string(),
                                action: Msg::AddField(path.to_vec(), f.name.to_string()),
                                valid_classes: FIELD_CLASSES
                                    .iter()
                                    .map(|v| v.to_string())
                                    .collect(),
                            })
                            .collect()
                    })
                    .unwrap_or_default();
                // Make it look like an actual field.
                html! {

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

                }
            } else {
                html! {}
            };
            html! {
              <>
                { header }
                { content }
                { footer }
              </>
            }
        };
        let onselect = ctx.props().onselect.clone();
        let onclick = {
            let path = path.clone();
            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
            }
        }
    }
}

impl NodeComponent {
    fn focus_input(&self) {
        let input_node = self.input_node_ref.clone();
        input_node
            .cast::<HtmlInputElement>()
            .unwrap()
            .focus()
            .unwrap();
    }
}
<commit_msg>Changed log<commit_after>use crate::{
    command_line::{CommandLine, Entry},
    schema::{default_renderer, FieldValidator, KindValue, ValidatorContext, SCHEMA},
    types::{append, File, Hash, Model, Msg, Node, Selector},
};
use std::{collections::BTreeMap, rc::Rc};
use web_sys::HtmlInputElement;
use yew::prelude::*;

pub struct NodeComponent {
    input_node_ref: NodeRef,
}

#[derive(Properties, PartialEq)]
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 validators: &'static [FieldValidator],
}

pub enum NodeMsg {
    Click,
}

pub const FIELD_CLASSES: &[&str] = &[
    "bg-blue-300",
    "inline-block",
    "p-0.5",
    "px-1",
    "border-blue-800",
    "border",
    "rounded",
];
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");
        Self {
            input_node_ref: NodeRef::default(),
        }
    }

    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 {
        log::debug!("rendering node");
        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);
        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 node.kind.is_empty() {
            let onupdatemodel = ctx.props().updatemodel.clone();
            let path = path.clone();
            let entries: Vec<Entry> = props
                .validators
                .iter()
                .flat_map(|v| match v {
                    FieldValidator::Kind(k) => Some(Entry {
                        label: k.to_string(),
                        description: "".to_string(),
                        action: Msg::ReplaceNode(
                            path.clone(),
                            Node {
                                kind: k.to_string(),
                                value: "".to_string(),
                                children: BTreeMap::new(),
                            },
                            false,
                        ),
                        valid_classes: KIND_CLASSES.iter().map(|v| v.to_string()).collect(),
                    }),
                    FieldValidator::Literal(v) => None,
                })
                .collect();
            log::info!("{:?}", entries);
            let onenter = {
                let path = path.clone();
                let onupdatemodel = onupdatemodel.clone();
                Callback::from(move |()| {
                    onupdatemodel.emit(Msg::Parent);
                })
            };
            html! {
              <CommandLine
                input_node_ref={ self.input_node_ref.clone() }
                entries={ entries }
                value={ node.value.clone() }
                oninput={ Callback::from(move |v| {
                    onupdatemodel.emit(Msg::SetNodeValue(path.clone(), v));
                 }) }
                onselect={ ctx.props().updatemodel.clone() }
                onenter={ onenter }
                enabled={ selected }
              />
            }
        } else {
            let renderer = kind
                .map(|r| {
                    let KindValue::Struct { renderer, .. } = r.value;
                    renderer
                })
                .unwrap_or(default_renderer);
            // TODO: Disable default renderer.
            let renderer = 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);
            /*
            // Node.
            // https://codepen.io/xotonic/pen/JRLAOR
            let children: Vec<_> = if false {
                node.children
                    .iter()
                    .map(|(field_name, hashes)| {
                        let field_schema = kind.and_then(|k| k.get_field(field_name));
                        let validators = field_schema
                            .map(|v| v.validators.clone())
                            .unwrap_or_default();
                        let values = hashes.iter().enumerate().map(|(i, h)| {
                            let child_path = append(
                                &path,
                                Selector {
                                    field: field_name.clone(),
                                    index: i,
                                },
                            );
                            html! {

                                    <NodeComponent
                                      file={ ctx.props().file.clone() }
                                      hash={ h.clone() }
                                      cursor={ ctx.props().cursor.clone() }
                                      onselect={ ctx.props().onselect.clone() }
                                      path={ child_path }
                                      updatemodel={ ctx.props().updatemodel.clone() }
                                      validators={ validators }
                                    />

                            }
                        });
                        html! {

                                    { field_name }

{ for values }

                        }
                    })
                    .collect()
            } else {
                node.children
                    .iter()
                    .flat_map(|(field_name, hashes)| {
                        let field_schema = kind.and_then(|k| k.get_field(field_name));
                        let validators = field_schema
                            .map(|v| v.validators.clone())
                            .unwrap_or_default();
                        let path = path.clone();
                        hashes.iter().enumerate().map(move |(i, h)| {
                            let child_path = append(
                                &path,
                                Selector {
                                    field: field_name.clone(),
                                    index: i,
                                },
                            );
                            let onclick = {
                                let onselect = props.onselect.clone();
                                let child_path = child_path.clone();
                                ctx.link().callback(move |e: MouseEvent| {
                                    log::info!("click");
                                    e.stop_propagation();
                                    onselect.emit(child_path.clone());
                                    NodeMsg::Noop
                                })
                            };
                            // TODO: Sticky field headers.
                            html! {

                                        { field_name }

                                        { ":" }

                                    <NodeComponent
                                      file={ ctx.props().file.clone() }
                                      hash={ h.clone() }
                                      cursor={ ctx.props().cursor.clone() }
                                      onselect={ ctx.props().onselect.clone() }
                                      path={ child_path }
                                      updatemodel={ ctx.props().updatemodel.clone() }
                                      validators={ validators }
                                    />

                            }
                        })
                    })
                    .collect()
            };
            let content = html! {

                    { for children }

            };
            */
            let header = html! {

                        { node.kind.clone() }

                        { ctx.props().hash.clone().unwrap_or("-".to_string()) }

            };
            let footer = if selected {
                let entries: Vec<Entry> = kind
                    .map(|k| {
                        let KindValue::Struct { fields, .. } = k.value;
                        fields
                            .iter()
                            .map(|f| Entry {
                                label: f.name.to_string(),
                                description: "".to_string(),
                                action: Msg::AddField(path.to_vec(), f.name.to_string()),
                                valid_classes: FIELD_CLASSES
                                    .iter()
                                    .map(|v| v.to_string())
                                    .collect(),
                            })
                            .collect()
                    })
                    .unwrap_or_default();
                // Make it look like an actual field.
                html! {

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

                }
            } else {
                html! {}
            };
            html! {
              <>
                { header }
                { content }
                { footer }
              </>
            }
        };
        let onselect = ctx.props().onselect.clone();
        let onclick = {
            let path = path.clone();
            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 {
        log::debug!("changed");
        true
    }
}

impl NodeComponent {
    fn focus_input(&self) {
        let input_node = self.input_node_ref.clone();
        input_node
            .cast::<HtmlInputElement>()
            .unwrap()
            .focus()
            .unwrap();
    }
}
<|endoftext|>


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

use std::iter::once;

use proc_macro::TokenStream;
use proc_macro2::{Group, Span, TokenStream as TokenStream2, TokenTree};
use quote::quote;
use syn::{
    parse, parse_str, punctuated::Punctuated, token::Comma, Attribute, Block, FnArg, Ident,
    ImplItem, ImplItemMethod, Item, Lit, Meta, MetaList, NestedMeta, Pat, PatIdent, PatType, Path,
    PathSegment, Receiver, ReturnType, Stmt, Token, Type, TypePath,
};

/// Add guard for mutating actions when the pool is in maintenance mode.
///
/// This method adds a statement that returns an error if the pool is set
/// to limit available actions.
fn add_method_guards(method: &mut ImplItemMethod, level: Ident) {
    let stmt = if let ReturnType::Type(_, ty) = &method.sig.output {
        if let Type::Path(TypePath {
            path: Path { segments, .. },
            ..
        }) = &**ty
        {
            if let Some(PathSegment { ident, .. }) = segments.iter().last() {
                if &ident.to_string() == "StratisResult" {
                    parse::<Stmt>(TokenStream::from(quote! {
                        if self.action_avail >= crate::engine::types::ActionAvailability::#level {
                            return Err(crate::stratis::StratisError::Msg(format!(
                                "Pool is in state {:?} where mutable actions cannot be performed until the issue is resolved manually",
                                self.action_avail
                            )));
                        }
                    })).expect("This block should be a valid statement")
                } else {
                    panic!("The only return type currently supported for mutable actions is StratisResult<_>; found return type {} for method {}", ident.to_string(), method.sig.ident);
                }
            } else {
                unreachable!();
            }
        } else {
            panic!(
                "Could not check return type for method {}",
                method.sig.ident
            );
        }
    } else {
        panic!(
            "Mutable action methods must return a value; method {} has not return type",
            method.sig.ident
        );
    };
    let stmts = once(stmt)
        .chain(method.block.stmts.iter().cloned())
        .collect();
    method.block.stmts = stmts;
}

/// Process the arguments in the argument list. There is special handling for
/// receivers. Typed `self` parameters are not currently supported in this macro
/// but support can be added if needed.
fn process_arguments(fn_arg: &FnArg) -> (Ident, PatType) {
    match fn_arg {
        FnArg::Receiver(Receiver {
            reference,
            mutability,
            ..
        }) => {
            let reference = reference.as_ref().map(|(r, lt)| quote! { #r #lt });

            let mutability = mutability.map(|m| quote! { #m });

            let self_ident = Ident::new("self", Span::call_site());
            let pool_ident = Ident::new("pool", Span::call_site());

            (
                self_ident,
                PatType {
                    attrs: Vec::new(),
                    pat: Box::new(Pat::from(PatIdent {
                        attrs: Vec::new(),
                        by_ref: None,
                        mutability: None,
                        ident: pool_ident,
                        subpat: None,
                    })),
                    colon_token: Token![:](Span::call_site()),
                    ty: Box::new(
                        parse::<Type>(TokenStream::from(quote! {
                            #reference #mutability crate::engine::strat_engine::pool::StratPool
                        }))
                        .expect("Valid type"),
                    ),
                },
            )
        }
        FnArg::Typed(typed) => {
            let ident = if let Pat::Ident(PatIdent { ref ident, .. }) = *typed.pat {
                if *ident == "self" {
                    panic!("Typed self parameters are not currently supported");
                } else {
                    ident.clone()
                }
            } else {
                panic!("Unrecognized argument format");
            };

            (ident, typed.clone())
        }
    }
}

/// Replace all uses of the `self` keyword with the ident `pool`. This is necessary
/// for wrapped methods as they do not take a `self` reference.
fn process_token_stream(token: TokenTree) -> TokenTree {
    if let TokenTree::Ident(ref i) = token {
        if *i == "self" {
            TokenTree::from(Ident::new("pool", Span::call_site()))
        } else {
            token
        }
    } else if let TokenTree::Group(grp) = token {
        let delimiter = grp.delimiter();
        let tstream = grp
            .stream()
            .into_iter()
            .map(process_token_stream)
            .collect::<TokenStream2>();
        TokenTree::Group(Group::new(delimiter, tstream))
    } else {
        token
    }
}

/// Take the body of the method and wrap it in an inner method, replacing the
/// body with a check for an error that limits available actions.
fn wrap_method(f: &mut ImplItemMethod) {
    let wrapped_ident = Ident::new(
        format!("{}_wrapped", f.sig.ident.clone()).as_str(),
        Span::call_site(),
    );
    let mut wrapped_sig = f.sig.clone();
    wrapped_sig.ident = wrapped_ident.clone();

    let (args, arg_idents) = f.sig.inputs.iter().map(process_arguments).fold(
        (Vec::new(), Vec::new()),
        |(mut args, mut arg_idents), (ident, arg)| {
            args.push(arg);
            arg_idents.push(ident);
            (args, arg_idents)
        },
    );
    let stmts = f.block.stmts.drain(..).collect::<Vec<_>>();

    wrapped_sig.inputs = args
        .into_iter()
        .map(FnArg::Typed)
        .collect::<Punctuated<FnArg, Comma>>();

    let method_body_tokens = quote! {
        #( #stmts )*
    }
    .into_iter()
    .map(process_token_stream)
    .collect::<TokenStream2>();

    let stmt = parse::<Stmt>(TokenStream::from(quote! {
        #wrapped_sig {
            #method_body_tokens
        }
    }))
    .expect("Could not parse generated method as a statement");

    let tokens = quote! { {
        #stmt

        match #wrapped_ident(#( #arg_idents),*) {
            Ok(ret) => Ok(ret),
            Err(e) => {
                if let Some(state) = e.error_to_available_actions() {
                    self.action_avail = state;
                }
                Err(e)
            }
        }
    } };
    f.block = parse::<Block>(TokenStream::from(tokens))
        .expect("Could not parse generated method body as a block");
}

/// Get the pool available action state level at which a pool operation ceases to be
/// accepted.
fn get_attr_level(attrs: &mut Vec<Attribute>) -> Option<Ident> {
    let mut return_value = None;
    let mut index = None;
    for (i, attr) in attrs.iter().enumerate() {
        if let Meta::List(MetaList {
            ref path,
            ref nested,
            ..
        }) = attr
            .parse_meta()
            .unwrap_or_else(|_| panic!("Attribute {:?} cannot be parsed", attr))
        {
            if path
                == &parse_str("pool_mutating_action").expect("pool_mutating_action is valid path")
            {
                for nested_meta in nested.iter() {
                    if let NestedMeta::Lit(Lit::Str(litstr)) = nested_meta {
                        index = Some(i);
                        return_value =
                            Some(parse_str::<Ident>(&litstr.value()).unwrap_or_else(|_| {
                                panic!("{} is not a valid identifier", litstr.value())
                            }));
                    } else {
                        panic!("pool_mutating_action attribute must be in form #[pool_mutating_action(\"REJECTION LEVEL\")]");
                    }
                }
            }
        }
    }
    if let Some(i) = index {
        attrs.remove(i);
    }
    return_value
}

/// Determine whether a method has the given attribute.
fn has_attribute(attrs: &mut Vec<Attribute>, attribute: &str) -> bool {
    let mut return_value = false;
    let mut index = None;
    for (i, attr) in attrs.iter().enumerate() {
        if let Meta::Path(path) = attr
            .parse_meta()
            .unwrap_or_else(|_| panic!("Attribute {:?} cannot be parsed", attr))
        {
            if path == parse_str(attribute).expect("pool_mutating_action is valid path") {
                index = Some(i);
                return_value = true;
            }
        }
    }
    if let Some(i) = index {
        attrs.remove(i);
    }
    return_value
}

/// Determine whether a method should be marked as needing to handle failed rollback
/// based on the attributes.
///
/// The attribute that will cause a method to be marked as potentially causing a failed
/// rollback is `#[pool_rollback]`.
fn performs_rollback(attrs: &mut Vec<Attribute>) -> bool {
    has_attribute(attrs, "pool_rollback")
}

/// Process impl item that was provided to the attribute procedural macro.
fn process_item(mut item: Item) -> Item {
    let i = match item {
        Item::Impl(ref mut i) => i,
        _ => panic!("This macro can only be applied to impl items"),
    };

    for impl_item in i.items.iter_mut() {
        if let ImplItem::Method(ref mut f) = impl_item {
            if let Some(level) = get_attr_level(&mut f.attrs) {
                add_method_guards(f, level);
            }

            if performs_rollback(&mut f.attrs) {
                wrap_method(f);
            }
        }
    }

    item
}

/// This macro is specifically targeted to remove boilerplate code in the StratPool
/// implementations. It provides two facilities:
/// * checking if the error returned should cause the pool to refuse mutating actions.
/// * returning an error if the method called would cause a mutating action to occur if
/// the pool cannot accept mutating actions.
///
/// This macro should be applied to `impl` items only.
#[proc_macro_attribute]
pub fn strat_pool_impl_gen(_: TokenStream, item: TokenStream) -> TokenStream {
    let item = process_item(parse::<Item>(item).expect("Could not parse input as item"));
    TokenStream::from(quote! {
        #item
    })
}
<commit_msg>stratisd_proc_macros: remove superfluous to_string() call<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::iter::once;

use proc_macro::TokenStream;
use proc_macro2::{Group, Span, TokenStream as TokenStream2, TokenTree};
use quote::quote;
use syn::{
    parse, parse_str, punctuated::Punctuated, token::Comma, Attribute, Block, FnArg, Ident,
    ImplItem, ImplItemMethod, Item, Lit, Meta, MetaList, NestedMeta, Pat, PatIdent, PatType, Path,
    PathSegment, Receiver, ReturnType, Stmt, Token, Type, TypePath,
};

/// Add guard for mutating actions when the pool is in maintenance mode.
///
/// This method adds a statement that returns an error if the pool is set
/// to limit available actions.
fn add_method_guards(method: &mut ImplItemMethod, level: Ident) {
    let stmt = if let ReturnType::Type(_, ty) = &method.sig.output {
        if let Type::Path(TypePath {
            path: Path { segments, .. },
            ..
        }) = &**ty
        {
            if let Some(PathSegment { ident, .. }) = segments.iter().last() {
                if &ident.to_string() == "StratisResult" {
                    parse::<Stmt>(TokenStream::from(quote! {
                        if self.action_avail >= crate::engine::types::ActionAvailability::#level {
                            return Err(crate::stratis::StratisError::Msg(format!(
                                "Pool is in state {:?} where mutable actions cannot be performed until the issue is resolved manually",
                                self.action_avail
                            )));
                        }
                    })).expect("This block should be a valid statement")
                } else {
                    panic!("The only return type currently supported for mutable actions is StratisResult<_>; found return type {} for method {}", ident, method.sig.ident);
                }
            } else {
                unreachable!();
            }
        } else {
            panic!(
                "Could not check return type for method {}",
                method.sig.ident
            );
        }
    } else {
        panic!(
            "Mutable action methods must return a value; method {} has not return type",
            method.sig.ident
        );
    };
    let stmts = once(stmt)
        .chain(method.block.stmts.iter().cloned())
        .collect();
    method.block.stmts = stmts;
}

/// Process the arguments in the argument list. There is special handling for
/// receivers. Typed `self` parameters are not currently supported in this macro
/// but support can be added if needed.
fn process_arguments(fn_arg: &FnArg) -> (Ident, PatType) {
    match fn_arg {
        FnArg::Receiver(Receiver {
            reference,
            mutability,
            ..
        }) => {
            let reference = reference.as_ref().map(|(r, lt)| quote! { #r #lt });

            let mutability = mutability.map(|m| quote! { #m });

            let self_ident = Ident::new("self", Span::call_site());
            let pool_ident = Ident::new("pool", Span::call_site());

            (
                self_ident,
                PatType {
                    attrs: Vec::new(),
                    pat: Box::new(Pat::from(PatIdent {
                        attrs: Vec::new(),
                        by_ref: None,
                        mutability: None,
                        ident: pool_ident,
                        subpat: None,
                    })),
                    colon_token: Token![:](Span::call_site()),
                    ty: Box::new(
                        parse::<Type>(TokenStream::from(quote! {
                            #reference #mutability crate::engine::strat_engine::pool::StratPool
                        }))
                        .expect("Valid type"),
                    ),
                },
            )
        }
        FnArg::Typed(typed) => {
            let ident = if let Pat::Ident(PatIdent { ref ident, .. }) = *typed.pat {
                if *ident == "self" {
                    panic!("Typed self parameters are not currently supported");
                } else {
                    ident.clone()
                }
            } else {
                panic!("Unrecognized argument format");
            };

            (ident, typed.clone())
        }
    }
}

/// Replace all uses of the `self` keyword with the ident `pool`. This is necessary
/// for wrapped methods as they do not take a `self` reference.
fn process_token_stream(token: TokenTree) -> TokenTree {
    if let TokenTree::Ident(ref i) = token {
        if *i == "self" {
            TokenTree::from(Ident::new("pool", Span::call_site()))
        } else {
            token
        }
    } else if let TokenTree::Group(grp) = token {
        let delimiter = grp.delimiter();
        let tstream = grp
            .stream()
            .into_iter()
            .map(process_token_stream)
            .collect::<TokenStream2>();
        TokenTree::Group(Group::new(delimiter, tstream))
    } else {
        token
    }
}

/// Take the body of the method and wrap it in an inner method, replacing the
/// body with a check for an error that limits available actions.
fn wrap_method(f: &mut ImplItemMethod) {
    let wrapped_ident = Ident::new(
        format!("{}_wrapped", f.sig.ident.clone()).as_str(),
        Span::call_site(),
    );
    let mut wrapped_sig = f.sig.clone();
    wrapped_sig.ident = wrapped_ident.clone();

    let (args, arg_idents) = f.sig.inputs.iter().map(process_arguments).fold(
        (Vec::new(), Vec::new()),
        |(mut args, mut arg_idents), (ident, arg)| {
            args.push(arg);
            arg_idents.push(ident);
            (args, arg_idents)
        },
    );
    let stmts = f.block.stmts.drain(..).collect::<Vec<_>>();

    wrapped_sig.inputs = args
        .into_iter()
        .map(FnArg::Typed)
        .collect::<Punctuated<FnArg, Comma>>();

    let method_body_tokens = quote! {
        #( #stmts )*
    }
    .into_iter()
    .map(process_token_stream)
    .collect::<TokenStream2>();

    let stmt = parse::<Stmt>(TokenStream::from(quote! {
        #wrapped_sig {
            #method_body_tokens
        }
    }))
    .expect("Could not parse generated method as a statement");

    let tokens = quote! { {
        #stmt

        match #wrapped_ident(#( #arg_idents),*) {
            Ok(ret) => Ok(ret),
            Err(e) => {
                if let Some(state) = e.error_to_available_actions() {
                    self.action_avail = state;
                }
                Err(e)
            }
        }
    } };
    f.block = parse::<Block>(TokenStream::from(tokens))
        .expect("Could not parse generated method body as a block");
}

/// Get the pool available action state level at which a pool operation ceases to be
/// accepted.
fn get_attr_level(attrs: &mut Vec<Attribute>) -> Option<Ident> {
    let mut return_value = None;
    let mut index = None;
    for (i, attr) in attrs.iter().enumerate() {
        if let Meta::List(MetaList {
            ref path,
            ref nested,
            ..
        }) = attr
            .parse_meta()
            .unwrap_or_else(|_| panic!("Attribute {:?} cannot be parsed", attr))
        {
            if path
                == &parse_str("pool_mutating_action").expect("pool_mutating_action is valid path")
            {
                for nested_meta in nested.iter() {
                    if let NestedMeta::Lit(Lit::Str(litstr)) = nested_meta {
                        index = Some(i);
                        return_value =
                            Some(parse_str::<Ident>(&litstr.value()).unwrap_or_else(|_| {
                                panic!("{} is not a valid identifier", litstr.value())
                            }));
                    } else {
                        panic!("pool_mutating_action attribute must be in form #[pool_mutating_action(\"REJECTION LEVEL\")]");
                    }
                }
            }
        }
    }
    if let Some(i) = index {
        attrs.remove(i);
    }
    return_value
}

/// Determine whether a method has the given attribute.
fn has_attribute(attrs: &mut Vec<Attribute>, attribute: &str) -> bool {
    let mut return_value = false;
    let mut index = None;
    for (i, attr) in attrs.iter().enumerate() {
        if let Meta::Path(path) = attr
            .parse_meta()
            .unwrap_or_else(|_| panic!("Attribute {:?} cannot be parsed", attr))
        {
            if path == parse_str(attribute).expect("pool_mutating_action is valid path") {
                index = Some(i);
                return_value = true;
            }
        }
    }
    if let Some(i) = index {
        attrs.remove(i);
    }
    return_value
}

/// Determine whether a method should be marked as needing to handle failed rollback
/// based on the attributes.
///
/// The attribute that will cause a method to be marked as potentially causing a failed
/// rollback is `#[pool_rollback]`.
fn performs_rollback(attrs: &mut Vec<Attribute>) -> bool {
    has_attribute(attrs, "pool_rollback")
}

/// Process impl item that was provided to the attribute procedural macro.
fn process_item(mut item: Item) -> Item {
    let i = match item {
        Item::Impl(ref mut i) => i,
        _ => panic!("This macro can only be applied to impl items"),
    };

    for impl_item in i.items.iter_mut() {
        if let ImplItem::Method(ref mut f) = impl_item {
            if let Some(level) = get_attr_level(&mut f.attrs) {
                add_method_guards(f, level);
            }

            if performs_rollback(&mut f.attrs) {
                wrap_method(f);
            }
        }
    }

    item
}

/// This macro is specifically targeted to remove boilerplate code in the StratPool
/// implementations. It provides two facilities:
/// * checking if the error returned should cause the pool to refuse mutating actions.
/// * returning an error if the method called would cause a mutating action to occur if
/// the pool cannot accept mutating actions.
///
/// This macro should be applied to `impl` items only.
#[proc_macro_attribute]
pub fn strat_pool_impl_gen(_: TokenStream, item: TokenStream) -> TokenStream {
    let item = process_item(parse::<Item>(item).expect("Could not parse input as item"));
    TokenStream::from(quote! {
        #item
    })
}
<|endoftext|>


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

<%namespace name="helpers" file="/helpers.mako.rs" />

// TODO: other background-* properties
<%helpers:shorthand name="background"
                    sub_properties="background-color background-position-x background-position-y background-repeat
                                    background-attachment background-image background-size background-origin
                                    background-clip"
                    spec="https://drafts.csswg.org/css-backgrounds/#the-background">
    use properties::longhands::{background_position_x, background_position_y, background_repeat};
    use properties::longhands::{background_attachment, background_image, background_size, background_origin};
    use properties::longhands::background_clip;
    use properties::longhands::background_clip::single_value::computed_value::T as Clip;
    use properties::longhands::background_origin::single_value::computed_value::T as Origin;
    use values::specified::{CSSColor, Position, PositionComponent};
    use parser::Parse;

    impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue {
        fn from(origin: background_origin::single_value::SpecifiedValue) ->
            background_clip::single_value::SpecifiedValue {
            match origin {
                background_origin::single_value::SpecifiedValue::content_box =>
                    background_clip::single_value::SpecifiedValue::content_box,
                background_origin::single_value::SpecifiedValue::padding_box =>
                    background_clip::single_value::SpecifiedValue::padding_box,
                background_origin::single_value::SpecifiedValue::border_box =>
                    background_clip::single_value::SpecifiedValue::border_box,
            }
        }
    }

    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
        let mut background_color = None;

        % for name in "image position_x position_y repeat size attachment origin clip".split():
            let mut background_${name} = background_${name}::SpecifiedValue(Vec::new());
        % endfor
        try!(input.parse_comma_separated(|input| {
            % for name in "image position repeat size attachment origin clip".split():
                let mut ${name} = None;
            % endfor
            loop {
                if let Ok(value) = input.try(|i| CSSColor::parse(context, i)) {
                    if background_color.is_none() {
                        background_color = Some(value);
                        continue
                    } else {
                        // color can only be the last element
                        return Err(())
                    }
                }
                if position.is_none() {
                    if let Ok(value) = input.try(|input| Position::parse(context, input)) {
                        position = Some(value);

                        // Parse background size, if applicable.
                        size = input.try(|input| {
                            try!(input.expect_delim('/'));
                            background_size::single_value::parse(context, input)
                        }).ok();

                        continue
                    }
                }
                % for name in "image repeat attachment origin clip".split():
                    if ${name}.is_none() {
                        if let Ok(value) = input.try(|input| background_${name}::single_value
                                                                               ::parse(context, input)) {
                            ${name} = Some(value);
                            continue
                        }
                    }
                % endfor
                break
            }
            if clip.is_none() {
                if let Some(origin) = origin {
                    clip = Some(background_clip::single_value::SpecifiedValue::from(origin));
                }
            }
            let mut any = false;
            % for name in "image position repeat size attachment origin clip".split():
                any = any || ${name}.is_some();
            % endfor
            any = any || background_color.is_some();
            if any {
                if let Some(position) = position {
                    background_position_x.0.push(position.horizontal);
                    background_position_y.0.push(position.vertical);
                } else {
                    background_position_x.0.push(PositionComponent::zero());
                    background_position_y.0.push(PositionComponent::zero());
                }
                % for name in "image repeat size attachment origin clip".split():
                    if let Some(bg_${name}) = ${name} {
                        background_${name}.0.push(bg_${name});
                    } else {
                        background_${name}.0.push(background_${name}::single_value
                                                                    ::get_initial_specified_value());
                    }
                % endfor
                Ok(())
            } else {
                Err(())
            }
        }));

        Ok(expanded! {
             background_color: background_color.unwrap_or(CSSColor::transparent()),
             background_image: background_image,
             background_position_x: background_position_x,
             background_position_y: background_position_y,
             background_repeat: background_repeat,
             background_attachment: background_attachment,
             background_size: background_size,
             background_origin: background_origin,
             background_clip: background_clip,
         })
    }

    impl<'a> ToCss for LonghandsToSerialize<'a>  {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            let len = self.background_image.0.len();
            // There should be at least one declared value
            if len == 0 {
                return Ok(());
            }

            // If a value list length is differs then we don't do a shorthand serialization.
            // The exceptions to this is color which appears once only and is serialized
            // with the last item.
            % for name in "image position_x position_y size repeat origin clip attachment".split():
                if len != self.background_${name}.0.len() {
                    return Ok(());
                }
            % endfor

            for i in 0..len {
                % for name in "image position_x position_y repeat size attachment origin clip".split():
                    let ${name} = &self.background_${name}.0[i];
                % endfor

                if i != 0 {
                    try!(write!(dest, ", "));
                }

                if i == len - 1 {
                    try!(self.background_color.to_css(dest));
                    try!(write!(dest, " "));
                }

                try!(image.to_css(dest));
                % for name in "repeat attachment".split():
                    try!(write!(dest, " "));
                    try!(${name}.to_css(dest));
                % endfor

                try!(write!(dest, " "));
                Position {
                    horizontal: position_x.clone(),
                    vertical: position_y.clone()
                }.to_css(dest)?;

                if *size != background_size::single_value::get_initial_specified_value() {
                    try!(write!(dest, " / "));
                    try!(size.to_css(dest));
                }

                if *origin != Origin::padding_box || *clip != Clip::border_box {
                    try!(write!(dest, " "));
                    try!(origin.to_css(dest));
                    if *clip != From::from(*origin) {
                        try!(write!(dest, " "));
                        try!(clip.to_css(dest));
                    }
                }
            }

            Ok(())
        }
    }
</%helpers:shorthand>

<%helpers:shorthand name="background-position"
                    sub_properties="background-position-x background-position-y"
                    spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
    use properties::longhands::{background_position_x, background_position_y};
    use values::specified::AllowQuirks;
    use values::specified::position::Position;

    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
        let mut position_x = background_position_x::SpecifiedValue(Vec::new());
        let mut position_y = background_position_y::SpecifiedValue(Vec::new());
        let mut any = false;

        input.parse_comma_separated(|input| {
            let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
            position_x.0.push(value.horizontal);
            position_y.0.push(value.vertical);
            any = true;
            Ok(())
        })?;
        if !any {
            return Err(());
        }

        Ok(expanded! {
            background_position_x: position_x,
            background_position_y: position_y,
        })
    }

    impl<'a> ToCss for LonghandsToSerialize<'a>  {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            let len = self.background_position_x.0.len();
            if len == 0 || len != self.background_position_y.0.len() {
                return Ok(());
            }
            for i in 0..len {
                Position {
                    horizontal: self.background_position_x.0[i].clone(),
                    vertical: self.background_position_y.0[i].clone()
                }.to_css(dest)?;

                if i < len - 1 {
                    dest.write_str(", ")?;
                }
            }
            Ok(())
        }
    }
</%helpers:shorthand>
<commit_msg>Rollup merge of #17019 - upsuper:background-shorthand, r=nox<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

// TODO: other background-* properties
<%helpers:shorthand name="background"
                    sub_properties="background-color background-position-x background-position-y background-repeat
                                    background-attachment background-image background-size background-origin
                                    background-clip"
                    spec="https://drafts.csswg.org/css-backgrounds/#the-background">
    use properties::longhands::{background_position_x, background_position_y, background_repeat};
    use properties::longhands::{background_attachment, background_image, background_size, background_origin};
    use properties::longhands::background_clip;
    use properties::longhands::background_clip::single_value::computed_value::T as Clip;
    use properties::longhands::background_origin::single_value::computed_value::T as Origin;
    use values::specified::{CSSColor, Position, PositionComponent};
    use parser::Parse;

    impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue {
        fn from(origin: background_origin::single_value::SpecifiedValue) ->
            background_clip::single_value::SpecifiedValue {
            match origin {
                background_origin::single_value::SpecifiedValue::content_box =>
                    background_clip::single_value::SpecifiedValue::content_box,
                background_origin::single_value::SpecifiedValue::padding_box =>
                    background_clip::single_value::SpecifiedValue::padding_box,
                background_origin::single_value::SpecifiedValue::border_box =>
                    background_clip::single_value::SpecifiedValue::border_box,
            }
        }
    }

    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
        let mut background_color = None;

        % for name in "image position_x position_y repeat size attachment origin clip".split():
            let mut background_${name} = background_${name}::SpecifiedValue(Vec::new());
        % endfor
        try!(input.parse_comma_separated(|input| {
            // background-color can only be in the last element, so if it
            // is parsed anywhere before, the value is invalid.
            if background_color.is_some() {
                return Err(());
            }

            % for name in "image position repeat size attachment origin clip".split():
                let mut ${name} = None;
            % endfor
            loop {
                if background_color.is_none() {
                    if let Ok(value) = input.try(|i| CSSColor::parse(context, i)) {
                        background_color = Some(value);
                        continue
                    }
                }
                if position.is_none() {
                    if let Ok(value) = input.try(|input| Position::parse(context, input)) {
                        position = Some(value);

                        // Parse background size, if applicable.
                        size = input.try(|input| {
                            try!(input.expect_delim('/'));
                            background_size::single_value::parse(context, input)
                        }).ok();

                        continue
                    }
                }
                % for name in "image repeat attachment origin clip".split():
                    if ${name}.is_none() {
                        if let Ok(value) = input.try(|input| background_${name}::single_value
                                                                               ::parse(context, input)) {
                            % if name == "clip" and product == "gecko":
                            // "text" value of background-clip should not be part of background
                            // shorthand per current spec and impls.
                            if value == background_clip::single_value::SpecifiedValue::text {
                                return Err(());
                            }
                            % endif
                            ${name} = Some(value);
                            continue
                        }
                    }
                % endfor
                break
            }
            if clip.is_none() {
                if let Some(origin) = origin {
                    clip = Some(background_clip::single_value::SpecifiedValue::from(origin));
                }
            }
            let mut any = false;
            % for name in "image position repeat size attachment origin clip".split():
                any = any || ${name}.is_some();
            % endfor
            any = any || background_color.is_some();
            if any {
                if let Some(position) = position {
                    background_position_x.0.push(position.horizontal);
                    background_position_y.0.push(position.vertical);
                } else {
                    background_position_x.0.push(PositionComponent::zero());
                    background_position_y.0.push(PositionComponent::zero());
                }
                % for name in "image repeat size attachment origin clip".split():
                    if let Some(bg_${name}) = ${name} {
                        background_${name}.0.push(bg_${name});
                    } else {
                        background_${name}.0.push(background_${name}::single_value
                                                                    ::get_initial_specified_value());
                    }
                % endfor
                Ok(())
            } else {
                Err(())
            }
        }));

        Ok(expanded! {
             background_color: background_color.unwrap_or(CSSColor::transparent()),
             background_image: background_image,
             background_position_x: background_position_x,
             background_position_y: background_position_y,
             background_repeat: background_repeat,
             background_attachment: background_attachment,
             background_size: background_size,
             background_origin: background_origin,
             background_clip: background_clip,
         })
    }

    impl<'a> ToCss for LonghandsToSerialize<'a>  {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            let len = self.background_image.0.len();
            // There should be at least one declared value
            if len == 0 {
                return Ok(());
            }

            // If a value list length is differs then we don't do a shorthand serialization.
            // The exceptions to this is color which appears once only and is serialized
            // with the last item.
            % for name in "image position_x position_y size repeat origin clip attachment".split():
                if len != self.background_${name}.0.len() {
                    return Ok(());
                }
            % endfor

            for i in 0..len {
                % for name in "image position_x position_y repeat size attachment origin clip".split():
                    let ${name} = &self.background_${name}.0[i];
                % endfor

                if i != 0 {
                    try!(write!(dest, ", "));
                }

                if i == len - 1 {
                    try!(self.background_color.to_css(dest));
                    try!(write!(dest, " "));
                }

                try!(image.to_css(dest));
                % for name in "repeat attachment".split():
                    try!(write!(dest, " "));
                    try!(${name}.to_css(dest));
                % endfor

                try!(write!(dest, " "));
                Position {
                    horizontal: position_x.clone(),
                    vertical: position_y.clone()
                }.to_css(dest)?;

                if *size != background_size::single_value::get_initial_specified_value() {
                    try!(write!(dest, " / "));
                    try!(size.to_css(dest));
                }

                if *origin != Origin::padding_box || *clip != Clip::border_box {
                    try!(write!(dest, " "));
                    try!(origin.to_css(dest));
                    if *clip != From::from(*origin) {
                        try!(write!(dest, " "));
                        try!(clip.to_css(dest));
                    }
                }
            }

            Ok(())
        }
    }
</%helpers:shorthand>

<%helpers:shorthand name="background-position"
                    sub_properties="background-position-x background-position-y"
                    spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position">
    use properties::longhands::{background_position_x, background_position_y};
    use values::specified::AllowQuirks;
    use values::specified::position::Position;

    pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
        let mut position_x = background_position_x::SpecifiedValue(Vec::new());
        let mut position_y = background_position_y::SpecifiedValue(Vec::new());
        let mut any = false;

        input.parse_comma_separated(|input| {
            let value = Position::parse_quirky(context, input, AllowQuirks::Yes)?;
            position_x.0.push(value.horizontal);
            position_y.0.push(value.vertical);
            any = true;
            Ok(())
        })?;
        if !any {
            return Err(());
        }

        Ok(expanded! {
            background_position_x: position_x,
            background_position_y: position_y,
        })
    }

    impl<'a> ToCss for LonghandsToSerialize<'a>  {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            let len = self.background_position_x.0.len();
            if len == 0 || len != self.background_position_y.0.len() {
                return Ok(());
            }
            for i in 0..len {
                Position {
                    horizontal: self.background_position_x.0[i].clone(),
                    vertical: self.background_position_y.0[i].clone()
                }.to_css(dest)?;

                if i < len - 1 {
                    dest.write_str(", ")?;
                }
            }
            Ok(())
        }
    }
</%helpers:shorthand>
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};

use im::HashMap;
use itertools::Itertools;

use crate::{
    builtin_functions::BuiltinFunction,
    compiler::hir::{self, Expression},
    database::project_directory,
    discover::run::run_call,
    input::Input,
};

use super::{
    result::DiscoverResult,
    run::Discover,
    value::{Environment, Value},
};

pub fn run_builtin_function(
    db: &dyn Discover,
    input: Input,
    builtin_function: BuiltinFunction,
    arguments: Vec<hir::Id>,
    environment: Environment,
) -> DiscoverResult {
    log::info!(
        "run_builtin_function: {:?} {}",
        builtin_function,
        arguments.len()
    );
    // Handle builtin functions that don't need to resolve the arguments.
    match builtin_function {
        BuiltinFunction::IfElse => return if_else(db, input, arguments, environment),
        _ => {}
    }

    let arguments = arguments
        .iter()
        .map(|it| environment.get(it).unwrap())
        .collect::<DiscoverResult<_>>()?;
    match builtin_function {
        BuiltinFunction::Add => add(arguments),
        BuiltinFunction::Equals => equals(arguments),
        BuiltinFunction::GetArgumentCount => get_argument_count(db, input, arguments),
        BuiltinFunction::Panic => panic(arguments),
        BuiltinFunction::Print => print(arguments),
        BuiltinFunction::StructGet => struct_get(arguments),
        BuiltinFunction::StructGetKeys => struct_get_keys(arguments),
        BuiltinFunction::StructHasKey => struct_has_key(arguments),
        BuiltinFunction::TypeOf => type_of(arguments),
        BuiltinFunction::Use => use_(db, arguments),
        _ => panic!("Unhandled builtin function: {:?}", builtin_function),
    }
}

macro_rules! destructure {
    ($arguments:expr, $enum:pat, $body:block) => {{
        if let $enum = &$arguments[..] {
            $body
        } else {
            DiscoverResult::panic(format!("Invalid arguments").to_owned())
        }
    }};
}

fn add(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [Value::Int(a), Value::Int(b)], {
        Value::Int(a + b).into()
    })
}

fn equals(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [a, b], { Value::bool(a == b).into() })
}

fn get_argument_count(db: &dyn Discover, input: Input, arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [Value::Lambda(function)], {
        // TODO: support parameter counts > 2^64 on 128-bit systems and better
        let expression = match db.find_expression(input, function.id.to_owned()).unwrap() {
            Expression::Lambda(lambda) => lambda,
            _ => panic!("Lambda's function"),
        };
        Value::Int(expression.parameters.len() as u64).into()
    })
}

fn if_else(
    db: &dyn Discover,
    input: Input,
    arguments: Vec<hir::Id>,
    environment: Environment,
) -> DiscoverResult {
    if let [condition, then, else_] = &arguments[..] {
        let body_id = match environment.get(condition).unwrap()? {
            value if value == Value::bool(true) => then,
            value if value == Value::bool(false) => else_,
            value => {
                return DiscoverResult::panic(format!(
                    "Condition must be a boolean, but was {:?}.",
                    value
                ));
            }
        };

        run_call(db, input, body_id.to_owned(), vec![], environment)
    } else {
        DiscoverResult::panic(format!(
            "Builtin if/else called with wrong number of arguments: {}, expected: {}",
            arguments.len(),
            3
        ))
    }
}

fn panic(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [value], { DiscoverResult::Panic(value.clone()) })
}

fn print(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [value], {
        println!("{:?}", value);
        Value::nothing().into()
    })
}

fn struct_get(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [struct_, key], {
        let struct_ = expect_struct("builtinStructGet".to_owned(), struct_)?;
        struct_
            .get(key)
            .map(|value| value.clone().into())
            .unwrap_or_else(|| {
                DiscoverResult::panic(format!("Struct does not contain key {:?}", key))
            })
    })
}
fn struct_get_keys(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [struct_], {
        let struct_ = expect_struct("builtinStructGetKeys".to_owned(), struct_)?;
        Value::list(struct_.keys().cloned().collect()).into()
    })
}
fn struct_has_key(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [struct_, key], {
        let struct_ = expect_struct("builtinStructHasKey".to_owned(), struct_)?;
        Value::bool(struct_.contains_key(key)).into()
    })
}
fn expect_struct(function_name: String, value: &Value) -> DiscoverResult<&HashMap<Value, Value>> {
    match value {
        Value::Struct(struct_) => struct_.into(),
        _ => {
            return DiscoverResult::panic(format!(
                "`{}` expected a struct as its first parameter, but received: {:?}",
                function_name, value
            ))
        }
    }
}

fn type_of(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [value], {
        match value {
            Value::Int(_) => Value::Symbol("Int".to_owned()).into(),
            Value::Text(_) => Value::Symbol("Text".to_owned()).into(),
            Value::Symbol(_) => Value::Symbol("Symbol".to_owned()).into(),
            Value::Struct(_) => Value::Symbol("Struct".to_owned()).into(),
            Value::Lambda(_) => Value::Symbol("Function".to_owned()).into(),
        }
    })
}

fn use_(db: &dyn Discover, arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [current_path, target], {
        let current_path_string = match current_path {
            Value::Text(value) => value,
            it => {
                return DiscoverResult::panic(format!(
                    "`use` expected a text as its first parameter, but received: {:?}",
                    it
                ))
            }
        };
        // `current_path` is set by us and not users, hence we don't have to validate it that strictly.
        let mut current_path = PathBuf::new();
        current_path.push(".");
        for segment in current_path_string.split('/') {
            current_path.push(segment);
        }

        let target = match target {
            Value::Text(value) => value,
            it => {
                return DiscoverResult::panic(format!(
                    "`use` expected a text as its second parameter, but received: {:?}",
                    it
                ))
            }
        };
        let target = match UseTarget::parse(target) {
            Ok(target) => target,
            Err(error) => return DiscoverResult::panic(error),
        };

        if target.parent_navigations > current_path.components().count() - 1 {
            return DiscoverResult::panic("Too many parent navigations.".to_owned());
        }

        let project_dir = project_directory.lock().unwrap().clone().unwrap();
        let target_paths = target.resolve(&current_path);
        let target = match target_paths
            .iter()
            .filter(|it| project_dir.join(it).exists())
            .next()
        {
            Some(target) => target,
            None => {
                return DiscoverResult::panic(format!(
                    "Target doesn't exist. Checked the following path(s): {}",
                    target_paths
                        .iter()
                        .map(|it| it.to_str().unwrap())
                        .join(", ")
                ));
            }
        };

        let input = Input::File(project_dir.join(target).to_owned());
        let (hir, _) = db.hir(input.clone()).unwrap();
        let discover_result = db.run_all(input);

        let module = hir
            .identifiers
            .iter()
            .filter_map(|(id, key)| {
                let key = Value::Text(key.to_owned());
                let value = discover_result.get(id)?.to_owned().value()?;
                Some((key, value))
            })
            .collect::<HashMap<Value, Value>>();
        DiscoverResult::Value(Value::Struct(module))
    })
}
struct UseTarget {
    parent_navigations: usize,
    path: Vec<String>,
}
impl UseTarget {
    const PARENT_NAVIGATION_CHAR: char = '.';

    fn parse(target: &str) -> Result<Self, String> {
        let mut parent_navigations = 0;
        let mut target = target;
        while target.chars().next() == Some(UseTarget::PARENT_NAVIGATION_CHAR) {
            parent_navigations += 1;
            target = &target[UseTarget::PARENT_NAVIGATION_CHAR.len_utf8()..];
        }

        let mut path = vec![];
        loop {
            let mut chars = vec![];
            while let Some(c) = target.chars().next() {
                if c == UseTarget::PARENT_NAVIGATION_CHAR {
                    break;
                }
                chars.push(c);
                target = &target[c.len_utf8()..];
            }

            if target.is_empty() {
                path.push(chars.into_iter().join(""));
                break;
            }

            if chars.is_empty() {
                return Err("Target contains consecutive dots (`.`) in the path.".to_owned());
            }

            path.push(chars.into_iter().join(""));
        }
        Ok(UseTarget {
            parent_navigations,
            path,
        })
    }

    fn resolve(&self, current_path: &Path) -> Vec<PathBuf> {
        let mut path = current_path;
        for _ in 0..self.parent_navigations {
            path = path.parent().unwrap();
        }

        let mut path = path.to_path_buf();
        for part in &self.path {
            path.push(part);
        }

        let mut result = vec![];

        let mut subdirectory = path.clone();
        subdirectory.push(".candy");
        result.push(subdirectory);

        if path.components().count() > 1 {
            path.set_file_name(format!(
                "{}.candy",
                path.file_name().unwrap().to_str().unwrap()
            ));
            result.push(path);
        }
        result
    }
}
<commit_msg>change(compiler): propagate panics across builtinUse<commit_after>use std::path::{Path, PathBuf};

use im::HashMap;
use itertools::Itertools;

use crate::{
    builtin_functions::BuiltinFunction,
    compiler::hir::{self, Expression},
    database::project_directory,
    discover::run::run_call,
    input::Input,
};

use super::{
    result::DiscoverResult,
    run::Discover,
    value::{Environment, Value},
};

pub fn run_builtin_function(
    db: &dyn Discover,
    input: Input,
    builtin_function: BuiltinFunction,
    arguments: Vec<hir::Id>,
    environment: Environment,
) -> DiscoverResult {
    log::info!(
        "run_builtin_function: {:?} {}",
        builtin_function,
        arguments.len()
    );
    // Handle builtin functions that don't need to resolve the arguments.
    match builtin_function {
        BuiltinFunction::IfElse => return if_else(db, input, arguments, environment),
        _ => {}
    }

    let arguments = arguments
        .iter()
        .map(|it| environment.get(it).unwrap())
        .collect::<DiscoverResult<_>>()?;
    match builtin_function {
        BuiltinFunction::Add => add(arguments),
        BuiltinFunction::Equals => equals(arguments),
        BuiltinFunction::GetArgumentCount => get_argument_count(db, input, arguments),
        BuiltinFunction::Panic => panic(arguments),
        BuiltinFunction::Print => print(arguments),
        BuiltinFunction::StructGet => struct_get(arguments),
        BuiltinFunction::StructGetKeys => struct_get_keys(arguments),
        BuiltinFunction::StructHasKey => struct_has_key(arguments),
        BuiltinFunction::TypeOf => type_of(arguments),
        BuiltinFunction::Use => use_(db, arguments),
        _ => panic!("Unhandled builtin function: {:?}", builtin_function),
    }
}

macro_rules! destructure {
    ($arguments:expr, $enum:pat, $body:block) => {{
        if let $enum = &$arguments[..] {
            $body
        } else {
            DiscoverResult::panic(format!("Invalid arguments").to_owned())
        }
    }};
}

fn add(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [Value::Int(a), Value::Int(b)], {
        Value::Int(a + b).into()
    })
}

fn equals(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [a, b], { Value::bool(a == b).into() })
}

fn get_argument_count(db: &dyn Discover, input: Input, arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [Value::Lambda(function)], {
        // TODO: support parameter counts > 2^64 on 128-bit systems and better
        let expression = match db.find_expression(input, function.id.to_owned()).unwrap() {
            Expression::Lambda(lambda) => lambda,
            _ => panic!("Lambda's function"),
        };
        Value::Int(expression.parameters.len() as u64).into()
    })
}

fn if_else(
    db: &dyn Discover,
    input: Input,
    arguments: Vec<hir::Id>,
    environment: Environment,
) -> DiscoverResult {
    if let [condition, then, else_] = &arguments[..] {
        let body_id = match environment.get(condition).unwrap()? {
            value if value == Value::bool(true) => then,
            value if value == Value::bool(false) => else_,
            value => {
                return DiscoverResult::panic(format!(
                    "Condition must be a boolean, but was {:?}.",
                    value
                ));
            }
        };

        run_call(db, input, body_id.to_owned(), vec![], environment)
    } else {
        DiscoverResult::panic(format!(
            "Builtin if/else called with wrong number of arguments: {}, expected: {}",
            arguments.len(),
            3
        ))
    }
}

fn panic(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [value], { DiscoverResult::Panic(value.clone()) })
}

fn print(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [value], {
        println!("{:?}", value);
        Value::nothing().into()
    })
}

fn struct_get(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [struct_, key], {
        let struct_ = expect_struct("builtinStructGet".to_owned(), struct_)?;
        struct_
            .get(key)
            .map(|value| value.clone().into())
            .unwrap_or_else(|| {
                DiscoverResult::panic(format!("Struct does not contain key {:?}", key))
            })
    })
}
fn struct_get_keys(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [struct_], {
        let struct_ = expect_struct("builtinStructGetKeys".to_owned(), struct_)?;
        Value::list(struct_.keys().cloned().collect()).into()
    })
}
fn struct_has_key(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [struct_, key], {
        let struct_ = expect_struct("builtinStructHasKey".to_owned(), struct_)?;
        Value::bool(struct_.contains_key(key)).into()
    })
}
fn expect_struct(function_name: String, value: &Value) -> DiscoverResult<&HashMap<Value, Value>> {
    match value {
        Value::Struct(struct_) => struct_.into(),
        _ => {
            return DiscoverResult::panic(format!(
                "`{}` expected a struct as its first parameter, but received: {:?}",
                function_name, value
            ))
        }
    }
}

fn type_of(arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [value], {
        match value {
            Value::Int(_) => Value::Symbol("Int".to_owned()).into(),
            Value::Text(_) => Value::Symbol("Text".to_owned()).into(),
            Value::Symbol(_) => Value::Symbol("Symbol".to_owned()).into(),
            Value::Struct(_) => Value::Symbol("Struct".to_owned()).into(),
            Value::Lambda(_) => Value::Symbol("Function".to_owned()).into(),
        }
    })
}

fn use_(db: &dyn Discover, arguments: Vec<Value>) -> DiscoverResult {
    destructure!(arguments, [current_path, target], {
        let current_path_string = match current_path {
            Value::Text(value) => value,
            it => {
                return DiscoverResult::panic(format!(
                    "`use` expected a text as its first parameter, but received: {:?}",
                    it
                ))
            }
        };
        // `current_path` is set by us and not users, hence we don't have to validate it that strictly.
        let mut current_path = PathBuf::new();
        current_path.push(".");
        for segment in current_path_string.split('/') {
            current_path.push(segment);
        }

        let target = match target {
            Value::Text(value) => value,
            it => {
                return DiscoverResult::panic(format!(
                    "`use` expected a text as its second parameter, but received: {:?}",
                    it
                ))
            }
        };
        let target = match UseTarget::parse(target) {
            Ok(target) => target,
            Err(error) => return DiscoverResult::panic(error),
        };

        if target.parent_navigations > current_path.components().count() - 1 {
            return DiscoverResult::panic("Too many parent navigations.".to_owned());
        }

        let project_dir = project_directory.lock().unwrap().clone().unwrap();
        let target_paths = target.resolve(&current_path);
        let target = match target_paths
            .iter()
            .filter(|it| project_dir.join(it).exists())
            .next()
        {
            Some(target) => target,
            None => {
                return DiscoverResult::panic(format!(
                    "Target doesn't exist. Checked the following path(s): {}",
                    target_paths
                        .iter()
                        .map(|it| it.to_str().unwrap())
                        .join(", ")
                ));
            }
        };

        let input = Input::File(project_dir.join(target).to_owned());
        let (hir, _) = db.hir(input.clone()).unwrap();
        let discover_result = db.run_all(input);

        hir.identifiers
            .iter()
            .map(|(id, key)| {
                let key = Value::Text(key.to_owned());
                let value = match discover_result.get(id) {
                    Some(value) => value.to_owned()?,
                    None => return DiscoverResult::ErrorInHir,
                };
                DiscoverResult::Value((key, value))
            })
            .collect::<DiscoverResult<HashMap<Value, Value>>>()
            .map(|it| Value::Struct(it))
    })
}
struct UseTarget {
    parent_navigations: usize,
    path: Vec<String>,
}
impl UseTarget {
    const PARENT_NAVIGATION_CHAR: char = '.';

    fn parse(target: &str) -> Result<Self, String> {
        let mut parent_navigations = 0;
        let mut target = target;
        while target.chars().next() == Some(UseTarget::PARENT_NAVIGATION_CHAR) {
            parent_navigations += 1;
            target = &target[UseTarget::PARENT_NAVIGATION_CHAR.len_utf8()..];
        }

        let mut path = vec![];
        loop {
            let mut chars = vec![];
            while let Some(c) = target.chars().next() {
                if c == UseTarget::PARENT_NAVIGATION_CHAR {
                    break;
                }
                chars.push(c);
                target = &target[c.len_utf8()..];
            }

            if target.is_empty() {
                path.push(chars.into_iter().join(""));
                break;
            }

            if chars.is_empty() {
                return Err("Target contains consecutive dots (`.`) in the path.".to_owned());
            }

            path.push(chars.into_iter().join(""));
        }
        Ok(UseTarget {
            parent_navigations,
            path,
        })
    }

    fn resolve(&self, current_path: &Path) -> Vec<PathBuf> {
        let mut path = current_path;
        for _ in 0..self.parent_navigations {
            path = path.parent().unwrap();
        }

        let mut path = path.to_path_buf();
        for part in &self.path {
            path.push(part);
        }

        let mut result = vec![];

        let mut subdirectory = path.clone();
        subdirectory.push(".candy");
        result.push(subdirectory);

        if path.components().count() > 1 {
            path.set_file_name(format!(
                "{}.candy",
                path.file_name().unwrap().to_str().unwrap()
            ));
            result.push(path);
        }
        result
    }
}
<|endoftext|>