chore: add Node macro

This commit is contained in:
nathan 2022-12-03 21:12:45 +08:00
parent eb1659fd3e
commit 81dd353d12
12 changed files with 303 additions and 38 deletions

View File

@ -4,14 +4,18 @@
use crate::event_attrs::EventEnumAttrs; use crate::event_attrs::EventEnumAttrs;
use crate::node_attrs::NodeStructAttrs; use crate::node_attrs::NodeStructAttrs;
use crate::{is_recognizable_field, pb_attrs, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs}; use crate::{is_recognizable_field, pb_attrs, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs, NODE_TYPE};
use proc_macro2::Ident;
use syn::Meta::NameValue;
use syn::{self, punctuated::Punctuated}; use syn::{self, punctuated::Punctuated};
pub struct ASTContainer<'a> { pub struct ASTContainer<'a> {
/// The struct or enum name (without generics). /// The struct or enum name (without generics).
pub ident: syn::Ident, pub ident: syn::Ident,
pub node_type: Option<String>,
/// Attributes on the structure. /// Attributes on the structure.
pub attrs: PBAttrsContainer, pub pb_attrs: PBAttrsContainer,
/// The contents of the struct or enum. /// The contents of the struct or enum.
pub data: ASTData<'a>, pub data: ASTData<'a>,
} }
@ -40,7 +44,13 @@ impl<'a> ASTContainer<'a> {
}; };
let ident = ast.ident.clone(); let ident = ast.ident.clone();
let item = ASTContainer { ident, attrs, data }; let node_type = get_node_type(ast_result, &ident, &ast.attrs);
let item = ASTContainer {
ident,
pb_attrs: attrs,
node_type,
data,
};
Some(item) Some(item)
} }
} }
@ -182,7 +192,6 @@ impl<'a> ASTField<'a> {
} }
} }
#[allow(dead_code)]
pub fn name(&self) -> Option<syn::Ident> { pub fn name(&self) -> Option<syn::Ident> {
if let syn::Member::Named(ident) = &self.member { if let syn::Member::Named(ident) = &self.member {
Some(ident.clone()) Some(ident.clone())
@ -249,3 +258,21 @@ fn fields_from_ast<'a>(cx: &ASTResult, fields: &'a Punctuated<syn::Field, Token!
}) })
.collect() .collect()
} }
fn get_node_type(ast_result: &ASTResult, struct_name: &Ident, attrs: &[syn::Attribute]) -> Option<String> {
let mut node_type = None;
attrs
.iter()
.filter(|attr| attr.path.segments.iter().any(|s| s.ident == NODE_TYPE))
.for_each(|attr| {
if let Ok(NameValue(named_value)) = attr.parse_meta() {
if node_type.is_some() {
ast_result.error_spanned_by(struct_name, "Duplicate node type definition");
}
if let syn::Lit::Str(s) = named_value.lit {
node_type = Some(s.value());
}
}
});
node_type
}

View File

