Browse Source

chore: add node ast parser

nathan 2 years ago
parent
commit
eb1659fd3e

+ 20 - 13
shared-lib/flowy-ast/src/ast.rs

@@ -1,21 +1,24 @@
 #![allow(clippy::all)]
 #![allow(unused_attributes)]
 #![allow(unused_assignments)]
-use crate::{attr, ty_ext::*, ASTResult, AttrsContainer};
+
+use crate::event_attrs::EventEnumAttrs;
+use crate::node_attrs::NodeStructAttrs;
+use crate::{is_recognizable_field, pb_attrs, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs};
 use syn::{self, punctuated::Punctuated};
 
 pub struct ASTContainer<'a> {
     /// The struct or enum name (without generics).
     pub ident: syn::Ident,
     /// Attributes on the structure.
-    pub attrs: AttrsContainer,
+    pub attrs: PBAttrsContainer,
     /// The contents of the struct or enum.
     pub data: ASTData<'a>,
 }
 
 impl<'a> ASTContainer<'a> {
     pub fn from_ast(ast_result: &ASTResult, ast: &'a syn::DeriveInput) -> Option<ASTContainer<'a>> {
-        let attrs = AttrsContainer::from_ast(ast_result, ast);
+        let attrs = PBAttrsContainer::from_ast(ast_result, ast);
         // syn::DeriveInput
         //  1. syn::DataUnion
         //  2. syn::DataStruct
@@ -55,7 +58,7 @@ impl<'a> ASTData<'a> {
         }
     }
 
-    pub fn all_variants(&'a self) -> Box<dyn Iterator<Item = &'a attr::ASTEnumAttrVariant> + 'a> {
+    pub fn all_variants(&'a self) -> Box<dyn Iterator<Item = &'a EventEnumAttrs> + 'a> {
         match self {
             ASTData::Enum(variants) => {
                 let iter = variants.iter().map(|variant| &variant.attrs);
@@ -85,7 +88,7 @@ impl<'a> ASTData<'a> {
 /// A variant of an enum.
 pub struct ASTEnumVariant<'a> {
     pub ident: syn::Ident,
-    pub attrs: attr::ASTEnumAttrVariant,
+    pub attrs: EventEnumAttrs,
     pub style: ASTStyle,
     pub fields: Vec<ASTField<'a>>,
     pub original: &'a syn::Variant,
@@ -106,7 +109,8 @@ pub enum BracketCategory {
 
 pub struct ASTField<'a> {
     pub member: syn::Member,
-    pub attrs: attr::ASTAttrField,
+    pub pb_attrs: PBStructAttrs,
+    pub node_attrs: NodeStructAttrs,
     pub ty: &'a syn::Type,
     pub original: &'a syn::Field,
     pub bracket_ty: Option<syn::Ident>,
@@ -161,7 +165,8 @@ impl<'a> ASTField<'a> {
                 Some(ident) => syn::Member::Named(ident.clone()),
                 None => syn::Member::Unnamed(index.into()),
             },
-            attrs: attr::ASTAttrField::from_ast(cx, index, field),
+            pb_attrs: PBStructAttrs::from_ast(cx, index, field),
+            node_attrs: NodeStructAttrs::from_ast(cx, index, field),
             ty: &field.ty,
             original: field,
             bracket_ty,
@@ -185,10 +190,6 @@ impl<'a> ASTField<'a> {
             None
         }
     }
-
-    pub fn is_option(&self) -> bool {
-        attr::is_option(self.ty)
-    }
 }
 
 #[derive(Copy, Clone)]
@@ -222,7 +223,7 @@ pub fn enum_from_ast<'a>(
     variants
         .iter()
         .flat_map(|variant| {
-            let attrs = attr::ASTEnumAttrVariant::from_ast(cx, ident, variant, enum_attrs);
+            let attrs = EventEnumAttrs::from_ast(cx, ident, variant, enum_attrs);
             let (style, fields) = struct_from_ast(cx, &variant.fields);
             Some(ASTEnumVariant {
                 ident: variant.ident.clone(),
@@ -239,6 +240,12 @@ fn fields_from_ast<'a>(cx: &ASTResult, fields: &'a Punctuated<syn::Field, Token!
     fields
         .iter()
         .enumerate()
-        .flat_map(|(index, field)| ASTField::new(cx, field, index).ok())
+        .flat_map(|(index, field)| {
+            if is_recognizable_field(field) {
+                ASTField::new(cx, field, index).ok()
+            } else {
+                None
+            }
+        })
         .collect()
 }

+ 147 - 0
shared-lib/flowy-ast/src/event_attrs.rs

@@ -0,0 +1,147 @@
+use crate::{get_event_meta_items, parse_lit_str, symbol::*, ASTResult};
+
+
+use syn::{
+    self,
+    parse::{self, Parse},
+    Meta::{List, NameValue, Path},
+    NestedMeta::{Lit, Meta},
+};
+
+#[derive(Debug, Clone)]
+pub struct EventAttrs {
+    input: Option<syn::Path>,
+    output: Option<syn::Path>,
+    error_ty: Option<String>,
+    pub ignore: bool,
+}
+
+#[derive(Debug, Clone)]
+pub struct EventEnumAttrs {
+    pub enum_name: String,
+    pub enum_item_name: String,
+    pub value: String,
+    pub event_attrs: EventAttrs,
+}
+
+impl EventEnumAttrs {
+    pub fn from_ast(
+        ast_result: &ASTResult,
+        ident: &syn::Ident,
+        variant: &syn::Variant,
+        enum_attrs: &[syn::Attribute],
+    ) -> Self {
+        let enum_item_name = variant.ident.to_string();
+        let enum_name = ident.to_string();
+        let mut value = String::new();
+        if variant.discriminant.is_some() {
+            if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 {
+                let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit {
+                    int_value
+                } else {
+                    unimplemented!()
+                };
+                value = lit_int.base10_digits().to_string();
+            }
+        }
+        let event_attrs = get_event_attrs_from(ast_result, &variant.attrs, enum_attrs);
+        EventEnumAttrs {
+            enum_name,
+            enum_item_name,
+            value,
+            event_attrs,
+        }
+    }
+
+    pub fn event_input(&self) -> Option<syn::Path> {
+        self.event_attrs.input.clone()
+    }
+
+    pub fn event_output(&self) -> Option<syn::Path> {
+        self.event_attrs.output.clone()
+    }
+
+    pub fn event_error(&self) -> String {
+        self.event_attrs.error_ty.as_ref().unwrap().clone()
+    }
+}
+
+fn get_event_attrs_from(
+    ast_result: &ASTResult,
+    variant_attrs: &[syn::Attribute],
+    enum_attrs: &[syn::Attribute],
+) -> EventAttrs {
+    let mut event_attrs = EventAttrs {
+        input: None,
+        output: None,
+        error_ty: None,
+        ignore: false,
+    };
+
+    enum_attrs
+        .iter()
+        .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR))
+        .for_each(|attr| {
+            if let Ok(NameValue(named_value)) = attr.parse_meta() {
+                if let syn::Lit::Str(s) = named_value.lit {
+                    event_attrs.error_ty = Some(s.value());
+                } else {
+                    eprintln!("❌ {} should not be empty", EVENT_ERR);
+                }
+            } else {
+                eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr);
+            }
+        });
+
+    let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item {
+        Meta(NameValue(name_value)) => {
+            if name_value.path == EVENT_INPUT {
+                if let syn::Lit::Str(s) = &name_value.lit {
+                    let input_type = parse_lit_str(s)
+                        .map_err(|_| {
+                            ast_result
+                                .error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value()))
+                        })
+                        .unwrap();
+                    event_attrs.input = Some(input_type);
+                }
+            }
+
+            if name_value.path == EVENT_OUTPUT {
+                if let syn::Lit::Str(s) = &name_value.lit {
+                    let output_type = parse_lit_str(s)
+                        .map_err(|_| {
+                            ast_result
+                                .error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value()))
+                        })
+                        .unwrap();
+                    event_attrs.output = Some(output_type);
+                }
+            }
+        }
+        Meta(Path(word)) => {
+            if word == EVENT_IGNORE && attr.path == EVENT {
+                event_attrs.ignore = true;
+            }
+        }
+        Lit(s) => ast_result.error_spanned_by(s, "unexpected attribute"),
+        _ => ast_result.error_spanned_by(meta_item, "unexpected attribute"),
+    };
+
+    let attr_meta_items_info = variant_attrs
+        .iter()
+        .flat_map(|attr| match get_event_meta_items(ast_result, attr) {
+            Ok(items) => Some((attr, items)),
+            Err(_) => None,
+        })
+        .collect::<Vec<(&syn::Attribute, Vec<syn::NestedMeta>)>>();
+
+    for (attr, nested_metas) in attr_meta_items_info {
+        nested_metas
+            .iter()
+            .for_each(|meta_item| extract_event_attr(attr, meta_item))
+    }
+
+    // eprintln!("😁{:#?}", event_attrs);
+    event_attrs
+}

+ 6 - 2
shared-lib/flowy-ast/src/lib.rs

@@ -5,12 +5,16 @@ extern crate syn;
 extern crate quote;
 
 mod ast;
-mod attr;
 mod ctxt;
+mod pb_attrs;
 
+mod event_attrs;
+mod node_attrs;
 pub mod symbol;
 pub mod ty_ext;
+
 pub use self::{symbol::*, ty_ext::*};
 pub use ast::*;
-pub use attr::*;
 pub use ctxt::ASTResult;
+pub use event_attrs::*;
+pub use pb_attrs::*;

+ 82 - 0
shared-lib/flowy-ast/src/node_attrs.rs

@@ -0,0 +1,82 @@
+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 quote::ToTokens;
+use syn::{
+    self,
+    parse::{self, Parse},
+    Meta::{List, NameValue, Path},
+    NestedMeta::{Lit, Meta},
+};
+
+pub struct NodeStructAttrs {
+    node_index: Option<syn::LitInt>,
+    get_node_value_with: Option<syn::ExprPath>,
+    set_node_value_with: Option<syn::ExprPath>,
+}
+
+impl NodeStructAttrs {
+    /// Extract out the `#[node(...)]` attributes from a struct field.
+    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 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 ident = match &field.ident {
+            Some(ident) => ident.to_string(),
+            None => index.to_string(),
+        };
+
+        for meta_item in field
+            .attrs
+            .iter()
+            .flat_map(|attr| get_node_meta_items(ast_result, attr))
+            .flatten()
+        {
+            match &meta_item {
+                // Parse '#[node(index = x)]'
+                Meta(NameValue(m)) if m.path == NODE_INDEX => {
+                    if let syn::Lit::Int(lit) = &m.lit {
+                        node_index.set(&m.path, lit.clone());
+                    }
+                }
+
+                // Parse `#[node(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) {
+                        get_node_value_with.set(&m.path, path);
+                    }
+                }
+
+                // Parse `#[node(set_node_value_with= "...")]`
+                Meta(NameValue(m)) if m.path == SET_NODE_VALUE_WITH => {
+                    if let Ok(path) = parse_lit_into_expr_path(ast_result, SET_NODE_VALUE_WITH, &m.lit) {
+                        set_node_value_with.set(&m.path, path);
+                    }
+                }
+
+                Meta(meta_item) => {
+                    let path = meta_item.path().into_token_stream().to_string().replace(' ', "");
+                    ast_result.error_spanned_by(meta_item.path(), format!("unknown node field attribute `{}`", path));
+                }
+
+                Lit(lit) => {
+                    ast_result.error_spanned_by(lit, "unexpected literal in field attribute");
+                }
+            }
+        }
+
+        NodeStructAttrs {
+            node_index: node_index.get(),
+            get_node_value_with: get_node_value_with.get(),
+            set_node_value_with: set_node_value_with.get(),
+        }
+    }
+
+    pub fn set_node_value_with(&self) -> Option<&syn::ExprPath> {
+        self.set_node_value_with.as_ref()
+    }
+
+    pub fn get_node_value_with(&self) -> Option<&syn::ExprPath> {
+        self.get_node_value_with.as_ref()
+    }
+}

