Browse Source

config event template

appflowy 3 years ago
parent
commit
569da533a1

+ 10 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,10 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <Languages>
+        <language minSize="46" name="Rust" />
+      </Languages>
+    </inspection_tool>
+  </profile>
+</component>

+ 2 - 1
rust-lib/dart-ffi/Flowy.toml

@@ -1 +1,2 @@
-proto_crates = ["src/model"]
+proto_crates = ["src/model"]
+event_files = []

+ 72 - 2
rust-lib/flowy-ast/src/attr.rs

@@ -255,14 +255,22 @@ pub enum Default {
     Path(syn::ExprPath),
 }
 
+#[derive(Debug, Clone)]
+pub struct EventAttrs {
+    input: Option<syn::Path>,
+    output: Option<syn::Path>,
+    pub ignore: bool,
+}
+
 #[derive(Debug, Clone)]
 pub struct ASTEnumAttrVariant {
     pub name: String,
     pub value: String,
+    pub event_attrs: EventAttrs,
 }
 
 impl ASTEnumAttrVariant {
-    pub fn from_ast(_cx: &Ctxt, variant: &syn::Variant) -> Self {
+    pub fn from_ast(ctxt: &Ctxt, variant: &syn::Variant) -> Self {
         let name = variant.ident.to_string();
         let mut value = String::new();
         if variant.discriminant.is_some() {
@@ -278,8 +286,70 @@ impl ASTEnumAttrVariant {
                 _ => {},
             }
         }
-        ASTEnumAttrVariant { name, value }
+        let mut event_attrs = EventAttrs {
+            input: None,
+            output: None,
+            ignore: false,
+        };
+        variant.attrs.iter().for_each(|attr| match get_meta_items(ctxt, attr) {
+            Ok(meta_items) => {
+                for meta_item in meta_items {
+                    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(|_| {
+                                            ctxt.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(|_| {
+                                            ctxt.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) => {
+                            ctxt.error_spanned_by(s, "unexpected type in cqrs container attribute");
+                        },
+                        _ => {
+                            ctxt.error_spanned_by(meta_item, "unexpected type in cqrs container attribute");
+                        },
+                    }
+                }
+            },
+            Err(_) => {},
+        });
+        ASTEnumAttrVariant {
+            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 get_meta_items(cx: &Ctxt, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {

+ 35 - 0
rust-lib/flowy-ast/src/event_ast.rs

@@ -0,0 +1,35 @@
+use crate::ASTEnumAttrVariant;
+
+pub struct EventASTContext {
+    pub event: syn::Ident,
+    pub event_ty: syn::Ident,
+    pub event_request_struct: syn::Ident,
+    pub event_input: Option<syn::Path>,
+    pub event_output: Option<syn::Path>,
+}
+
+impl EventASTContext {
+    pub fn from(variant: &ASTEnumAttrVariant) -> EventASTContext {
+        let command_name = variant.name.clone();
+        if command_name.is_empty() {
+            panic!("Invalid command name: {}", variant.name);
+        }
+
+        let event = format_ident!("{}", &command_name);
+        let splits = command_name.split("_").collect::<Vec<&str>>();
+
+        let event_ty = format_ident!("UserEvent");
+        let event_request_struct = format_ident!("{}Event", &splits.join(""));
+
+        let event_input = variant.event_input();
+        let event_output = variant.event_output();
+
+        EventASTContext {
+            event,
+            event_ty,
+            event_request_struct,
+            event_input,
+            event_output,
+        }
+    }
+}

+ 2 - 1
rust-lib/flowy-ast/src/lib.rs

@@ -7,9 +7,10 @@ extern crate quote;
 mod ast;
 mod attr;
 mod ctxt;
+
+pub mod event_ast;
 pub mod symbol;
 pub mod ty_ext;
-
 pub use self::{symbol::*, ty_ext::*};
 pub use ast::*;
 pub use attr::*;

+ 5 - 0
rust-lib/flowy-ast/src/symbol.rs

@@ -14,6 +14,11 @@ pub const SKIP_SERIALIZING: Symbol = Symbol("skip_serializing"); //#[pb(skip_ser
 pub const PB_STRUCT: Symbol = Symbol("struct"); //#[pb(struct="some struct")]
 pub const PB_ENUM: Symbol = Symbol("enum"); //#[pb(enum="some enum")]
 
+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");
+
 impl PartialEq<Symbol> for Ident {
     fn eq(&self, word: &Symbol) -> bool { self == word.0 }
 }

+ 2 - 1
rust-lib/flowy-user/Flowy.toml

@@ -1,2 +1,3 @@
 
-proto_crates = ["src/domain"]
+proto_crates = ["src/domain"]
+event_files = ["src/module.rs"]

+ 1 - 0
scripts/flowy-tool/src/config/mod.rs

@@ -3,6 +3,7 @@ use std::fs;
 #[derive(serde::Deserialize)]
 pub struct FlowyConfig {
     pub proto_crates: Vec<String>,
+    pub event_files: Vec<String>,
 }
 
 impl FlowyConfig {

+ 122 - 0
scripts/flowy-tool/src/dart_event/dart_event.rs

@@ -0,0 +1,122 @@
+use super::event_template::*;
+
+use crate::util::*;
+use flowy_ast::{event_ast::*, *};
+use syn::Item;
+use walkdir::WalkDir;
+
+pub struct DartEventCodeGen {
+    pub rust_source: String,
+    pub output_dir: String,
+}
+
+impl DartEventCodeGen {
+    pub fn gen(&self) {
+        let event_crates = parse_dart_event_files(self.rust_source.as_ref());
+        let event_ast = event_crates
+            .iter()
+            .map(|event_crate| parse_event_crate(event_crate))
+            .flatten()
+            .collect::<Vec<_>>();
+
+        let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref());
+
+        let mut render_result = String::new();
+        for (index, render_ctx) in event_render_ctx.into_iter().enumerate() {
+            let mut event_template = EventTemplate::new();
+
+            match event_template.render(render_ctx, index) {
+                Some(content) => render_result.push_str(content.as_ref()),
+                None => {}
+            }
+        }
+
+        save_content_to_file_with_diff_prompt(
+            render_result.as_ref(),
+            self.output_dir.as_str(),
+            true,
+        );
+    }
+}
+
+pub struct DartEventCrate {
+    crate_path: String,
+    crate_name: String,
+    event_files: Vec<String>,
+}
+
+impl DartEventCrate {
+    pub fn from_config(config: &CrateConfig) -> Self {
+        DartEventCrate {
+            crate_path: config.crate_path.clone(),
+            crate_name: config.folder_name.clone(),
+            event_files: config.flowy_config.event_files.clone(),
+        }
+    }
+}
+
+pub fn parse_dart_event_files(root: &str) -> Vec<DartEventCrate> {
+    WalkDir::new(root)
+        .into_iter()
+        .filter_entry(|e| !is_hidden(e))
+        .filter_map(|e| e.ok())
+        .filter(|e| is_crate_dir(e))
+        .flat_map(|e| parse_crate_config_from(&e))
+        .map(|crate_config| DartEventCrate::from_config(&crate_config))
+        .collect::<Vec<DartEventCrate>>()
+}
+
+pub fn parse_event_crate(event_crate: &DartEventCrate) -> Vec<EventASTContext> {
+    event_crate
+        .event_files
+        .iter()
+        .map(|event_file| {
+            let file_path = format!("{}/{}", event_crate.crate_path, event_file);
+            let file_content = read_file(file_path.as_ref()).unwrap();
+            let ast = syn::parse_file(file_content.as_ref()).expect("Unable to parse file");
+
+            ast.items
+                .iter()
+                .map(|item| match item {
+                    Item::Enum(item_enum) => {
+                        let ctxt = Ctxt::new();
+                        let attrs = flowy_ast::enum_from_ast(&ctxt, &item_enum.variants);
+                        ctxt.check().unwrap();
+                        attrs
+                            .iter()
+                            .filter(|attr| attr.attrs.event_attrs.ignore == false)
+                            .enumerate()
+                            .map(|(_index, attr)| EventASTContext::from(&attr.attrs))
+                            .collect::<Vec<_>>()
+                    }
+                    _ => vec![],
+                })
+                .flatten()
+                .collect::<Vec<_>>()
+        })
+        .flatten()
+        .collect::<Vec<EventASTContext>>()
+}
+
+pub fn ast_to_event_render_ctx(ast: &Vec<EventASTContext>) -> Vec<EventRenderContext> {
+    ast.iter()
+        .map(|event_ast| EventRenderContext {
+            input_deserializer: event_ast
+                .event_input
+                .as_ref()
+                .unwrap()
+                .get_ident()
+                .unwrap()
+                .to_string(),
+            output_deserializer: event_ast
+                .event_output
+                .as_ref()
+                .unwrap()
+                .get_ident()
+                .unwrap()
+                .to_string(),
+            event: event_ast.event.to_string(),
+            event_ty: event_ast.event_ty.to_string(),
+        })
+        .collect::<Vec<EventRenderContext>>()
+}

+ 25 - 0
scripts/flowy-tool/src/dart_event/event_template.rs

@@ -0,0 +1,25 @@
+use tera::Context;
+
+pub struct EventTemplate {
+    tera_context: Context,
+}
+
+pub struct EventRenderContext {
+    pub input_deserializer: String,
+    pub output_deserializer: String,
+    pub event: String,
+    pub event_ty: String,
+}
+
+#[allow(dead_code)]
+impl EventTemplate {
+    pub fn new() -> Self {
+        return EventTemplate {
+            tera_context: Context::new(),
+        };
+    }
+
+    pub fn render(&mut self, _render_context: EventRenderContext, _index: usize) -> Option<String> {
+        None
+    }
+}

+ 0 - 0
scripts/flowy-tool/src/dart_event/event_template.tera


+ 4 - 0
scripts/flowy-tool/src/dart_event/mod.rs

@@ -0,0 +1,4 @@
+mod dart_event;
+mod event_template;
+
+pub use dart_event::*;

+ 27 - 0
scripts/flowy-tool/src/main.rs

@@ -1,4 +1,5 @@
 mod config;
+mod dart_event;
 mod proto;
 mod util;
 
@@ -22,6 +23,17 @@ fn main() {
             .build()
             .gen();
     }
+
+    if let Some(ref matches) = matches.subcommand_matches("dart-event") {
+        let rust_source = matches.value_of("rust_source").unwrap().to_string();
+        let output_dir = matches.value_of("output").unwrap().to_string();
+
+        let code_gen = dart_event::DartEventCodeGen {
+            rust_source,
+            output_dir,
+        };
+        code_gen.gen();
+    }
 }
 
 pub fn app<'a, 'b>() -> App<'a, 'b> {
@@ -49,6 +61,21 @@ pub fn app<'a, 'b>() -> App<'a, 'b> {
                         .long("flutter_package_lib")
                         .value_name("DIRECTORY"),
                 ),
+        )
+        .subcommand(
+            App::new("dart-event")
+                .about("Generate the codes that sending events from rust ast")
+                .arg(
+                    Arg::with_name("rust_source")
+                        .long("rust_source")
+                        .value_name("DIRECTORY")
+                        .help("Directory of the cargo workspace"),
+                )
+                .arg(
+                    Arg::with_name("output")
+                        .long("output")
+                        .value_name("DIRECTORY"),
+                ),
         );
 
     app

+ 6 - 7
scripts/flowy-tool/src/proto/ast.rs

@@ -1,5 +1,4 @@
-use crate::proto::crate_info::*;
-use crate::proto::helper::*;
+use crate::proto::proto_info::*;
 use crate::proto::template::{EnumTemplate, StructTemplate};
 use crate::util::*;
 use fancy_regex::Regex;
@@ -16,19 +15,19 @@ pub fn parse_crate_protobuf(root: &str) -> Vec<CrateProtoInfo> {
         .map(|crate_info| {
             let proto_output_dir = crate_info.proto_file_output_dir();
             let files = crate_info
-                .proto_crate_paths
+                .proto_paths
                 .iter()
                 .map(|proto_crate_path| parse_files_protobuf(proto_crate_path, &proto_output_dir))
                 .flatten()
-                .collect::<Vec<FileProtoInfo>>();
+                .collect::<Vec<ProtoFile>>();
 
             CrateProtoInfo::from_crate_info(crate_info, files)
         })
         .collect::<Vec<CrateProtoInfo>>()
 }
 
-fn parse_files_protobuf(proto_crate_path: &str, proto_output_dir: &str) -> Vec<FileProtoInfo> {
-    let mut gen_proto_vec: Vec<FileProtoInfo> = vec![];
+fn parse_files_protobuf(proto_crate_path: &str, proto_output_dir: &str) -> Vec<ProtoFile> {
+    let mut gen_proto_vec: Vec<ProtoFile> = vec![];
     // file_stem https://doc.rust-lang.org/std/path/struct.Path.html#method.file_stem
     for (path, file_name) in WalkDir::new(proto_crate_path)
         .into_iter()
@@ -78,7 +77,7 @@ fn parse_files_protobuf(proto_crate_path: &str, proto_output_dir: &str) -> Vec<F
         });
 
         if !enums.is_empty() || !structs.is_empty() {
-            let info = FileProtoInfo {
+            let info = ProtoFile {
                 file_name: file_name.clone(),
                 structs: structs.iter().map(|s| s.name.clone()).collect(),
                 enums: enums.iter().map(|e| e.name.clone()).collect(),

+ 0 - 26
scripts/flowy-tool/src/proto/helper.rs

@@ -1,26 +0,0 @@
-pub fn is_crate_dir(e: &walkdir::DirEntry) -> bool {
-    let cargo = e.path().file_stem().unwrap().to_str().unwrap().to_string();
-    cargo == "Cargo".to_string()
-}
-
-pub fn is_proto_file(e: &walkdir::DirEntry) -> bool {
-    if e.path().extension().is_none() {
-        return false;
-    }
-    let ext = e.path().extension().unwrap().to_str().unwrap().to_string();
-    ext == "proto".to_string()
-}
-
-pub fn is_hidden(entry: &walkdir::DirEntry) -> bool {
-    entry
-        .file_name()
-        .to_str()
-        .map(|s| s.starts_with("."))
-        .unwrap_or(false)
-}
-
-pub fn create_dir_if_not_exist(dir: &str) {
-    if !std::path::Path::new(&dir).exists() {
-        std::fs::create_dir_all(&dir).unwrap();
-    }
-}

+ 1 - 2
scripts/flowy-tool/src/proto/mod.rs

@@ -1,8 +1,7 @@
 mod ast;
 mod builder;
-mod crate_info;
-mod helper;
 mod proto_gen;
+mod proto_info;
 mod template;
 
 pub use builder::*;

+ 3 - 25
scripts/flowy-tool/src/proto/proto_gen.rs

@@ -1,9 +1,7 @@
 use crate::proto::ast::*;
-use crate::proto::crate_info::*;
-use crate::proto::helper::*;
+use crate::proto::proto_info::*;
 use crate::{proto::template::*, util::*};
 use std::{fs::OpenOptions, io::Write};
-use walkdir::WalkDir;
 
 pub struct ProtoGen {
     pub(crate) rust_source_dir: String,
@@ -77,7 +75,7 @@ fn write_flutter_protobuf_package_mod_file(
     package_info: &FlutterProtobufInfo,
 ) {
     let mod_path = package_info.mod_file_path();
-    let model_dir = package_info.model_dir();
+    let _model_dir = package_info.model_dir();
     match OpenOptions::new()
         .create(true)
         .write(true)
@@ -90,7 +88,7 @@ fn write_flutter_protobuf_package_mod_file(
             mod_file_content.push_str("// Auto-generated, do not edit \n");
 
             for crate_info in crate_infos {
-                let mod_path = crate_info.inner.proto_model_mod_file();
+                let _mod_path = crate_info.inner.proto_model_mod_file();
                 walk_dir(
                     crate_info.inner.proto_file_output_dir().as_ref(),
                     |e| e.file_type().is_dir() == false,
@@ -151,23 +149,3 @@ fn run_flutter_protoc(crate_infos: &Vec<CrateProtoInfo>, package_info: &FlutterP
         );
     }
 }
-
-fn walk_dir<F1, F2>(dir: &str, filter: F2, mut path_and_name: F1)
-where
-    F1: FnMut(String, String),
-    F2: Fn(&walkdir::DirEntry) -> bool,
-{
-    for (path, name) in WalkDir::new(dir)
-        .into_iter()
-        .filter_map(|e| e.ok())
-        .filter(|e| filter(e))
-        .map(|e| {
-            (
-                e.path().to_str().unwrap().to_string(),
-                e.path().file_stem().unwrap().to_str().unwrap().to_string(),
-            )
-        })
-    {
-        path_and_name(path, name);
-    }
-}

+ 47 - 61
scripts/flowy-tool/src/proto/crate_info.rs → scripts/flowy-tool/src/proto/proto_info.rs

@@ -1,45 +1,15 @@
-use crate::config::FlowyConfig;
-use crate::proto::helper::*;
+use crate::util::*;
 use std::fs::OpenOptions;
 use std::io::Write;
 use walkdir::WalkDir;
 
-#[derive(Clone)]
-pub struct CrateInfo {
-    pub crate_folder_name: String,
-    pub proto_crate_paths: Vec<String>,
-    pub crate_path: String,
-}
-
 pub struct CrateProtoInfo {
-    pub files: Vec<FileProtoInfo>,
-    pub inner: CrateInfo,
-}
-
-impl CrateInfo {
-    fn protobuf_crate_name(&self) -> String {
-        format!("{}/src/protobuf", self.crate_path)
-    }
-
-    pub fn proto_file_output_dir(&self) -> String {
-        let dir = format!("{}/proto", self.protobuf_crate_name());
-        create_dir_if_not_exist(dir.as_ref());
-        dir
-    }
-
-    pub fn proto_struct_output_dir(&self) -> String {
-        let dir = format!("{}/model", self.protobuf_crate_name());
-        create_dir_if_not_exist(dir.as_ref());
-        dir
-    }
-
-    pub fn proto_model_mod_file(&self) -> String {
-        format!("{}/mod.rs", self.proto_struct_output_dir())
-    }
+    pub files: Vec<ProtoFile>,
+    pub inner: ProtobufCrate,
 }
 
 impl CrateProtoInfo {
-    pub fn from_crate_info(inner: CrateInfo, files: Vec<FileProtoInfo>) -> Self {
+    pub fn from_crate_info(inner: ProtobufCrate, files: Vec<ProtoFile>) -> Self {
         Self { files, inner }
     }
 
@@ -68,45 +38,61 @@ pub use model::*;
     }
 }
 
+#[derive(Clone, Debug)]
+pub struct ProtobufCrate {
+    pub folder_name: String,
+    pub proto_paths: Vec<String>,
+    pub crate_path: String,
+}
+
+impl ProtobufCrate {
+    pub fn from_config(config: CrateConfig) -> Self {
+        let proto_paths = config.proto_paths();
+        ProtobufCrate {
+            folder_name: config.folder_name,
+            proto_paths,
+            crate_path: config.crate_path,
+        }
+    }
+
+    fn protobuf_crate_name(&self) -> String {
+        format!("{}/src/protobuf", self.crate_path)
+    }
+
+    pub fn proto_file_output_dir(&self) -> String {
+        let dir = format!("{}/proto", self.protobuf_crate_name());
+        create_dir_if_not_exist(dir.as_ref());
+        dir
+    }
+
+    pub fn proto_struct_output_dir(&self) -> String {
+        let dir = format!("{}/model", self.protobuf_crate_name());
+        create_dir_if_not_exist(dir.as_ref());
+        dir
+    }
+
+    pub fn proto_model_mod_file(&self) -> String {
+        format!("{}/mod.rs", self.proto_struct_output_dir())
+    }
+}
+
 #[derive(Debug)]
-pub struct FileProtoInfo {
+pub struct ProtoFile {
     pub file_name: String,
     pub structs: Vec<String>,
     pub enums: Vec<String>,
     pub generated_content: String,
 }
 
-pub fn parse_crate_info_from_path(root: &str) -> Vec<CrateInfo> {
+pub fn parse_crate_info_from_path(root: &str) -> Vec<ProtobufCrate> {
     WalkDir::new(root)
         .into_iter()
         .filter_entry(|e| !is_hidden(e))
         .filter_map(|e| e.ok())
         .filter(|e| is_crate_dir(e))
-        .flat_map(|e| {
-            // Assert e.path().parent() will be the crate dir
-            let path = e.path().parent().unwrap();
-            let crate_path = path.to_str().unwrap().to_string();
-            let crate_folder_name = path.file_stem().unwrap().to_str().unwrap().to_string();
-            let flowy_config_file = format!("{}/Flowy.toml", crate_path);
-
-            if std::path::Path::new(&flowy_config_file).exists() {
-                let config = FlowyConfig::from_toml_file(flowy_config_file.as_ref());
-                let crate_path = path.to_str().unwrap().to_string();
-                let proto_crate_paths = config
-                    .proto_crates
-                    .iter()
-                    .map(|name| format!("{}/{}", crate_path, name))
-                    .collect::<Vec<String>>();
-                Some(CrateInfo {
-                    crate_folder_name,
-                    proto_crate_paths,
-                    crate_path,
-                })
-            } else {
-                None
-            }
-        })
-        .collect::<Vec<CrateInfo>>()
+        .flat_map(|e| parse_crate_config_from(&e))
+        .map(|crate_config| ProtobufCrate::from_config(crate_config))
+        .collect::<Vec<ProtobufCrate>>()
 }
 
 pub struct FlutterProtobufInfo {

+ 2 - 2
scripts/flowy-tool/src/proto/template/derive_meta/derive_meta.rs

@@ -1,4 +1,4 @@
-use crate::proto::crate_info::{CrateProtoInfo, FileProtoInfo};
+use crate::proto::proto_info::{CrateProtoInfo, ProtoFile};
 use crate::util::{get_tera, read_file};
 use std::fs::OpenOptions;
 use std::io::Write;
@@ -40,7 +40,7 @@ pub fn write_derive_meta(crate_infos: &Vec<CrateProtoInfo>, derive_meta_dir: &st
         .iter()
         .map(|ref crate_info| &crate_info.files)
         .flatten()
-        .collect::<Vec<&FileProtoInfo>>();
+        .collect::<Vec<&ProtoFile>>();
 
     let structs: Vec<String> = file_proto_infos
         .iter()

+ 38 - 0
scripts/flowy-tool/src/util/crate_config.rs

@@ -0,0 +1,38 @@
+use crate::config::FlowyConfig;
+
+pub struct CrateConfig {
+    pub(crate) crate_path: String,
+    pub(crate) folder_name: String,
+    pub(crate) flowy_config: FlowyConfig,
+}
+
+impl CrateConfig {
+    pub fn proto_paths(&self) -> Vec<String> {
+        let proto_paths = self
+            .flowy_config
+            .proto_crates
+            .iter()
+            .map(|name| format!("{}/{}", self.crate_path, name))
+            .collect::<Vec<String>>();
+        proto_paths
+    }
+}
+
+pub fn parse_crate_config_from(entry: &walkdir::DirEntry) -> Option<CrateConfig> {
+    let path = entry.path().parent().unwrap();
+    let crate_path = path.to_str().unwrap().to_string();
+    let folder_name = path.file_stem().unwrap().to_str().unwrap().to_string();
+    let config_path = format!("{}/Flowy.toml", crate_path);
+
+    if std::path::Path::new(&config_path).exists() {
+        return None;
+    }
+
+    let flowy_config = FlowyConfig::from_toml_file(config_path.as_ref());
+
+    Some(CrateConfig {
+        crate_path,
+        folder_name,
+        flowy_config,
+    })
+}

+ 50 - 2
scripts/flowy-tool/src/util/file.rs

@@ -1,5 +1,5 @@
 use console::Style;
-use dialoguer::Confirm;
+
 use similar::{ChangeTag, TextDiff};
 use std::{
     fs::{File, OpenOptions},
@@ -7,6 +7,7 @@ use std::{
     path::Path,
 };
 use tera::Tera;
+use walkdir::WalkDir;
 
 pub fn read_file(path: &str) -> Option<String> {
     let mut file = File::open(path).expect("Unable to open file");
@@ -20,7 +21,7 @@ pub fn read_file(path: &str) -> Option<String> {
     }
 }
 
-pub fn save_content_to_file_with_diff_prompt(content: &str, output_file: &str, force_write: bool) {
+pub fn save_content_to_file_with_diff_prompt(content: &str, output_file: &str, _force_write: bool) {
     if Path::new(output_file).exists() {
         let old_content = read_file(output_file).unwrap();
         let new_content = content.to_owned();
@@ -106,3 +107,50 @@ pub fn get_tera(directory: &str) -> Tera {
         }
     }
 }
+
+pub fn is_crate_dir(e: &walkdir::DirEntry) -> bool {
+    let cargo = e.path().file_stem().unwrap().to_str().unwrap().to_string();
+    cargo == "Cargo".to_string()
+}
+
+pub fn is_proto_file(e: &walkdir::DirEntry) -> bool {
+    if e.path().extension().is_none() {
+        return false;
+    }
+    let ext = e.path().extension().unwrap().to_str().unwrap().to_string();
+    ext == "proto".to_string()
+}
+
+pub fn is_hidden(entry: &walkdir::DirEntry) -> bool {
+    entry
+        .file_name()
+        .to_str()
+        .map(|s| s.starts_with("."))
+        .unwrap_or(false)
+}
+
+pub fn create_dir_if_not_exist(dir: &str) {
+    if !std::path::Path::new(&dir).exists() {
+        std::fs::create_dir_all(&dir).unwrap();
+    }
+}
+
+pub(crate) fn walk_dir<F1, F2>(dir: &str, filter: F2, mut path_and_name: F1)
+where
+    F1: FnMut(String, String),
+    F2: Fn(&walkdir::DirEntry) -> bool,
+{
+    for (path, name) in WalkDir::new(dir)
+        .into_iter()
+        .filter_map(|e| e.ok())
+        .filter(|e| filter(e))
+        .map(|e| {
+            (
+                e.path().to_str().unwrap().to_string(),
+                e.path().file_stem().unwrap().to_str().unwrap().to_string(),
+            )
+        })
+    {
+        path_and_name(path, name);
+    }
+}

+ 2 - 0
scripts/flowy-tool/src/util/mod.rs

@@ -1,3 +1,5 @@
+mod crate_config;
 mod file;
 
+pub use crate_config::*;
 pub use file::*;