@ -1,14 +1,17 @@
use crate::{get_node_meta_items, get_pb_meta_items, parse_lit_into_expr_path, symbol::*, ASTAttr, ASTResult}; use crate::{get_node_meta_items, get_pb_meta_items, parse_lit_into_expr_path, symbol::*, ASTAttr, ASTResult};
use proc_macro2::{Group, Span, TokenStream, TokenTree}; use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use quote::ToTokens; use quote::ToTokens;
use syn::{ use syn::{
self, self,
parse::{self, Parse}, parse::{self, Parse},
LitStr,
Meta::{List, NameValue, Path}, Meta::{List, NameValue, Path},
NestedMeta::{Lit, Meta}, NestedMeta::{Lit, Meta},
}; };
pub struct NodeStructAttrs { pub struct NodeStructAttrs {
pub rename: Option<LitStr>,
pub is_children: bool,
node_index: Option<syn::LitInt>, node_index: Option<syn::LitInt>,
get_node_value_with: Option<syn::ExprPath>, get_node_value_with: Option<syn::ExprPath>,
set_node_value_with: Option<syn::ExprPath>, set_node_value_with: Option<syn::ExprPath>,
@ -18,14 +21,11 @@ impl NodeStructAttrs {
/// Extract out the `#[node(...)]` attributes from a struct field. /// Extract out the `#[node(...)]` attributes from a struct field.
pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self { pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self {
let mut node_index = ASTAttr::none(ast_result, NODE_INDEX); let mut node_index = ASTAttr::none(ast_result, NODE_INDEX);
let mut rename = ASTAttr::none(ast_result, NODE_RENAME);
let mut is_children = ASTAttr::none(ast_result, NODE_CHILDREN);
let mut get_node_value_with = ASTAttr::none(ast_result, GET_NODE_VALUE_WITH); let mut get_node_value_with = ASTAttr::none(ast_result, GET_NODE_VALUE_WITH);
let mut set_node_value_with = ASTAttr::none(ast_result, SET_NODE_VALUE_WITH); let mut set_node_value_with = ASTAttr::none(ast_result, SET_NODE_VALUE_WITH);
let ident = match &field.ident {
Some(ident) => ident.to_string(),
None => index.to_string(),
};
for meta_item in field for meta_item in field
.attrs .attrs
.iter() .iter()
@ -40,6 +40,18 @@ impl NodeStructAttrs {
} }
} }
// Parse '#[node(children)]'
Meta(Path(path)) if path == NODE_CHILDREN => {
eprintln!("😄 {:?}", path);
is_children.set(path, true);
}
// Parse '#[node(rename = x)]'
Meta(NameValue(m)) if m.path == NODE_RENAME => {
if let syn::Lit::Str(lit) = &m.lit {
rename.set(&m.path, lit.clone());
}
}
// Parse `#[node(get_node_value_with = "...")]` // Parse `#[node(get_node_value_with = "...")]`
Meta(NameValue(m)) if m.path == GET_NODE_VALUE_WITH => { Meta(NameValue(m)) if m.path == GET_NODE_VALUE_WITH => {
if let Ok(path) = parse_lit_into_expr_path(ast_result, GET_NODE_VALUE_WITH, &m.lit) { if let Ok(path) = parse_lit_into_expr_path(ast_result, GET_NODE_VALUE_WITH, &m.lit) {
@ -66,7 +78,9 @@ impl NodeStructAttrs {
} }
NodeStructAttrs { NodeStructAttrs {
rename: rename.get(),
node_index: node_index.get(), node_index: node_index.get(),
is_children: is_children.get().unwrap_or(false),
get_node_value_with: get_node_value_with.get(), get_node_value_with: get_node_value_with.get(),
set_node_value_with: set_node_value_with.get(), set_node_value_with: set_node_value_with.get(),
} }

View File

@ -114,7 +114,7 @@ impl<'c, T> ASTAttr<'c, T> {
} }
} }
fn set_if_none(&mut self, value: T) { pub(crate) fn set_if_none(&mut self, value: T) {
if self.value.is_none() { if self.value.is_none() {
self.value = Some(value); self.value = Some(value);
} }
@ -260,7 +260,7 @@ pub enum Default {
} }
pub fn is_recognizable_attribute(attr: &syn::Attribute) -> bool { pub fn is_recognizable_attribute(attr: &syn::Attribute) -> bool {
attr.path == PB_ATTRS || attr.path == EVENT || attr.path == NODE_ATTRS attr.path == PB_ATTRS || attr.path == EVENT || attr.path == NODE_ATTRS || attr.path == NODES_ATTRS
} }
pub fn get_pb_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> { pub fn get_pb_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
@ -286,17 +286,14 @@ pub fn get_pb_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<sy
pub fn get_node_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> { pub fn get_node_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
// Only handle the attribute that we have defined // Only handle the attribute that we have defined
if attr.path != NODE_ATTRS { if attr.path != NODE_ATTRS && attr.path != NODES_ATTRS {
return Ok(vec![]); return Ok(vec![]);
} }
// http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html
match attr.parse_meta() { match attr.parse_meta() {
Ok(List(meta)) => Ok(meta.nested.into_iter().collect()), Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
Ok(other) => { Ok(_) => Ok(vec![]),
cx.error_spanned_by(other, "expected #[node(...)]");
Err(())
}
Err(err) => { Err(err) => {
cx.error_spanned_by(attr, "attribute must be str, e.g. #[node(xx = \"xxx\")]"); cx.error_spanned_by(attr, "attribute must be str, e.g. #[node(xx = \"xxx\")]");
cx.syn_error(err); cx.syn_error(err);
@ -304,6 +301,7 @@ pub fn get_node_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<
} }
} }
} }
pub fn get_event_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> { pub fn get_event_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
// Only handle the attribute that we have defined // Only handle the attribute that we have defined
if attr.path != EVENT { if attr.path != EVENT {

View File

@ -34,11 +34,16 @@ pub const EVENT_ERR: Symbol = Symbol("event_err");
// Node // Node
pub const NODE_ATTRS: Symbol = Symbol("node"); pub const NODE_ATTRS: Symbol = Symbol("node");
pub const NODES_ATTRS: Symbol = Symbol("nodes");
pub const NODE_TYPE: Symbol = Symbol("node_type");
pub const NODE_INDEX: Symbol = Symbol("index");
pub const NODE_RENAME: Symbol = Symbol("rename");
pub const NODE_CHILDREN: Symbol = Symbol("children");
pub const SKIP_NODE_ATTRS: Symbol = Symbol("skip_node_attribute"); pub const SKIP_NODE_ATTRS: Symbol = Symbol("skip_node_attribute");
pub const GET_NODE_VALUE_WITH: Symbol = Symbol("get_value_with"); pub const GET_NODE_VALUE_WITH: Symbol = Symbol("get_value_with");
pub const SET_NODE_VALUE_WITH: Symbol = Symbol("set_value_with"); pub const SET_NODE_VALUE_WITH: Symbol = Symbol("set_value_with");
//#[node(index = "1")] pub const GET_VEC_ELEMENT_WITH: Symbol = Symbol("get_element_with");
pub const NODE_INDEX: Symbol = Symbol("index"); pub const GET_MUT_VEC_ELEMENT_WITH: Symbol = Symbol("get_mut_element_with");
impl PartialEq<Symbol> for Ident { impl PartialEq<Symbol> for Ident {
fn eq(&self, word: &Symbol) -> bool { fn eq(&self, word: &Symbol) -> bool {

View File

@ -37,7 +37,7 @@ pub fn derive_dart_event(input: TokenStream) -> TokenStream {
.into() .into()
} }
#[proc_macro_derive(Node, attributes(node))] #[proc_macro_derive(Node, attributes(node, nodes, node_type))]
pub fn derive_node(input: TokenStream) -> TokenStream { pub fn derive_node(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
node::expand_derive(&input).unwrap_or_else(to_compile_errors).into() node::expand_derive(&input).unwrap_or_else(to_compile_errors).into()

View File

@ -1,14 +1,134 @@
use flowy_ast::{ASTContainer, ASTResult}; use flowy_ast::{ASTContainer, ASTField, ASTResult};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
pub fn expand_derive(input: &syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> { pub fn expand_derive(input: &syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
let ast_result = ASTResult::new(); let ast_result = ASTResult::new();
// let cont = match ASTContainer::from_ast(&ast_result, input) { let cont = match ASTContainer::from_ast(&ast_result, input) {
// Some(cont) => cont, Some(cont) => cont,
// None => return Err(ast_result.check().unwrap_err()), None => return Err(ast_result.check().unwrap_err()),
// }; };
let mut token_stream: TokenStream = TokenStream::default(); let mut token_stream: TokenStream = TokenStream::default();
token_stream.extend(make_to_node_data_token_stream(&cont));
if let Some(get_value_token_stream) = make_get_set_value_token_steam(&cont) {
token_stream.extend(get_value_token_stream);
}
ast_result.check()?; ast_result.check()?;
Ok(token_stream) Ok(token_stream)
} }
pub fn make_alter_children_token_stream(ast: &ASTContainer) -> TokenStream {
let mut token_streams = TokenStream::default();
token_streams
}
pub fn make_to_node_data_token_stream(ast: &ASTContainer) -> TokenStream {
let struct_ident = &ast.ident;
let mut token_streams = TokenStream::default();
let node_type = ast
.node_type
.as_ref()
.expect("Define the type of the node by using #[node_type = \"xx\" in the struct");
let set_key_values = ast
.data
.all_fields()
.filter(|field| !field.node_attrs.is_children)
.flat_map(|field| {
let mut field_name = field.name().expect("the name of the field should not be empty");
let original_field_name = field.name().expect("the name of the field should not be empty");
if let Some(rename) = &field.node_attrs.rename {
field_name = format_ident!("{}", rename.value());
}
let field_name_str = field_name.to_string();
quote! {
.insert_attribute(#field_name_str, self.#original_field_name.clone())
}
});
let children_fields = ast
.data
.all_fields()
.filter(|field| field.node_attrs.is_children)
.collect::<Vec<&ASTField>>();
let childrens_token_streams = match children_fields.is_empty() {
true => {
quote! {
let children = vec![];
}
}
false => {
let children_field = children_fields.first().unwrap();
let original_field_name = children_field
.name()
.expect("the name of the field should not be empty");
quote! {
let children = self.#original_field_name.iter().map(|value| value.to_node_data()).collect::<Vec<NodeData>>();
}
}
};
token_streams.extend(quote! {
impl #struct_ident {
pub fn to_node_data(&self) -> NodeData {
#childrens_token_streams
let builder = NodeDataBuilder::new(#node_type)
#(#set_key_values)*
.extend_node_data(children);
builder.build()
}
}
});
token_streams
}
pub fn make_get_set_value_token_steam(ast: &ASTContainer) -> Option<TokenStream> {
let struct_ident = &ast.ident;
let mut token_streams = TokenStream::default();
let tree = format_ident!("tree");
for field in ast.data.all_fields() {
if field.node_attrs.is_children {
continue;
}
let mut field_name = field.name().expect("the name of the field should not be empty");
if let Some(rename) = &field.node_attrs.rename {
field_name = format_ident!("{}", rename.value());
}
let field_name_str = field_name.to_string();
let get_func_name = format_ident!("get_{}", field_name);
let set_func_name = format_ident!("set_{}", field_name);
let get_value_return_ty = field.ty;
let set_value_input_ty = field.ty;
if let Some(get_value_with_fn) = field.node_attrs.get_node_value_with() {
token_streams.extend(quote! {
impl #struct_ident {
pub fn #get_func_name(&self) -> Option<#get_value_return_ty> {
#get_value_with_fn(self.#tree.clone(), &self.path, #field_name_str)
}
}
});
}
if let Some(set_value_with_fn) = field.node_attrs.set_node_value_with() {
token_streams.extend(quote! {
impl #struct_ident {
pub fn #set_func_name(&self, value: #set_value_input_ty) {
let _ = #set_value_with_fn(self.#tree.clone(), &self.path, #field_name_str, value);
}
}
});
}
}
ast.data.all_fields().for_each(|field| {});
Some(token_streams)
}

View File

@ -2,8 +2,8 @@ use crate::proto_buf::util::*;
use flowy_ast::*; use flowy_ast::*;
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
pub fn make_de_token_steam(ctxt: &ASTResult, ast: &ASTContainer) -> Option<TokenStream> { pub fn make_de_token_steam(ast_result: &ASTResult, ast: &ASTContainer) -> Option<TokenStream> {
let pb_ty = ast.attrs.pb_struct_type()?; let pb_ty = ast.pb_attrs.pb_struct_type()?;
let struct_ident = &ast.ident; let struct_ident = &ast.ident;
let build_take_fields = ast let build_take_fields = ast
@ -15,9 +15,9 @@ pub fn make_de_token_steam(ctxt: &ASTResult, ast: &ASTContainer) -> Option<Token
let member = &field.member; let member = &field.member;
Some(quote! { o.#member=#struct_ident::#func(pb); }) Some(quote! { o.#member=#struct_ident::#func(pb); })
} else if field.pb_attrs.is_one_of() { } else if field.pb_attrs.is_one_of() {
token_stream_for_one_of(ctxt, field) token_stream_for_one_of(ast_result, field)
} else { } else {
token_stream_for_field(ctxt, &field.member, field.ty, false) token_stream_for_field(ast_result, &field.member, field.ty, false)
} }
}); });
@ -58,10 +58,10 @@ pub fn make_de_token_steam(ctxt: &ASTResult, ast: &ASTContainer) -> Option<Token
// None // None
} }
fn token_stream_for_one_of(ctxt: &ASTResult, field: &ASTField) -> Option<TokenStream> { fn token_stream_for_one_of(ast_result: &ASTResult, field: &ASTField) -> Option<TokenStream> {
let member = &field.member; let member = &field.member;
let ident = get_member_ident(ctxt, member)?; let ident = get_member_ident(ast_result, member)?;
let ty_info = match parse_ty(ctxt, field.ty) { let ty_info = match parse_ty(ast_result, field.ty) {
Ok(ty_info) => ty_info, Ok(ty_info) => ty_info,
Err(e) => { Err(e) => {
eprintln!("token_stream_for_one_of failed: {:?} with error: {}", member, e); eprintln!("token_stream_for_one_of failed: {:?} with error: {}", member, e);

View File

@ -4,7 +4,7 @@ use proc_macro2::TokenStream;
#[allow(dead_code)] #[allow(dead_code)]
pub fn make_enum_token_stream(_ast_result: &ASTResult, cont: &ASTContainer) -> Option<TokenStream> { pub fn make_enum_token_stream(_ast_result: &ASTResult, cont: &ASTContainer) -> Option<TokenStream> {
let enum_ident = &cont.ident; let enum_ident = &cont.ident;
let pb_enum = cont.attrs.pb_enum_type()?; let pb_enum = cont.pb_attrs.pb_enum_type()?;
let build_to_pb_enum = cont.data.all_idents().map(|i| { let build_to_pb_enum = cont.data.all_idents().map(|i| {
let token_stream: TokenStream = quote! { let token_stream: TokenStream = quote! {
#enum_ident::#i => crate::protobuf::#pb_enum::#i, #enum_ident::#i => crate::protobuf::#pb_enum::#i,

View File

@ -4,7 +4,7 @@ use flowy_ast::*;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Option<TokenStream> { pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Option<TokenStream> {
let pb_ty = ast.attrs.pb_struct_type()?; let pb_ty = ast.pb_attrs.pb_struct_type()?;
let struct_ident = &ast.ident; let struct_ident = &ast.ident;
let build_set_pb_fields = ast let build_set_pb_fields = ast

View File

@ -1,12 +1,92 @@
use crate::client_folder::AtomicNodeTree; use crate::client_folder::AtomicNodeTree;
use crate::errors::CollaborateResult;
use flowy_derive::Node; use flowy_derive::Node;
use lib_ot::core::*;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, Clone, Node)] #[derive(Clone, Node)]
#[node_type = "workspace"]
pub struct WorkspaceNode2 { pub struct WorkspaceNode2 {
tree: Arc<AtomicNodeTree>, tree: Arc<AtomicNodeTree>,
#[node] path: Path,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String, pub id: String,
// pub name: String,
// pub path: Path, #[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
#[node(rename = "name123")]
pub name: String,
#[node(get_value_with = "get_attributes_int_value")]
pub time: i64,
#[node(children)]
pub apps: Vec<AppNode2>,
}
#[derive(Clone, Node)]
#[node_type = "app"]
pub struct AppNode2 {
tree: Arc<AtomicNodeTree>,
path: Path,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub name: String,
}
pub fn get_attributes_str_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<String> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| node.attributes.get(key).cloned())
.and_then(|value| value.str_value())
}
pub fn set_attributes_str_value(
tree: Arc<AtomicNodeTree>,
path: &Path,
key: &str,
value: String,
) -> CollaborateResult<()> {
let old_attributes = match get_attributes(tree.clone(), path) {
None => AttributeHashMap::new(),
Some(attributes) => attributes,
};
let mut new_attributes = old_attributes.clone();
new_attributes.insert(key, value);
let update_operation = NodeOperation::Update {
path: path.clone(),
changeset: Changeset::Attributes {
new: new_attributes,
old: old_attributes,
},
};
let _ = tree.write().apply_op(update_operation)?;
Ok(())
}
pub fn get_attributes_int_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<i64> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| node.attributes.get(key).cloned())
.and_then(|value| value.int_value())
}
pub fn get_attributes(tree: Arc<AtomicNodeTree>, path: &Path) -> Option<AttributeHashMap> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| Some(node.attributes.clone()))
}
pub fn get_attributes_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<AttributeValue> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| node.attributes.get(key).cloned())
} }

View File

@ -293,6 +293,18 @@ impl std::convert::From<f64> for AttributeValue {
} }
} }
impl std::convert::From<i64> for AttributeValue {
fn from(value: i64) -> Self {
AttributeValue::from_int(value)
}
}
impl std::convert::From<i32> for AttributeValue {
fn from(value: i32) -> Self {
AttributeValue::from_int(value as i64)
}
}
#[derive(Default)] #[derive(Default)]
pub struct AttributeBuilder { pub struct AttributeBuilder {
attributes: AttributeHashMap, attributes: AttributeHashMap,

View File

@ -6,6 +6,10 @@ use crate::errors::OTError;
use crate::text_delta::DeltaTextOperations; use crate::text_delta::DeltaTextOperations;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub trait ToNodeData: Send + Sync {
fn to_node_data(&self) -> NodeData;
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct NodeData { pub struct NodeData {
#[serde(rename = "type")] #[serde(rename = "type")]
@ -66,6 +70,11 @@ impl NodeDataBuilder {
self self
} }
pub fn extend_node_data(mut self, node: Vec<NodeData>) -> Self {
self.node.children.extend(node);
self
}
/// Inserts attributes to the builder's node. /// Inserts attributes to the builder's node.
/// ///
/// The attributes will be replace if they shared the same key /// The attributes will be replace if they shared the same key