+ 94 - 178
shared-lib/flowy-ast/src/attr.rs → shared-lib/flowy-ast/src/pb_attrs.rs

@@ -1,5 +1,7 @@
 #![allow(clippy::all)]
+
 use crate::{symbol::*, ASTResult};
+use proc_macro2::{Group, Span, TokenStream, TokenTree};
 use quote::ToTokens;
 use syn::{
     self,
@@ -8,16 +10,14 @@ use syn::{
     NestedMeta::{Lit, Meta},
 };
 
-use proc_macro2::{Group, Span, TokenStream, TokenTree};
-
 #[allow(dead_code)]
-pub struct AttrsContainer {
+pub struct PBAttrsContainer {
     name: String,
     pb_struct_type: Option<syn::Type>,
     pb_enum_type: Option<syn::Type>,
 }
 
-impl AttrsContainer {
+impl PBAttrsContainer {
     /// Extract out the `#[pb(...)]` attributes from an item.
     pub fn from_ast(ast_result: &ASTResult, item: &syn::DeriveInput) -> Self {
         let mut pb_struct_type = ASTAttr::none(ast_result, PB_STRUCT);
@@ -25,7 +25,7 @@ impl AttrsContainer {
         for meta_item in item
             .attrs
             .iter()
-            .flat_map(|attr| get_meta_items(ast_result, attr))
+            .flat_map(|attr| get_pb_meta_items(ast_result, attr))
             .flatten()
         {
             match &meta_item {
@@ -45,11 +45,11 @@ impl AttrsContainer {
 
                 Meta(meta_item) => {
                     let path = meta_item.path().into_token_stream().to_string().replace(' ', "");
-                    ast_result.error_spanned_by(meta_item.path(), format!("unknown pb container attribute `{}`", path));
+                    ast_result.error_spanned_by(meta_item.path(), format!("unknown container attribute `{}`", path));
                 }
 
                 Lit(lit) => {
-                    ast_result.error_spanned_by(lit, "unexpected literal in pb container attribute");
+                    ast_result.error_spanned_by(lit, "unexpected literal in container attribute");
                 }
             }
         }
@@ -63,7 +63,7 @@ impl AttrsContainer {
             _ => {}
         }
 
-        AttrsContainer {
+        PBAttrsContainer {
             name: item.ident.to_string(),
             pb_struct_type: pb_struct_type.get(),
             pb_enum_type: pb_enum_type.get(),
@@ -79,7 +79,7 @@ impl AttrsContainer {
     }
 }
 
-struct ASTAttr<'c, T> {
+pub struct ASTAttr<'c, T> {
     ast_result: &'c ASTResult,
     name: Symbol,
     tokens: TokenStream,
@@ -87,7 +87,7 @@ struct ASTAttr<'c, T> {
 }
 
 impl<'c, T> ASTAttr<'c, T> {
-    fn none(ast_result: &'c ASTResult, name: Symbol) -> Self {
+    pub(crate) fn none(ast_result: &'c ASTResult, name: Symbol) -> Self {
         ASTAttr {
             ast_result,
             name,
@@ -96,7 +96,7 @@ impl<'c, T> ASTAttr<'c, T> {
         }
     }
 
-    fn set<A: ToTokens>(&mut self, obj: A, value: T) {
+    pub(crate) fn set<A: ToTokens>(&mut self, obj: A, value: T) {
         let tokens = obj.into_token_stream();
 
         if self.value.is_some() {
@@ -120,7 +120,7 @@ impl<'c, T> ASTAttr<'c, T> {
         }
     }
 
-    fn get(self) -> Option<T> {
+    pub(crate) fn get(self) -> Option<T> {
         self.value
     }
 
@@ -133,26 +133,30 @@ impl<'c, T> ASTAttr<'c, T> {
     }
 }
 
-pub struct ASTAttrField {
+pub struct PBStructAttrs {
     #[allow(dead_code)]
     name: String,
     pb_index: Option<syn::LitInt>,
     pb_one_of: bool,
-    skip_serializing: bool,
-    skip_deserializing: bool,
-    serialize_with: Option<syn::ExprPath>,
-    deserialize_with: Option<syn::ExprPath>,
+    skip_pb_serializing: bool,
+    skip_pb_deserializing: bool,
+    serialize_pb_with: Option<syn::ExprPath>,
+    deserialize_pb_with: Option<syn::ExprPath>,
+}
+
+pub fn is_recognizable_field(field: &syn::Field) -> bool {
+    field.attrs.iter().any(|attr| is_recognizable_attribute(attr))
 }
 
-impl ASTAttrField {
+impl PBStructAttrs {
     /// Extract out the `#[pb(...)]` attributes from a struct field.
     pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self {
         let mut pb_index = ASTAttr::none(ast_result, PB_INDEX);
         let mut pb_one_of = BoolAttr::none(ast_result, PB_ONE_OF);
-        let mut serialize_with = ASTAttr::none(ast_result, SERIALIZE_WITH);
-        let mut skip_serializing = BoolAttr::none(ast_result, SKIP_SERIALIZING);
-        let mut deserialize_with = ASTAttr::none(ast_result, DESERIALIZE_WITH);
-        let mut skip_deserializing = BoolAttr::none(ast_result, SKIP_DESERIALIZING);
+        let mut serialize_pb_with = ASTAttr::none(ast_result, SERIALIZE_PB_WITH);
+        let mut skip_pb_serializing = BoolAttr::none(ast_result, SKIP_PB_SERIALIZING);
+        let mut deserialize_pb_with = ASTAttr::none(ast_result, DESERIALIZE_PB_WITH);
+        let mut skip_pb_deserializing = BoolAttr::none(ast_result, SKIP_PB_DESERIALIZING);
 
         let ident = match &field.ident {
             Some(ident) => ident.to_string(),
@@ -162,14 +166,14 @@ impl ASTAttrField {
         for meta_item in field
             .attrs
             .iter()
-            .flat_map(|attr| get_meta_items(ast_result, attr))
+            .flat_map(|attr| get_pb_meta_items(ast_result, attr))
             .flatten()
         {
             match &meta_item {
                 // Parse `#[pb(skip)]`
                 Meta(Path(word)) if word == SKIP => {
-                    skip_serializing.set_true(word);
-                    skip_deserializing.set_true(word);
+                    skip_pb_serializing.set_true(word);
+                    skip_pb_deserializing.set_true(word);
                 }
 
                 // Parse '#[pb(index = x)]'
@@ -184,39 +188,39 @@ impl ASTAttrField {
                     pb_one_of.set_true(path);
                 }
 
-                // Parse `#[pb(serialize_with = "...")]`
-                Meta(NameValue(m)) if m.path == SERIALIZE_WITH => {
-                    if let Ok(path) = parse_lit_into_expr_path(ast_result, SERIALIZE_WITH, &m.lit) {
-                        serialize_with.set(&m.path, path);
+                // Parse `#[pb(serialize_pb_with = "...")]`
+                Meta(NameValue(m)) if m.path == SERIALIZE_PB_WITH => {
+                    if let Ok(path) = parse_lit_into_expr_path(ast_result, SERIALIZE_PB_WITH, &m.lit) {
+                        serialize_pb_with.set(&m.path, path);
                     }
                 }
 
-                // Parse `#[pb(deserialize_with = "...")]`
-                Meta(NameValue(m)) if m.path == DESERIALIZE_WITH => {
-                    if let Ok(path) = parse_lit_into_expr_path(ast_result, DESERIALIZE_WITH, &m.lit) {
-                        deserialize_with.set(&m.path, path);
+                // Parse `#[pb(deserialize_pb_with = "...")]`
+                Meta(NameValue(m)) if m.path == DESERIALIZE_PB_WITH => {
+                    if let Ok(path) = parse_lit_into_expr_path(ast_result, DESERIALIZE_PB_WITH, &m.lit) {
+                        deserialize_pb_with.set(&m.path, path);
                     }
                 }
 
                 Meta(meta_item) => {
                     let path = meta_item.path().into_token_stream().to_string().replace(' ', "");
-                    ast_result.error_spanned_by(meta_item.path(), format!("unknown field attribute `{}`", path));
+                    ast_result.error_spanned_by(meta_item.path(), format!("unknown pb field attribute `{}`", path));
                 }
 
                 Lit(lit) => {
-                    ast_result.error_spanned_by(lit, "unexpected literal in pb field attribute");
+                    ast_result.error_spanned_by(lit, "unexpected literal in field attribute");
                 }
             }
         }
 
-        ASTAttrField {
+        PBStructAttrs {
             name: ident,
             pb_index: pb_index.get(),
             pb_one_of: pb_one_of.get(),
-            skip_serializing: skip_serializing.get(),
-            skip_deserializing: skip_deserializing.get(),
-            serialize_with: serialize_with.get(),
-            deserialize_with: deserialize_with.get(),
+            skip_pb_serializing: skip_pb_serializing.get(),
+            skip_pb_deserializing: skip_pb_deserializing.get(),
+            serialize_pb_with: serialize_pb_with.get(),
+            deserialize_pb_with: deserialize_pb_with.get(),
         }
     }
 
@@ -229,20 +233,20 @@ impl ASTAttrField {
         self.pb_one_of
     }
 
-    pub fn serialize_with(&self) -> Option<&syn::ExprPath> {
-        self.serialize_with.as_ref()
+    pub fn serialize_pb_with(&self) -> Option<&syn::ExprPath> {
+        self.serialize_pb_with.as_ref()
     }
 
-    pub fn deserialize_with(&self) -> Option<&syn::ExprPath> {
-        self.deserialize_with.as_ref()
+    pub fn deserialize_pb_with(&self) -> Option<&syn::ExprPath> {
+        self.deserialize_pb_with.as_ref()
     }
 
-    pub fn skip_serializing(&self) -> bool {
-        self.skip_serializing
+    pub fn skip_pb_serializing(&self) -> bool {
+        self.skip_pb_serializing
     }
 
-    pub fn skip_deserializing(&self) -> bool {
-        self.skip_deserializing
+    pub fn skip_pb_deserializing(&self) -> bool {
+        self.skip_pb_deserializing
     }
 }
 
@@ -255,146 +259,54 @@ pub enum Default {
     Path(syn::ExprPath),
 }
 
-#[derive(Debug, Clone)]
-pub struct EventAttrs {
-    input: Option<syn::Path>,
-    output: Option<syn::Path>,
-    error_ty: Option<String>,
-    pub ignore: bool,
+pub fn is_recognizable_attribute(attr: &syn::Attribute) -> bool {
+    attr.path == PB_ATTRS || attr.path == EVENT || attr.path == NODE_ATTRS
 }
 
-#[derive(Debug, Clone)]
-pub struct ASTEnumAttrVariant {
-    pub enum_name: String,
-    pub enum_item_name: String,
-    pub value: String,
-    pub event_attrs: EventAttrs,
-}
+pub fn get_pb_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
+    // Only handle the attribute that we have defined
+    if attr.path != PB_ATTRS {
+        return Ok(vec![]);
+    }
 
-impl ASTEnumAttrVariant {
-    pub fn from_ast(
-        ast_result: &ASTResult,
-        ident: &syn::Ident,
-        variant: &syn::Variant,
-        enum_attrs: &[syn::Attribute],
-    ) -> Self {
-        let enum_item_name = variant.ident.to_string();
-        let enum_name = ident.to_string();
-        let mut value = String::new();
-        if variant.discriminant.is_some() {
-            if let syn::Expr::Lit(ref expr_list) = variant.discriminant.as_ref().unwrap().1 {
-                let lit_int = if let syn::Lit::Int(ref int_value) = expr_list.lit {
-                    int_value
-                } else {
-                    unimplemented!()
-                };
-                value = lit_int.base10_digits().to_string();
-            }
+    // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html
+    match attr.parse_meta() {
+        Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
+        Ok(other) => {
+            cx.error_spanned_by(other, "expected #[pb(...)]");
+            Err(())
         }
-        let event_attrs = get_event_attrs_from(ast_result, &variant.attrs, enum_attrs);
-        ASTEnumAttrVariant {
-            enum_name,
-            enum_item_name,
-            value,
-            event_attrs,
+        Err(err) => {
+            cx.error_spanned_by(attr, "attribute must be str, e.g. #[pb(xx = \"xxx\")]");
+            cx.syn_error(err);
+            Err(())
         }
     }
-
-    pub fn event_input(&self) -> Option<syn::Path> {
-        self.event_attrs.input.clone()
-    }
-
-    pub fn event_output(&self) -> Option<syn::Path> {
-        self.event_attrs.output.clone()
-    }
-
-    pub fn event_error(&self) -> String {
-        self.event_attrs.error_ty.as_ref().unwrap().clone()
-    }
 }
 
-fn get_event_attrs_from(
-    ast_result: &ASTResult,
-    variant_attrs: &[syn::Attribute],
-    enum_attrs: &[syn::Attribute],
-) -> EventAttrs {
-    let mut event_attrs = EventAttrs {
-        input: None,
-        output: None,
-        error_ty: None,
-        ignore: false,
-    };
-
-    enum_attrs
-        .iter()
-        .filter(|attr| attr.path.segments.iter().any(|s| s.ident == EVENT_ERR))
-        .for_each(|attr| {
-            if let Ok(NameValue(named_value)) = attr.parse_meta() {
-                if let syn::Lit::Str(s) = named_value.lit {
-                    event_attrs.error_ty = Some(s.value());
-                } else {
-                    eprintln!("❌ {} should not be empty", EVENT_ERR);
-                }
-            } else {
-                eprintln!("❌ Can not find any {} on attr: {:#?}", EVENT_ERR, attr);
-            }
-        });
-
-    let mut extract_event_attr = |attr: &syn::Attribute, meta_item: &syn::NestedMeta| match &meta_item {
-        Meta(NameValue(name_value)) => {
-            if name_value.path == EVENT_INPUT {
-                if let syn::Lit::Str(s) = &name_value.lit {
-                    let input_type = parse_lit_str(s)
-                        .map_err(|_| {
-                            ast_result
-                                .error_spanned_by(s, format!("failed to parse request deserializer {:?}", s.value()))
-                        })
-                        .unwrap();
-                    event_attrs.input = Some(input_type);
-                }
-            }
+pub fn get_node_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
+    // Only handle the attribute that we have defined
+    if attr.path != NODE_ATTRS {
+        return Ok(vec![]);
+    }
 
-            if name_value.path == EVENT_OUTPUT {
-                if let syn::Lit::Str(s) = &name_value.lit {
-                    let output_type = parse_lit_str(s)
-                        .map_err(|_| {
-                            ast_result
-                                .error_spanned_by(s, format!("failed to parse response deserializer {:?}", s.value()))
-                        })
-                        .unwrap();
-                    event_attrs.output = Some(output_type);
-                }
-            }
+    // http://strymon.systems.ethz.ch/typename/syn/enum.Meta.html
+    match attr.parse_meta() {
+        Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
+        Ok(other) => {
+            cx.error_spanned_by(other, "expected #[node(...)]");
+            Err(())
         }
-        Meta(Path(word)) => {
-            if word == EVENT_IGNORE && attr.path == EVENT {
-                event_attrs.ignore = true;
-            }
+        Err(err) => {
+            cx.error_spanned_by(attr, "attribute must be str, e.g. #[node(xx = \"xxx\")]");
+            cx.syn_error(err);
+            Err(())
         }
-        Lit(s) => ast_result.error_spanned_by(s, "unexpected attribute"),
-        _ => ast_result.error_spanned_by(meta_item, "unexpected attribute"),
-    };
-
-    let attr_meta_items_info = variant_attrs
-        .iter()
-        .flat_map(|attr| match get_meta_items(ast_result, attr) {
-            Ok(items) => Some((attr, items)),
-            Err(_) => None,
-        })
-        .collect::<Vec<(&syn::Attribute, Vec<syn::NestedMeta>)>>();
-
-    for (attr, nested_metas) in attr_meta_items_info {
-        nested_metas
-            .iter()
-            .for_each(|meta_item| extract_event_attr(attr, meta_item))
     }
-
-    // eprintln!("😁{:#?}", event_attrs);
-    event_attrs
 }
-
-pub fn get_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
-    if attr.path != PB_ATTRS && attr.path != EVENT {
+pub fn get_event_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {
+    // Only handle the attribute that we have defined
+    if attr.path != EVENT {
         return Ok(vec![]);
     }
 
@@ -402,18 +314,22 @@ pub fn get_meta_items(cx: &ASTResult, attr: &syn::Attribute) -> Result<Vec<syn::
     match attr.parse_meta() {
         Ok(List(meta)) => Ok(meta.nested.into_iter().collect()),
         Ok(other) => {
-            cx.error_spanned_by(other, "expected #[pb(...)] or or #[event(...)]");
+            cx.error_spanned_by(other, "expected #[event(...)]");
             Err(())
         }
         Err(err) => {
-            cx.error_spanned_by(attr, "attribute must be str, e.g. #[pb(xx = \"xxx\")]");
+            cx.error_spanned_by(attr, "attribute must be str, e.g. #[event(xx = \"xxx\")]");
             cx.syn_error(err);
             Err(())
         }
     }
 }
 
-fn parse_lit_into_expr_path(ast_result: &ASTResult, attr_name: Symbol, lit: &syn::Lit) -> Result<syn::ExprPath, ()> {
+pub fn parse_lit_into_expr_path(
+    ast_result: &ASTResult,
+    attr_name: Symbol,
+    lit: &syn::Lit,
+) -> Result<syn::ExprPath, ()> {
     let string = get_lit_str(ast_result, attr_name, lit)?;
     parse_lit_str(string)
         .map_err(|_| ast_result.error_spanned_by(lit, format!("failed to parse path: {:?}", string.value())))

+ 29 - 9
shared-lib/flowy-ast/src/symbol.rs

@@ -3,23 +3,43 @@ use syn::{Ident, Path};
 
 #[derive(Copy, Clone)]
 pub struct Symbol(&'static str);
+
+// Protobuf
 pub const PB_ATTRS: Symbol = Symbol("pb");
-pub const SKIP: Symbol = Symbol("skip"); //#[pb(skip)]
-pub const PB_INDEX: Symbol = Symbol("index"); //#[pb(index = "1")]
-pub const PB_ONE_OF: Symbol = Symbol("one_of"); //#[pb(one_of)]
-pub const DESERIALIZE_WITH: Symbol = Symbol("deserialize_with");
-pub const SKIP_DESERIALIZING: Symbol = Symbol("skip_deserializing");
-pub const SERIALIZE_WITH: Symbol = Symbol("serialize_with"); //#[pb(serialize_with = "...")]
-pub const SKIP_SERIALIZING: Symbol = Symbol("skip_serializing"); //#[pb(skip_serializing)]
-pub const PB_STRUCT: Symbol = Symbol("struct"); //#[pb(struct="some struct")]
-pub const PB_ENUM: Symbol = Symbol("enum"); //#[pb(enum="some enum")]
+//#[pb(skip)]
+pub const SKIP: Symbol = Symbol("skip");
+//#[pb(index = "1")]
+pub const PB_INDEX: Symbol = Symbol("index");
+//#[pb(one_of)]
+pub const PB_ONE_OF: Symbol = Symbol("one_of");
+//#[pb(skip_pb_deserializing = "...")]
+pub const SKIP_PB_DESERIALIZING: Symbol = Symbol("skip_pb_deserializing");
+//#[pb(skip_pb_serializing)]
+pub const SKIP_PB_SERIALIZING: Symbol = Symbol("skip_pb_serializing");
+//#[pb(serialize_pb_with = "...")]
+pub const SERIALIZE_PB_WITH: Symbol = Symbol("serialize_pb_with");
+//#[pb(deserialize_pb_with = "...")]
+pub const DESERIALIZE_PB_WITH: Symbol = Symbol("deserialize_pb_with");
+//#[pb(struct="some struct")]
+pub const PB_STRUCT: Symbol = Symbol("struct");
+//#[pb(enum="some enum")]
+pub const PB_ENUM: Symbol = Symbol("enum");
 
+// Event
 pub const EVENT_INPUT: Symbol = Symbol("input");
 pub const EVENT_OUTPUT: Symbol = Symbol("output");
 pub const EVENT_IGNORE: Symbol = Symbol("ignore");
 pub const EVENT: Symbol = Symbol("event");
 pub const EVENT_ERR: Symbol = Symbol("event_err");
 
+// Node
+pub const NODE_ATTRS: Symbol = Symbol("node");
+pub const SKIP_NODE_ATTRS: Symbol = Symbol("skip_node_attribute");
+pub const GET_NODE_VALUE_WITH: Symbol = Symbol("get_value_with");
+pub const SET_NODE_VALUE_WITH: Symbol = Symbol("set_value_with");
+//#[node(index = "1")]
+pub const NODE_INDEX: Symbol = Symbol("index");
+
 impl PartialEq<Symbol> for Ident {
     fn eq(&self, word: &Symbol) -> bool {
         self == word.0

+ 8 - 8
shared-lib/flowy-codegen/src/dart_event/ast.rs

@@ -1,4 +1,4 @@
-use flowy_ast::ASTEnumAttrVariant;
+use flowy_ast::EventEnumAttrs;
 
 pub struct EventASTContext {
     pub event: syn::Ident,
@@ -10,21 +10,21 @@ pub struct EventASTContext {
 }
 
 impl EventASTContext {
-    pub fn from(variant: &ASTEnumAttrVariant) -> EventASTContext {
-        let command_name = variant.enum_item_name.clone();
+    pub fn from(enum_attrs: &EventEnumAttrs) -> EventASTContext {
+        let command_name = enum_attrs.enum_item_name.clone();
         if command_name.is_empty() {
-            panic!("Invalid command name: {}", variant.enum_item_name);
+            panic!("Invalid command name: {}", enum_attrs.enum_item_name);
         }
 
         let event = format_ident!("{}", &command_name);
         let splits = command_name.split('_').collect::<Vec<&str>>();
 
-        let event_ty = format_ident!("{}", variant.enum_name);
+        let event_ty = format_ident!("{}", enum_attrs.enum_name);
         let event_request_struct = format_ident!("{}Event", &splits.join(""));
 
-        let event_input = variant.event_input();
-        let event_output = variant.event_output();
-        let event_error = variant.event_error();
+        let event_input = enum_attrs.event_input();
+        let event_output = enum_attrs.event_output();
+        let event_error = enum_attrs.event_error();
 
         EventASTContext {
             event,

+ 1 - 1
shared-lib/flowy-codegen/src/dart_event/dart_event.rs

@@ -130,7 +130,7 @@ pub fn parse_event_crate(event_crate: &DartEventCrate) -> Vec<EventASTContext> {
                             .iter()
                             .filter(|attr| !attr.attrs.event_attrs.ignore)
                             .enumerate()
-                            .map(|(_index, attr)| EventASTContext::from(&attr.attrs))
+                            .map(|(_index, variant)| EventASTContext::from(&variant.attrs))
                             .collect::<Vec<_>>()
                     }
                     _ => vec![],

+ 2 - 2
shared-lib/flowy-codegen/src/protobuf_file/ast.rs

@@ -66,7 +66,7 @@ fn parse_files_protobuf(proto_crate_path: &Path, proto_output_path: &Path) -> Ve
 
             s.fields
                 .iter()
-                .filter(|field| field.attrs.pb_index().is_some())
+                .filter(|field| field.pb_attrs.pb_index().is_some())
                 .for_each(|field| {
                     ref_types.push(field.ty_as_str());
                     struct_template.set_field(field);
@@ -121,7 +121,7 @@ pub fn get_ast_structs(ast: &syn::File) -> Vec<Struct> {
         if let Item::Struct(item_struct) = item {
             let (_, fields) = struct_from_ast(&ast_result, &item_struct.fields);
 
-            if fields.iter().filter(|f| f.attrs.pb_index().is_some()).count() > 0 {
+            if fields.iter().filter(|f| f.pb_attrs.pb_index().is_some()).count() > 0 {
                 proto_structs.push(Struct {
                     name: item_struct.ident.to_string(),
                     fields,

+ 1 - 1
shared-lib/flowy-codegen/src/protobuf_file/template/proto_file/struct_template.rs

@@ -36,7 +36,7 @@ impl StructTemplate {
     pub fn set_field(&mut self, field: &ASTField) {
         // {{ field_type }} {{ field_name }} = {{index}};
         let name = field.name().unwrap().to_string();
-        let index = field.attrs.pb_index().unwrap();
+        let index = field.pb_attrs.pb_index().unwrap();
 
         let ty: &str = &field.ty_as_str();
         let mut mapped_ty: &str = ty;

+ 7 - 0
shared-lib/flowy-derive/src/lib.rs

@@ -9,6 +9,7 @@ use syn::{parse_macro_input, DeriveInput};
 extern crate quote;
 
 mod dart_event;
+mod node;
 mod proto_buf;
 
 // Inspired by https://serde.rs/attributes.html
@@ -36,6 +37,12 @@ pub fn derive_dart_event(input: TokenStream) -> TokenStream {
         .into()
 }
 
+#[proc_macro_derive(Node, attributes(node))]
+pub fn derive_node(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    node::expand_derive(&input).unwrap_or_else(to_compile_errors).into()
+}
+
 fn to_compile_errors(errors: Vec<syn::Error>) -> proc_macro2::TokenStream {
     let compile_errors = errors.iter().map(syn::Error::to_compile_error);
     quote!(#(#compile_errors)*)

+ 14 - 0
shared-lib/flowy-derive/src/node/mod.rs

@@ -0,0 +1,14 @@
+use flowy_ast::{ASTContainer, ASTResult};
+use proc_macro2::TokenStream;
+
+pub fn expand_derive(input: &syn::DeriveInput) -> Result<TokenStream, Vec<syn::Error>> {
+    let ast_result = ASTResult::new();
+    // let cont = match ASTContainer::from_ast(&ast_result, input) {
+    //     Some(cont) => cont,
+    //     None => return Err(ast_result.check().unwrap_err()),
+    // };
+
+    let mut token_stream: TokenStream = TokenStream::default();
+    ast_result.check()?;
+    Ok(token_stream)
+}

+ 3 - 3
shared-lib/flowy-derive/src/proto_buf/deserialize.rs

@@ -9,12 +9,12 @@ pub fn make_de_token_steam(ctxt: &ASTResult, ast: &ASTContainer) -> Option<Token
     let build_take_fields = ast
         .data
         .all_fields()
-        .filter(|f| !f.attrs.skip_deserializing())
+        .filter(|f| !f.pb_attrs.skip_pb_deserializing())
         .flat_map(|field| {
-            if let Some(func) = field.attrs.deserialize_with() {
+            if let Some(func) = field.pb_attrs.deserialize_pb_with() {
                 let member = &field.member;
                 Some(quote! { o.#member=#struct_ident::#func(pb); })
-            } else if field.attrs.is_one_of() {
+            } else if field.pb_attrs.is_one_of() {
                 token_stream_for_one_of(ctxt, field)
             } else {
                 token_stream_for_field(ctxt, &field.member, field.ty, false)

+ 3 - 3
shared-lib/flowy-derive/src/proto_buf/serialize.rs

@@ -10,7 +10,7 @@ pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Optio
     let build_set_pb_fields = ast
         .data
         .all_fields()
-        .filter(|f| !f.attrs.skip_serializing())
+        .filter(|f| !f.pb_attrs.skip_pb_serializing())
         .flat_map(|field| se_token_stream_for_field(ast_result, field, false));
 
     let se_token_stream: TokenStream = quote! {
@@ -38,10 +38,10 @@ pub fn make_se_token_stream(ast_result: &ASTResult, ast: &ASTContainer) -> Optio
 }
 
 fn se_token_stream_for_field(ast_result: &ASTResult, field: &ASTField, _take: bool) -> Option<TokenStream> {
-    if let Some(func) = &field.attrs.serialize_with() {
+    if let Some(func) = &field.pb_attrs.serialize_pb_with() {
         let member = &field.member;
         Some(quote! { pb.#member=o.#func(); })
-    } else if field.attrs.is_one_of() {
+    } else if field.pb_attrs.is_one_of() {
         token_stream_for_one_of(ast_result, field)
     } else {
         gen_token_stream(ast_result, &field.member, field.ty, false)

+ 2 - 2
shared-lib/flowy-sync/src/client_folder/app_node.rs

@@ -60,8 +60,8 @@ impl AppNode {
         get_attributes_str_value(self.tree.clone(), &self.path, "name")
     }
 
-    pub fn set_name(&self, name: &str) -> CollaborateResult<()> {
-        set_attributes_str_value(self.tree.clone(), &self.path, "name", name.to_string())
+    pub fn set_name(&self, name: String) -> CollaborateResult<()> {
+        set_attributes_str_value(self.tree.clone(), &self.path, "name", name)
     }
 
     fn get_workspace_id(&self) -> Option<String> {

+ 37 - 0
shared-lib/flowy-sync/src/client_folder/folder_node.rs

@@ -11,8 +11,45 @@ use std::sync::Arc;
 
 pub type AtomicNodeTree = RwLock<NodeTree>;
 
+// pub struct FolderNodePad2 {
+//     tree: Arc<AtomicNodeTree>,
+//
+//     #[node(rename = "workspaces", revision = "WorkspaceRevision")]
+//     workspaces: Vec<Arc<WorkspaceNode>>,
+// }
+//
+// impl FolderNodePad2 {
+//     pub fn get_workspace() {}
+//     pub fn get_mut_workspace() {}
+//     pub fn add_workspace() {}
+//     pub fn remove_workspace() {}
+//     pub fn to_json() {}
+// }
+//
+// #[derive(Debug, Clone)]
+// pub struct WorkspaceNode2 {
+//     tree: Arc<AtomicNodeTree>,
+//     pub id: String,
+//     pub name: String,
+//     pub path: Path,
+// }
+//
+// impl WorkspaceNode2 {
+//     pub fn get_id() {}
+//     pub fn set_id() {}
+//     pub fn get_name() {}
+//     pub fn set_name() {}
+//     pub fn get_apps() {}
+//
+//     pub fn get_app() {}
+//     pub fn get_mut_app() {}
+//     pub fn add_app() {}
+//     pub fn remove_app() {}
+// }
+
 pub struct FolderNodePad {
     tree: Arc<AtomicNodeTree>,
+    // name: workspaces, index of the node,
     workspaces: Vec<Arc<WorkspaceNode>>,
     trash: Vec<Arc<TrashNode>>,
 }

+ 1 - 0
shared-lib/flowy-sync/src/client_folder/mod.rs

@@ -4,6 +4,7 @@ mod folder_node;
 mod folder_pad;
 mod view_node;
 mod workspace_node;
+mod workspace_node_2;
 
 pub use folder_node::*;
 pub use folder_pad::*;

+ 12 - 0
shared-lib/flowy-sync/src/client_folder/workspace_node_2.rs

@@ -0,0 +1,12 @@
+use crate::client_folder::AtomicNodeTree;
+use flowy_derive::Node;
+use std::sync::Arc;
+
+#[derive(Debug, Clone, Node)]
+pub struct WorkspaceNode2 {
+    tree: Arc<AtomicNodeTree>,
+    #[node]
+    pub id: String,
+    // pub name: String,
+    // pub path: Path,
+}

+ 7 - 1
shared-lib/flowy-sync/tests/client_folder/script.rs

@@ -5,6 +5,7 @@ use std::sync::Arc;
 pub enum FolderNodePadScript {
     CreateApp { id: String, name: String },
     DeleteApp { id: String },
+    UpdateApp { id: String, name: String },
     AssertApp { id: String, expected: Option<AppRevision> },
     AssertAppContent { id: String, name: String },
     AssertNumberOfApps { expected: usize },
@@ -58,7 +59,12 @@ impl FolderNodePadTest {
                 let workspace_node = Arc::make_mut(workspace_node);
                 workspace_node.remove_app(&id);
             }
-
+            FolderNodePadScript::UpdateApp { id, name } => {
+                let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap();
+                let workspace_node = Arc::make_mut(workspace_node);
+                let app_node = Arc::make_mut(workspace_node.get_mut_app(&id).unwrap());
+                app_node.set_name(name).unwrap();
+            }
             FolderNodePadScript::AssertApp { id, expected } => {
                 let workspace_node = self.folder_pad.get_workspace("1").unwrap();
                 let app = workspace_node.get_app(&id);

+ 19 - 0
shared-lib/flowy-sync/tests/client_folder/workspace_test.rs

@@ -32,3 +32,22 @@ fn client_folder_delete_app_test() {
         },
     ]);
 }
+
+#[test]
+fn client_folder_update_app_test() {
+    let mut test = FolderNodePadTest::new();
+    test.run_scripts(vec![
+        CreateApp {
+            id: "1".to_string(),
+            name: "my first app".to_string(),
+        },
+        UpdateApp {
+            id: "1".to_string(),
+            name: "TODO".to_string(),
+        },
+        AssertAppContent {
+            id: "1".to_string(),
+            name: "TODO".to_string(),
+        },
+    ]);
+}