Переглянути джерело

refactor: generate x.proto file using build.rs

appflowy 3 роки тому
батько
коміт
7646f984e0
35 змінених файлів з 1971 додано та 62 видалено
  1. 2 1
      frontend/Makefile.toml
  2. 467 6
      frontend/rust-lib/Cargo.lock
  3. 2 2
      frontend/rust-lib/dart-ffi/build.rs
  4. 2 2
      frontend/rust-lib/dart-notify/build.rs
  5. 2 2
      frontend/rust-lib/flowy-error/build.rs
  6. 2 2
      frontend/rust-lib/flowy-folder/build.rs
  7. 2 2
      frontend/rust-lib/flowy-net/build.rs
  8. 4 1
      frontend/rust-lib/flowy-sdk/Cargo.toml
  9. 9 0
      frontend/rust-lib/flowy-sdk/build.rs
  10. 2 2
      frontend/rust-lib/flowy-user/build.rs
  11. 2 2
      frontend/scripts/flowy-tool/src/proto/proto_gen.rs
  12. 480 6
      shared-lib/Cargo.lock
  13. 2 2
      shared-lib/error-code/build.rs
  14. 2 2
      shared-lib/flowy-collaboration/build.rs
  15. 2 2
      shared-lib/flowy-folder-data-model/build.rs
  16. 2 2
      shared-lib/flowy-user-data-model/build.rs
  17. 26 0
      shared-lib/lib-infra/Cargo.toml
  18. 4 1
      shared-lib/lib-infra/src/lib.rs
  19. 54 23
      shared-lib/lib-infra/src/pb.rs
  20. 180 0
      shared-lib/lib-infra/src/proto_gen/ast.rs
  21. 52 0
      shared-lib/lib-infra/src/proto_gen/flowy_toml.rs
  22. 8 0
      shared-lib/lib-infra/src/proto_gen/mod.rs
  23. 63 0
      shared-lib/lib-infra/src/proto_gen/proto_gen.rs
  24. 142 0
      shared-lib/lib-infra/src/proto_gen/proto_info.rs
  25. 79 0
      shared-lib/lib-infra/src/proto_gen/template/derive_meta/derive_meta.rs
  26. 45 0
      shared-lib/lib-infra/src/proto_gen/template/derive_meta/derive_meta.tera
  27. 4 0
      shared-lib/lib-infra/src/proto_gen/template/derive_meta/mod.rs
  28. 40 0
      shared-lib/lib-infra/src/proto_gen/template/mod.rs
  29. 5 0
      shared-lib/lib-infra/src/proto_gen/template/proto_file/enum.tera
  30. 38 0
      shared-lib/lib-infra/src/proto_gen/template/proto_file/enum_template.rs
  31. 5 0
      shared-lib/lib-infra/src/proto_gen/template/proto_file/mod.rs
  32. 5 0
      shared-lib/lib-infra/src/proto_gen/template/proto_file/struct.tera
  33. 107 0
      shared-lib/lib-infra/src/proto_gen/template/proto_file/struct_template.rs
  34. 128 0
      shared-lib/lib-infra/src/proto_gen/util.rs
  35. 2 2
      shared-lib/lib-ws/build.rs

+ 2 - 1
frontend/Makefile.toml

@@ -22,6 +22,7 @@ CRATE_TYPE = "staticlib"
 SDK_EXT = "a"
 APP_ENVIRONMENT = "local"
 FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk/lib/protobuf"
+PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
 
 [env.development-mac]
 RUST_LOG = "trace"
@@ -157,7 +158,7 @@ script_runner = "@duckscript"
 condition = { env_set = [ "FLUTTER_FLOWY_SDK_PATH"] }
 script = [
     """
-      cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/dart-ffi
+      cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-folder
       cargo build -vv
       """,
 ]

+ 467 - 6
frontend/rust-lib/Cargo.lock

@@ -166,13 +166,34 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.5",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
 ]
 
 [[package]]
@@ -193,6 +214,12 @@ version = "3.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
 
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
 [[package]]
 name = "bytecount"
 version = "0.6.2"
@@ -248,6 +275,28 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "chrono-tz"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf 0.10.1",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069"
+dependencies = [
+ "parse-zoneinfo",
+ "phf 0.10.1",
+ "phf_codegen",
+]
+
 [[package]]
 name = "claim"
 version = "0.4.0"
@@ -327,6 +376,21 @@ dependencies = [
  "yaml-rust",
 ]
 
+[[package]]
+name = "console"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "regex",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.4.0"
@@ -588,6 +652,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "deunicode"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
+
 [[package]]
 name = "diesel"
 version = "1.4.8"
@@ -620,13 +690,22 @@ dependencies = [
  "migrations_macros",
 ]
 
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
 [[package]]
 name = "digest"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.5",
 ]
 
 [[package]]
@@ -647,6 +726,12 @@ version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.30"
@@ -728,6 +813,12 @@ dependencies = [
  "rand 0.7.3",
 ]
 
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
 [[package]]
 name = "fancy-regex"
 version = "0.5.0"
@@ -1239,6 +1330,15 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
 [[package]]
 name = "generic-array"
 version = "0.14.5"
@@ -1287,6 +1387,30 @@ version = "0.26.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
 
+[[package]]
+name = "globset"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags",
+ "ignore",
+ "walkdir",
+]
+
 [[package]]
 name = "h2"
 version = "0.3.10"
@@ -1388,6 +1512,12 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
 
+[[package]]
+name = "humansize"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
+
 [[package]]
 name = "humantime"
 version = "2.1.0"
@@ -1448,6 +1578,24 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "ignore"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
+dependencies = [
+ "crossbeam-utils",
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
 [[package]]
 name = "indenter"
 version = "0.3.3"
@@ -1563,12 +1711,23 @@ dependencies = [
  "bytes",
  "chrono",
  "cmd_lib",
+ "console",
+ "fancy-regex",
+ "flowy-ast",
  "futures-core",
+ "itertools",
+ "lazy_static",
  "log",
+ "phf 0.8.0",
  "pin-project",
  "protoc-rust",
  "rand 0.8.4",
+ "serde",
+ "similar",
+ "syn",
+ "tera",
  "tokio",
+ "toml",
  "uuid",
  "walkdir",
 ]
@@ -1693,6 +1852,12 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
 [[package]]
 name = "matchers"
 version = "0.0.1"
@@ -1876,6 +2041,12 @@ version = "11.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
 [[package]]
 name = "opaque-debug"
 version = "0.3.0"
@@ -1956,6 +2127,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
+dependencies = [
+ "regex",
+]
+
 [[package]]
 name = "paste"
 version = "1.0.6"
@@ -1968,6 +2148,132 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
 
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+dependencies = [
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1 0.8.2",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_macros",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared 0.8.0",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand 0.8.4",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+ "uncased",
+]
+
 [[package]]
 name = "pin-project"
 version = "1.0.10"
@@ -2064,6 +2370,12 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.36"
@@ -2164,6 +2476,7 @@ dependencies = [
  "rand_chacha 0.2.2",
  "rand_core 0.5.1",
  "rand_hc 0.2.0",
+ "rand_pcg",
 ]
 
 [[package]]
@@ -2234,6 +2547,15 @@ dependencies = [
  "rand_core 0.6.3",
 ]
 
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
 [[package]]
 name = "rayon"
 version = "1.5.1"
@@ -2555,17 +2877,29 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
 [[package]]
 name = "sha-1"
 version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.9.0",
  "cfg-if",
  "cpufeatures",
- "digest",
- "opaque-debug",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
 ]
 
 [[package]]
@@ -2586,12 +2920,33 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "similar"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
+
+[[package]]
+name = "siphasher"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e"
+
 [[package]]
 name = "slab"
 version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
 
+[[package]]
+name = "slug"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
+dependencies = [
+ "deunicode",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.7.0"
@@ -2663,6 +3018,28 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "tera"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3cac831b615c25bcef632d1cabf864fa05813baad3d526829db18eb70e8b58d"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding",
+ "pest",
+ "pest_derive",
+ "rand 0.8.4",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unic-segment",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.1.2"
@@ -2672,6 +3049,16 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "textwrap"
 version = "0.11.0"
@@ -2824,6 +3211,15 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "tower-service"
 version = "0.3.1"
@@ -2963,7 +3359,7 @@ dependencies = [
  "httparse",
  "log",
  "rand 0.8.4",
- "sha-1",
+ "sha-1 0.9.8",
  "thiserror",
  "url",
  "utf-8",
@@ -2975,6 +3371,71 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+
+[[package]]
+name = "uncased"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.7"

+ 2 - 2
frontend/rust-lib/dart-ffi/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("dart-ffi", "./src/protobuf/proto");
+    pb::gen_files("dart-ffi", "./src/protobuf/proto");
 }

+ 2 - 2
frontend/rust-lib/dart-notify/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("dart-notify", "./src/protobuf/proto");
+    pb::gen_files("dart-notify", "./src/protobuf/proto");
 }

+ 2 - 2
frontend/rust-lib/flowy-error/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-error", "./src/protobuf/proto");
+    pb::gen_files("flowy-error", "./src/protobuf/proto");
 }

+ 2 - 2
frontend/rust-lib/flowy-folder/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-folder", "./src/protobuf/proto");
+    pb::gen_files("flowy-folder", "./src/protobuf/proto");
 }

+ 2 - 2
frontend/rust-lib/flowy-net/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-net", "./src/protobuf/proto");
+    pb::gen_files("flowy-net", "./src/protobuf/proto");
 }

+ 4 - 1
frontend/rust-lib/flowy-sdk/Cargo.toml

@@ -39,4 +39,7 @@ futures-util = "0.3.15"
 [features]
 http_server = ["flowy-user/http_server", "flowy-folder/http_server", "flowy-document/http_server"]
 use_bunyan = ["lib-log/use_bunyan"]
-dart = ["flowy-user/dart", "flowy-net/dart", "flowy-folder/dart", "flowy-collaboration/dart"]
+dart = ["flowy-user/dart", "flowy-net/dart", "flowy-folder/dart", "flowy-collaboration/dart"]
+
+[build-dependencies]
+lib-infra = { path = "../../../shared-lib/lib-infra", features = ["proto_gen"] }

+ 9 - 0
frontend/rust-lib/flowy-sdk/build.rs

@@ -0,0 +1,9 @@
+// use lib_infra::proto_gen;
+
+fn main() {
+    // let derive_meta_dir = format!(
+    //     "{}/{}",
+    //     env!("CARGO_MAKE_WORKING_DIRECTORY"),
+    //     env!("PROTOBUF_DERIVE_CACHE"),
+    // );
+}

+ 2 - 2
frontend/rust-lib/flowy-user/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-user", "./src/protobuf/proto");
+    pb::gen_files("flowy-user", "./src/protobuf/proto");
 }

+ 2 - 2
frontend/scripts/flowy-tool/src/proto/proto_gen.rs

@@ -19,10 +19,10 @@ impl ProtoGen {
     pub fn gen(&self) {
         let crate_proto_infos = parse_crate_protobuf(self.rust_source_dirs.clone());
         write_proto_files(&crate_proto_infos);
+        run_rust_protoc(&crate_proto_infos);
 
-        // run_rust_protoc(&crate_proto_infos);
         // write_rust_crate_mod_file(&crate_proto_infos);
-        // write_derive_meta(&crate_proto_infos, self.derive_meta_dir.as_ref());
+        write_derive_meta(&crate_proto_infos, self.derive_meta_dir.as_ref());
 
         // let flutter_package = FlutterProtobufInfo::new(self.flutter_package_lib.as_ref());
         // run_flutter_protoc(&crate_proto_infos, &flutter_package);

+ 480 - 6
shared-lib/Cargo.lock

@@ -82,15 +82,51 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.4",
+]
+
 [[package]]
 name = "block-buffer"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.4",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "memchr",
 ]
 
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
 [[package]]
 name = "bytecount"
 version = "0.6.2"
@@ -128,6 +164,28 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "chrono-tz"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552"
+dependencies = [
+ "chrono",
+ "chrono-tz-build",
+ "phf 0.10.1",
+]
+
+[[package]]
+name = "chrono-tz-build"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069"
+dependencies = [
+ "parse-zoneinfo",
+ "phf 0.10.1",
+ "phf_codegen",
+]
+
 [[package]]
 name = "claim"
 version = "0.4.0"
@@ -162,6 +220,21 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "console"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "regex",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
 [[package]]
 name = "convert_case"
 version = "0.4.0"
@@ -177,6 +250,16 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
+dependencies = [
+ "cfg-if",
+ "lazy_static",
+]
+
 [[package]]
 name = "dashmap"
 version = "4.0.2"
@@ -200,13 +283,28 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "deunicode"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.4",
+]
+
 [[package]]
 name = "digest"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.4",
 ]
 
 [[package]]
@@ -221,6 +319,12 @@ version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
 
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
 [[package]]
 name = "env_logger"
 version = "0.7.1"
@@ -274,6 +378,12 @@ dependencies = [
  "rand 0.7.3",
 ]
 
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
 [[package]]
 name = "fancy-regex"
 version = "0.5.0"
@@ -496,6 +606,15 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "generic-array"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
+dependencies = [
+ "typenum",
+]
+
 [[package]]
 name = "generic-array"
 version = "0.14.4"
@@ -534,6 +653,30 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 
+[[package]]
+name = "globset"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "globwalk"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
+dependencies = [
+ "bitflags",
+ "ignore",
+ "walkdir",
+]
+
 [[package]]
 name = "heck"
 version = "0.3.3"
@@ -569,6 +712,12 @@ version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
 
+[[package]]
+name = "humansize"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
+
 [[package]]
 name = "humantime"
 version = "2.1.0"
@@ -586,6 +735,24 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "ignore"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
+dependencies = [
+ "crossbeam-utils",
+ "globset",
+ "lazy_static",
+ "log",
+ "memchr",
+ "regex",
+ "same-file",
+ "thread_local",
+ "walkdir",
+ "winapi-util",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -595,6 +762,15 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.8"
@@ -614,12 +790,23 @@ dependencies = [
  "bytes",
  "chrono",
  "cmd_lib",
+ "console",
+ "fancy-regex",
+ "flowy-ast",
  "futures-core",
+ "itertools",
+ "lazy_static",
  "log",
+ "phf 0.8.0",
  "pin-project",
  "protoc-rust",
  "rand 0.8.4",
+ "serde",
+ "similar",
+ "syn",
+ "tera",
  "tokio",
+ "toml",
  "uuid",
  "walkdir",
 ]
@@ -695,6 +882,12 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
 [[package]]
 name = "matches"
 version = "0.1.9"
@@ -779,6 +972,12 @@ version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
 
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
 [[package]]
 name = "opaque-debug"
 version = "0.3.0"
@@ -820,6 +1019,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "parse-zoneinfo"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
+dependencies = [
+ "regex",
+]
+
 [[package]]
 name = "paste"
 version = "1.0.6"
@@ -841,6 +1049,123 @@ dependencies = [
  "ucd-trie",
 ]
 
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+dependencies = [
+ "maplit",
+ "pest",
+ "sha-1 0.8.2",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_macros",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared 0.8.0",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand 0.8.4",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
+dependencies = [
+ "phf_generator 0.8.0",
+ "phf_shared 0.8.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+ "uncased",
+]
+
 [[package]]
 name = "pin-project"
 version = "1.0.8"
@@ -1004,6 +1329,7 @@ dependencies = [
  "rand_chacha 0.2.2",
  "rand_core 0.5.1",
  "rand_hc 0.2.0",
+ "rand_pcg",
 ]
 
 [[package]]
@@ -1074,6 +1400,15 @@ dependencies = [
  "rand_core 0.6.3",
 ]
 
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.10"
@@ -1210,17 +1545,29 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+dependencies = [
+ "block-buffer 0.7.3",
+ "digest 0.8.1",
+ "fake-simd",
+ "opaque-debug 0.2.3",
+]
+
 [[package]]
 name = "sha-1"
 version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.9.0",
  "cfg-if",
  "cpufeatures",
- "digest",
- "opaque-debug",
+ "digest 0.9.0",
+ "opaque-debug 0.3.0",
 ]
 
 [[package]]
@@ -1232,12 +1579,33 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "similar"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
+
+[[package]]
+name = "siphasher"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e"
+
 [[package]]
 name = "slab"
 version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
 
+[[package]]
+name = "slug"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
+dependencies = [
+ "deunicode",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.7.0"
@@ -1287,6 +1655,28 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "tera"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3cac831b615c25bcef632d1cabf864fa05813baad3d526829db18eb70e8b58d"
+dependencies = [
+ "chrono",
+ "chrono-tz",
+ "globwalk",
+ "humansize",
+ "lazy_static",
+ "percent-encoding",
+ "pest",
+ "pest_derive",
+ "rand 0.8.4",
+ "regex",
+ "serde",
+ "serde_json",
+ "slug",
+ "unic-segment",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.1.2"
@@ -1296,6 +1686,16 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.30"
@@ -1316,6 +1716,15 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
 [[package]]
 name = "time"
 version = "0.1.44"
@@ -1455,7 +1864,7 @@ dependencies = [
  "httparse",
  "log",
  "rand 0.8.4",
- "sha-1",
+ "sha-1 0.9.8",
  "thiserror",
  "url",
  "utf-8",
@@ -1473,6 +1882,65 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
 
+[[package]]
+name = "uncased"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.7"
@@ -1494,6 +1962,12 @@ version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
 
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.2"

+ 2 - 2
shared-lib/error-code/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("error-code", "./src/protobuf/proto");
+    pb::gen_files("error-code", "./src/protobuf/proto");
 }

+ 2 - 2
shared-lib/flowy-collaboration/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-collaboration", "./src/protobuf/proto");
+    pb::gen_files("flowy-collaboration", "./src/protobuf/proto");
 }

+ 2 - 2
shared-lib/flowy-folder-data-model/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-folder-data-model", "./src/protobuf/proto");
+    pb::gen_files("flowy-folder-data-model", "./src/protobuf/proto");
 }

+ 2 - 2
shared-lib/flowy-user-data-model/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("flowy-user-data-model", "./src/protobuf/proto");
+    pb::gen_files("flowy-user-data-model", "./src/protobuf/proto");
 }

+ 26 - 0
shared-lib/lib-infra/Cargo.toml

@@ -19,6 +19,32 @@ cmd_lib = { version = "1", optional = true }
 protoc-rust = { version = "2", optional = true }
 walkdir = { version = "2", optional = true }
 
+flowy-ast = { path = "../flowy-ast", optional = true}
+similar = { version = "1.2.2", optional = true }
+syn = { version = "1.0.60", features = ["extra-traits", "parsing", "derive", "full"], optional = true }
+fancy-regex = { version = "0.5.0", optional = true }
+lazy_static = { version = "1.4.0", optional = true }
+tera = { version = "1.5.0", optional = true}
+itertools = {versino = "0.10", optional = true}
+phf = { version = "0.8.0", features = ["macros"], optional = true }
+console = {version = "0.14.0", optional = true}
+serde = { version = "1.0", features = ["derive"], optional = true}
+toml = {version = "0.5.8", optional = true}
+
 [features]
 pb_gen = ["cmd_lib", "protoc-rust", "walkdir"]
+proto_gen = [
+    "flowy-ast",
+    "similar",
+    "syn",
+    "fancy-regex",
+    "lazy_static",
+    "tera",
+    "itertools",
+    "phf",
+    "walkdir",
+    "console",
+    "serde",
+    "toml"
+]
 dart = []

+ 4 - 1
shared-lib/lib-infra/src/lib.rs

@@ -2,7 +2,10 @@ pub mod future;
 pub mod retry;
 
 #[cfg(feature = "pb_gen")]
-pub mod pb_gen;
+pub mod pb;
+
+#[cfg(feature = "proto_gen")]
+pub mod proto_gen;
 
 #[allow(dead_code)]
 pub fn uuid_string() -> String {

+ 54 - 23
shared-lib/lib-infra/src/pb_gen.rs → shared-lib/lib-infra/src/pb.rs

@@ -2,37 +2,68 @@
 #![allow(unused_imports)]
 #![allow(unused_attributes)]
 #![allow(dead_code)]
+use crate::proto_gen;
+use crate::proto_gen::proto_info::ProtobufCrate;
+use crate::proto_gen::util::suffix_relative_to_path;
+use crate::proto_gen::ProtoGenerator;
+use log::info;
 use std::fs::File;
 use std::io::Write;
 use std::process::Command;
 use walkdir::WalkDir;
 
-pub fn gen(name: &str, root: &str) {
-    let mut paths = vec![];
-    let mut file_names = vec![];
-    for (path, file_name) in WalkDir::new(root).into_iter().filter_map(|e| e.ok()).map(|e| {
-        let path = e.path().to_str().unwrap().to_string();
-        let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string();
-        (path, file_name)
-    }) {
-        if path.ends_with(".proto") {
-            // https://stackoverflow.com/questions/49077147/how-can-i-force-build-rs-to-run-again-without-cleaning-my-whole-project
-            println!("cargo:rerun-if-changed={}", path);
-            paths.push(path);
-            file_names.push(file_name);
+fn gen_protos(root: &str) -> Vec<ProtobufCrate> {
+    let crate_context = ProtoGenerator::gen(root);
+    let proto_crates = crate_context
+        .iter()
+        .map(|info| info.protobuf_crate.clone())
+        .collect::<Vec<_>>();
+    crate_context
+        .into_iter()
+        .map(|info| info.files)
+        .flatten()
+        .for_each(|file| {
+            println!("cargo:rerun-if-changed={}", file.file_path);
+        });
+    proto_crates
+}
+
+pub fn gen_files(name: &str, root: &str) {
+    let root_absolute_path = std::fs::canonicalize(".").unwrap().as_path().display().to_string();
+    for protobuf_crate in gen_protos(&root_absolute_path) {
+        let mut paths = vec![];
+        let mut file_names = vec![];
+
+        let proto_relative_path = suffix_relative_to_path(&protobuf_crate.proto_output_dir(), &root_absolute_path);
+
+        for (path, file_name) in WalkDir::new(proto_relative_path)
+            .into_iter()
+            .filter_map(|e| e.ok())
+            .map(|e| {
+                let path = e.path().to_str().unwrap().to_string();
+                let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string();
+                (path, file_name)
+            })
+        {
+            if path.ends_with(".proto") {
+                // https://stackoverflow.com/questions/49077147/how-can-i-force-build-rs-to-run-again-without-cleaning-my-whole-project
+                println!("cargo:rerun-if-changed={}", path);
+                paths.push(path);
+                file_names.push(file_name);
+            }
         }
-    }
-    println!("cargo:rerun-if-changed=build.rs");
+        println!("cargo:rerun-if-changed=build.rs");
 
-    #[cfg(feature = "dart")]
-    gen_pb_for_dart(name, root, &paths, &file_names);
+        #[cfg(feature = "dart")]
+        gen_pb_for_dart(name, root, &paths, &file_names);
 
-    protoc_rust::Codegen::new()
-        .out_dir("./src/protobuf/model")
-        .inputs(&paths)
-        .include(root)
-        .run()
-        .expect("Running protoc failed.");
+        protoc_rust::Codegen::new()
+            .out_dir("./src/protobuf/model")
+            .inputs(&paths)
+            .include(root)
+            .run()
+            .expect("Running protoc failed.");
+    }
 }
 
 #[cfg(feature = "dart")]

+ 180 - 0
shared-lib/lib-infra/src/proto_gen/ast.rs

@@ -0,0 +1,180 @@
+use crate::proto_gen::proto_info::{parse_crate_info_from_path, ProtoFile, ProtobufCrateContext};
+use crate::proto_gen::template::{EnumTemplate, StructTemplate};
+use crate::proto_gen::util::*;
+use fancy_regex::Regex;
+use flowy_ast::*;
+use lazy_static::lazy_static;
+use std::{fs::File, io::Read, path::Path};
+use syn::Item;
+use walkdir::WalkDir;
+
+pub fn parse_crate_protobuf(roots: Vec<String>) -> Vec<ProtobufCrateContext> {
+    let crate_infos = parse_crate_info_from_path(roots);
+    crate_infos
+        .into_iter()
+        .map(|crate_info| {
+            let proto_output_dir = crate_info.proto_output_dir();
+            let files = crate_info
+                .proto_paths
+                .iter()
+                .map(|proto_crate_path| parse_files_protobuf(proto_crate_path, &proto_output_dir))
+                .flatten()
+                .collect::<Vec<ProtoFile>>();
+
+            ProtobufCrateContext::from_crate_info(crate_info, files)
+        })
+        .collect::<Vec<ProtobufCrateContext>>()
+}
+
+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()
+        .filter_entry(|e| !is_hidden(e))
+        .filter_map(|e| e.ok())
+        .filter(|e| !e.file_type().is_dir())
+        .map(|e| {
+            let path = e.path().to_str().unwrap().to_string();
+            let file_name = e.path().file_stem().unwrap().to_str().unwrap().to_string();
+            (path, file_name)
+        })
+    {
+        if file_name == "mod" {
+            continue;
+        }
+
+        // https://docs.rs/syn/1.0.54/syn/struct.File.html
+        let ast = syn::parse_file(read_file(&path).unwrap().as_ref())
+            .unwrap_or_else(|_| panic!("Unable to parse file at {}", path));
+        let structs = get_ast_structs(&ast);
+        let proto_file_path = format!("{}/{}.proto", &proto_output_dir, &file_name);
+        let mut proto_file_content = parse_or_init_proto_file(proto_file_path.as_ref());
+
+        structs.iter().for_each(|s| {
+            let mut struct_template = StructTemplate::new();
+            struct_template.set_message_struct_name(&s.name);
+
+            s.fields.iter().filter(|f| f.attrs.pb_index().is_some()).for_each(|f| {
+                struct_template.set_field(&f);
+            });
+
+            let s = struct_template.render().unwrap();
+            proto_file_content.push_str(s.as_ref());
+            proto_file_content.push('\n');
+        });
+
+        let enums = get_ast_enums(&ast);
+        enums.iter().for_each(|e| {
+            let mut enum_template = EnumTemplate::new();
+            enum_template.set_message_enum(&e);
+            let s = enum_template.render().unwrap();
+            proto_file_content.push_str(s.as_ref());
+            proto_file_content.push('\n');
+        });
+
+        if !enums.is_empty() || !structs.is_empty() {
+            let info = ProtoFile {
+                file_path: path.clone(),
+                file_name: file_name.clone(),
+                structs: structs.iter().map(|s| s.name.clone()).collect(),
+                enums: enums.iter().map(|e| e.name.clone()).collect(),
+                generated_content: proto_file_content.clone(),
+            };
+            gen_proto_vec.push(info);
+        }
+    }
+
+    gen_proto_vec
+}
+
+pub fn parse_or_init_proto_file(path: &str) -> String {
+    let mut proto_file_content = String::new();
+    let imported_content = find_proto_file_import(path);
+    proto_file_content.push_str(imported_content.as_ref());
+    proto_file_content.push('\n');
+    proto_file_content
+}
+
+pub fn get_ast_structs(ast: &syn::File) -> Vec<Struct> {
+    // let mut content = format!("{:#?}", &ast);
+    // let mut file = File::create("./foo.txt").unwrap();
+    // file.write_all(content.as_bytes()).unwrap();
+    let ctxt = Ctxt::new();
+    let mut proto_structs: Vec<Struct> = vec![];
+    ast.items.iter().for_each(|item| {
+        if let Item::Struct(item_struct) = item {
+            let (_, fields) = struct_from_ast(&ctxt, &item_struct.fields);
+
+            if fields.iter().filter(|f| f.attrs.pb_index().is_some()).count() > 0 {
+                proto_structs.push(Struct {
+                    name: item_struct.ident.to_string(),
+                    fields,
+                });
+            }
+        }
+    });
+    ctxt.check().unwrap();
+    proto_structs
+}
+
+pub fn get_ast_enums(ast: &syn::File) -> Vec<FlowyEnum> {
+    let mut flowy_enums: Vec<FlowyEnum> = vec![];
+    let ctxt = Ctxt::new();
+
+    ast.items.iter().for_each(|item| {
+        // https://docs.rs/syn/1.0.54/syn/enum.Item.html
+        if let Item::Enum(item_enum) = item {
+            let attrs = flowy_ast::enum_from_ast(&ctxt, &item_enum.ident, &item_enum.variants, &ast.attrs);
+            flowy_enums.push(FlowyEnum {
+                name: item_enum.ident.to_string(),
+                attrs,
+            });
+        }
+    });
+    ctxt.check().unwrap();
+    flowy_enums
+}
+
+pub struct FlowyEnum<'a> {
+    pub name: String,
+    pub attrs: Vec<ASTEnumVariant<'a>>,
+}
+
+pub struct Struct<'a> {
+    pub name: String,
+    pub fields: Vec<ASTField<'a>>,
+}
+
+lazy_static! {
+    static ref SYNTAX_REGEX: Regex = Regex::new("syntax.*;").unwrap();
+    static ref IMPORT_REGEX: Regex = Regex::new("(import\\s).*;").unwrap();
+}
+
+fn find_proto_file_import(path: &str) -> String {
+    let mut result = String::new();
+    if !Path::new(path).exists() {
+        // log::error!("{} not exist", path);
+        result = String::from("syntax = \"proto3\";");
+        return result;
+    }
+
+    let mut file = File::open(path).unwrap();
+    let mut content = String::new();
+    file.read_to_string(&mut content).unwrap();
+
+    content.lines().for_each(|line| {
+        ////Result<Option<Match<'t>>>
+        if let Ok(Some(m)) = SYNTAX_REGEX.find(line) {
+            result.push_str(m.as_str());
+            result.push('\n');
+        }
+
+        if let Ok(Some(m)) = IMPORT_REGEX.find(line) {
+            result.push_str(m.as_str());
+            result.push('\n');
+        }
+    });
+
+    result
+}

+ 52 - 0
shared-lib/lib-infra/src/proto_gen/flowy_toml.rs

@@ -0,0 +1,52 @@
+use std::fs;
+
+#[derive(serde::Deserialize)]
+pub struct FlowyConfig {
+    pub proto_crates: Vec<String>,
+    pub event_files: Vec<String>,
+}
+
+impl FlowyConfig {
+    pub fn from_toml_file(path: &str) -> Self {
+        let content = fs::read_to_string(path).unwrap();
+        let config: FlowyConfig = toml::from_str(content.as_ref()).unwrap();
+        config
+    }
+}
+
+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,
+    })
+}

+ 8 - 0
shared-lib/lib-infra/src/proto_gen/mod.rs

@@ -0,0 +1,8 @@
+mod ast;
+mod flowy_toml;
+mod proto_gen;
+pub mod proto_info;
+mod template;
+pub mod util;
+
+pub(crate) use proto_gen::*;

+ 63 - 0
shared-lib/lib-infra/src/proto_gen/proto_gen.rs

@@ -0,0 +1,63 @@
+use crate::proto_gen::ast::parse_crate_protobuf;
+use crate::proto_gen::proto_info::ProtobufCrateContext;
+use crate::proto_gen::template::write_derive_meta;
+use crate::proto_gen::util::*;
+use std::{fs::OpenOptions, io::Write};
+
+pub(crate) struct ProtoGenerator();
+impl ProtoGenerator {
+    pub(crate) fn gen(root: &str) -> Vec<ProtobufCrateContext> {
+        let crate_contexts = parse_crate_protobuf(vec![root.to_owned()]);
+        write_proto_files(&crate_contexts);
+        write_rust_crate_mod_file(&crate_contexts);
+        for crate_info in &crate_contexts {
+            let _ = crate_info.protobuf_crate.create_output_dir();
+            let _ = crate_info.protobuf_crate.proto_output_dir();
+            crate_info.create_crate_mod_file();
+        }
+
+        crate_contexts
+    }
+}
+
+fn write_proto_files(crate_contexts: &[ProtobufCrateContext]) {
+    for context in crate_contexts {
+        let dir = context.protobuf_crate.proto_output_dir();
+        context.files.iter().for_each(|info| {
+            let proto_file_path = format!("{}/{}.proto", dir, &info.file_name);
+            save_content_to_file_with_diff_prompt(&info.generated_content, proto_file_path.as_ref());
+        });
+    }
+}
+
+fn write_rust_crate_mod_file(crate_contexts: &[ProtobufCrateContext]) {
+    for context in crate_contexts {
+        let mod_path = context.protobuf_crate.proto_model_mod_file();
+        match OpenOptions::new()
+            .create(true)
+            .write(true)
+            .append(false)
+            .truncate(true)
+            .open(&mod_path)
+        {
+            Ok(ref mut file) => {
+                let mut mod_file_content = String::new();
+
+                mod_file_content.push_str("#![cfg_attr(rustfmt, rustfmt::skip)]\n");
+                mod_file_content.push_str("// Auto-generated, do not edit\n");
+                walk_dir(
+                    context.protobuf_crate.proto_output_dir().as_ref(),
+                    |e| !e.file_type().is_dir(),
+                    |_, name| {
+                        let c = format!("\nmod {};\npub use {}::*;\n", &name, &name);
+                        mod_file_content.push_str(c.as_ref());
+                    },
+                );
+                file.write_all(mod_file_content.as_bytes()).unwrap();
+            }
+            Err(err) => {
+                panic!("Failed to open file: {}", err);
+            }
+        }
+    }
+}

+ 142 - 0
shared-lib/lib-infra/src/proto_gen/proto_info.rs

@@ -0,0 +1,142 @@
+#![allow(dead_code)]
+use crate::proto_gen::flowy_toml::{parse_crate_config_from, CrateConfig};
+use crate::proto_gen::util::*;
+use std::fs::OpenOptions;
+use std::io::Write;
+use walkdir::WalkDir;
+
+#[derive(Debug)]
+pub struct ProtobufCrateContext {
+    pub files: Vec<ProtoFile>,
+    pub protobuf_crate: ProtobufCrate,
+}
+
+impl ProtobufCrateContext {
+    pub fn from_crate_info(inner: ProtobufCrate, files: Vec<ProtoFile>) -> Self {
+        Self {
+            files,
+            protobuf_crate: inner,
+        }
+    }
+
+    pub fn create_crate_mod_file(&self) {
+        // mod model;
+        // pub use model::*;
+        let mod_file_path = format!("{}/mod.rs", self.protobuf_crate.protobuf_crate_name());
+        let mut content = "#![cfg_attr(rustfmt, rustfmt::skip)]\n".to_owned();
+        content.push_str("// Auto-generated, do not edit\n");
+        content.push_str("mod model;\npub use model::*;");
+        match OpenOptions::new()
+            .create(true)
+            .write(true)
+            .append(false)
+            .truncate(true)
+            .open(&mod_file_path)
+        {
+            Ok(ref mut file) => {
+                file.write_all(content.as_bytes()).unwrap();
+            }
+            Err(err) => {
+                panic!("Failed to open protobuf mod file: {}", err);
+            }
+        }
+    }
+
+    #[allow(dead_code)]
+    pub fn flutter_mod_dir(&self, root: &str) -> String {
+        let crate_module_dir = format!("{}/{}", root, self.protobuf_crate.folder_name);
+        crate_module_dir
+    }
+
+    #[allow(dead_code)]
+    pub fn flutter_mod_file(&self, root: &str) -> String {
+        let crate_module_dir = format!("{}/{}/protobuf.dart", root, self.protobuf_crate.folder_name);
+        crate_module_dir
+    }
+}
+
+#[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_output_dir(&self) -> String {
+        let dir = format!("{}/proto", self.protobuf_crate_name());
+        create_dir_if_not_exist(dir.as_ref());
+        dir
+    }
+
+    pub fn create_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.create_output_dir())
+    }
+}
+
+#[derive(Debug)]
+pub struct ProtoFile {
+    pub file_path: String,
+    pub file_name: String,
+    pub structs: Vec<String>,
+    pub enums: Vec<String>,
+    pub generated_content: String,
+}
+
+pub fn parse_crate_info_from_path(roots: Vec<String>) -> Vec<ProtobufCrate> {
+    let mut protobuf_crates: Vec<ProtobufCrate> = vec![];
+    roots.iter().for_each(|root| {
+        let crates = 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(ProtobufCrate::from_config)
+            .collect::<Vec<ProtobufCrate>>();
+        protobuf_crates.extend(crates);
+    });
+    protobuf_crates
+}
+
+pub struct FlutterProtobufInfo {
+    package_path: String,
+}
+impl FlutterProtobufInfo {
+    pub fn new(root: &str) -> Self {
+        FlutterProtobufInfo {
+            package_path: root.to_owned(),
+        }
+    }
+
+    pub fn model_dir(&self) -> String {
+        let model_dir = format!("{}/protobuf", self.package_path);
+        create_dir_if_not_exist(model_dir.as_ref());
+        model_dir
+    }
+
+    #[allow(dead_code)]
+    pub fn mod_file_path(&self) -> String {
+        let mod_file_path = format!("{}/protobuf.dart", self.package_path);
+        mod_file_path
+    }
+}

+ 79 - 0
shared-lib/lib-infra/src/proto_gen/template/derive_meta/derive_meta.rs

@@ -0,0 +1,79 @@
+use crate::proto_gen::proto_info::{ProtoFile, ProtobufCrateContext};
+use crate::proto_gen::template::{get_tera, read_file};
+use itertools::Itertools;
+use std::fs::OpenOptions;
+use std::io::Write;
+use tera::Context;
+
+pub struct ProtobufDeriveMeta {
+    context: Context,
+    structs: Vec<String>,
+    enums: Vec<String>,
+}
+
+#[allow(dead_code)]
+impl ProtobufDeriveMeta {
+    pub fn new(structs: Vec<String>, enums: Vec<String>) -> Self {
+        let enums: Vec<_> = enums.into_iter().unique().collect();
+        ProtobufDeriveMeta {
+            context: Context::new(),
+            structs,
+            enums,
+        }
+    }
+
+    pub fn render(&mut self) -> Option<String> {
+        self.context.insert("names", &self.structs);
+        self.context.insert("enums", &self.enums);
+
+        let tera = get_tera("proto_gen/template/derive_meta");
+        match tera.render("derive_meta.tera", &self.context) {
+            Ok(r) => Some(r),
+            Err(e) => {
+                log::error!("{:?}", e);
+                None
+            }
+        }
+    }
+}
+
+pub fn write_derive_meta(crate_infos: &[ProtobufCrateContext], derive_meta_dir: &str) {
+    let file_proto_infos = crate_infos
+        .iter()
+        .map(|ref crate_info| &crate_info.files)
+        .flatten()
+        .collect::<Vec<&ProtoFile>>();
+
+    let structs: Vec<String> = file_proto_infos
+        .iter()
+        .map(|info| info.structs.clone())
+        .flatten()
+        .collect();
+    let enums: Vec<String> = file_proto_infos
+        .iter()
+        .map(|info| info.enums.clone())
+        .flatten()
+        .collect();
+
+    let mut derive_template = ProtobufDeriveMeta::new(structs, enums);
+    let new_content = derive_template.render().unwrap();
+    let old_content = read_file(derive_meta_dir).unwrap();
+    if new_content == old_content {
+        return;
+    }
+    // println!("{}", diff_lines(&old_content, &new_content));
+    match OpenOptions::new()
+        .create(true)
+        .write(true)
+        .append(false)
+        .truncate(true)
+        .open(derive_meta_dir)
+    {
+        Ok(ref mut file) => {
+            file.write_all(new_content.as_bytes()).unwrap();
+        }
+        Err(err) => {
+            panic!("Failed to open log file: {}", err);
+        }
+    }
+}

+ 45 - 0
shared-lib/lib-infra/src/proto_gen/template/derive_meta/derive_meta.tera

@@ -0,0 +1,45 @@
+#![cfg_attr(rustfmt, rustfmt::skip)]
+pub enum TypeCategory {
+    Array,
+    Map,
+    Str,
+    Protobuf,
+    Bytes,
+    Enum,
+    Opt,
+    Primitive,
+}
+// auto generate, do not edit
+pub fn category_from_str(type_str: &str) -> TypeCategory {
+    match type_str {
+        "Vec" => TypeCategory::Array,
+        "HashMap" => TypeCategory::Map,
+        "u8" => TypeCategory::Bytes,
+        "String" => TypeCategory::Str,
+{%- for name in names -%}
+    {%- if loop.first %}
+        "{{ name }}"
+    {%- else %}
+        | "{{ name }}"
+    {%- endif -%}
+    {%- if loop.last %}
+        => TypeCategory::Protobuf,
+    {%- endif %}
+
+{%- endfor %}
+
+{%- for enum in enums -%}
+    {%- if loop.first %}
+        "{{ enum }}"
+    {%- else %}
+        | "{{ enum }}"
+    {%- endif -%}
+    {%- if loop.last %}
+        => TypeCategory::Enum,
+    {%- endif %}
+{%- endfor %}
+
+        "Option" => TypeCategory::Opt,
+        _ => TypeCategory::Primitive,
+    }
+}

+ 4 - 0
shared-lib/lib-infra/src/proto_gen/template/derive_meta/mod.rs

@@ -0,0 +1,4 @@
+#![allow(clippy::module_inception)]
+mod derive_meta;
+
+pub use derive_meta::*;

+ 40 - 0
shared-lib/lib-infra/src/proto_gen/template/mod.rs

@@ -0,0 +1,40 @@
+mod derive_meta;
+mod proto_file;
+
+pub use derive_meta::*;
+pub use proto_file::*;
+use std::fs::File;
+use std::io::Read;
+use tera::Tera;
+
+pub fn get_tera(directory: &str) -> Tera {
+    let mut root = format!("{}/../shared-lib/lib-infra/src/", env!("CARGO_MAKE_WORKING_DIRECTORY"));
+    root.push_str(directory);
+
+    let root_absolute_path = std::fs::canonicalize(root).unwrap().as_path().display().to_string();
+    let mut template_path = format!("{}/**/*.tera", root_absolute_path);
+    if cfg!(windows) {
+        // remove "\\?\" prefix on windows
+        template_path = format!("{}/**/*.tera", &root_absolute_path[4..]);
+    }
+
+    match Tera::new(template_path.as_ref()) {
+        Ok(t) => t,
+        Err(e) => {
+            log::error!("Parsing error(s): {}", e);
+            ::std::process::exit(1);
+        }
+    }
+}
+
+pub fn read_file(path: &str) -> Option<String> {
+    let mut file = File::open(path).unwrap_or_else(|_| panic!("Unable to open file at {}", path));
+    let mut content = String::new();
+    match file.read_to_string(&mut content) {
+        Ok(_) => Some(content),
+        Err(e) => {
+            log::error!("{}, with error: {:?}", path, e);
+            Some("".to_string())
+        }
+    }
+}

+ 5 - 0
shared-lib/lib-infra/src/proto_gen/template/proto_file/enum.tera

@@ -0,0 +1,5 @@
+enum {{ enum_name }} {
+    {%- for item in items %}
+    {{ item }}
+    {%- endfor %}
+}

+ 38 - 0
shared-lib/lib-infra/src/proto_gen/template/proto_file/enum_template.rs

@@ -0,0 +1,38 @@
+use crate::proto_gen::ast::FlowyEnum;
+use crate::proto_gen::template::get_tera;
+use tera::Context;
+
+pub struct EnumTemplate {
+    context: Context,
+    items: Vec<String>,
+}
+
+#[allow(dead_code)]
+impl EnumTemplate {
+    pub fn new() -> Self {
+        EnumTemplate {
+            context: Context::new(),
+            items: vec![],
+        }
+    }
+
+    pub fn set_message_enum(&mut self, flowy_enum: &FlowyEnum) {
+        self.context.insert("enum_name", &flowy_enum.name);
+        flowy_enum.attrs.iter().for_each(|item| {
+            self.items
+                .push(format!("{} = {};", item.attrs.enum_item_name, item.attrs.value))
+        })
+    }
+
+    pub fn render(&mut self) -> Option<String> {
+        self.context.insert("items", &self.items);
+        let tera = get_tera("proto_gen/template/proto_file");
+        match tera.render("enum.tera", &self.context) {
+            Ok(r) => Some(r),
+            Err(e) => {
+                log::error!("{:?}", e);
+                None
+            }
+        }
+    }
+}

+ 5 - 0
shared-lib/lib-infra/src/proto_gen/template/proto_file/mod.rs

@@ -0,0 +1,5 @@
+mod enum_template;
+mod struct_template;
+
+pub use enum_template::*;
+pub use struct_template::*;

+ 5 - 0
shared-lib/lib-infra/src/proto_gen/template/proto_file/struct.tera

@@ -0,0 +1,5 @@
+message {{ struct_name }} {
+    {%- for field in fields %}
+    {{ field }}
+    {%- endfor %}
+}

+ 107 - 0
shared-lib/lib-infra/src/proto_gen/template/proto_file/struct_template.rs

@@ -0,0 +1,107 @@
+use flowy_ast::*;
+use phf::phf_map;
+
+use crate::proto_gen::template::get_tera;
+use tera::Context;
+
+// Protobuf data type : https://developers.google.com/protocol-buffers/docs/proto3
+static RUST_TYPE_MAP: phf::Map<&'static str, &'static str> = phf_map! {
+    "String" => "string",
+    "i64" => "int64",
+    "i32" => "int32",
+    "u64" => "uint64",
+    "u32" => "uint32",
+    "Vec" => "repeated",
+    "f64" => "double",
+    "HashMap" => "map",
+};
+
+pub struct StructTemplate {
+    context: Context,
+    fields: Vec<String>,
+}
+
+#[allow(dead_code)]
+impl StructTemplate {
+    pub fn new() -> Self {
+        StructTemplate {
+            context: Context::new(),
+            fields: vec![],
+        }
+    }
+
+    pub fn set_message_struct_name(&mut self, name: &str) {
+        self.context.insert("struct_name", name);
+    }
+
+    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 ty: &str = &field.ty_as_str();
+        let mut mapped_ty: &str = ty;
+
+        if RUST_TYPE_MAP.contains_key(ty) {
+            mapped_ty = RUST_TYPE_MAP[ty];
+        }
+
+        if let Some(ref category) = field.bracket_category {
+            match category {
+                BracketCategory::Opt => match &field.bracket_inner_ty {
+                    None => {}
+                    Some(inner_ty) => match inner_ty.to_string().as_str() {
+                        //TODO: support hashmap or something else wrapped by Option
+                        "Vec" => {
+                            self.fields
+                                .push(format!("oneof one_of_{} {{ bytes {} = {}; }};", name, name, index));
+                        }
+                        _ => {
+                            self.fields.push(format!(
+                                "oneof one_of_{} {{ {} {} = {}; }};",
+                                name, mapped_ty, name, index
+                            ));
+                        }
+                    },
+                },
+                BracketCategory::Map((k, v)) => {
+                    let key: &str = k;
+                    let value: &str = v;
+                    self.fields.push(format!(
+                        // map<string, string> attrs = 1;
+                        "map<{}, {}> {} = {};",
+                        RUST_TYPE_MAP.get(key).unwrap_or(&key),
+                        RUST_TYPE_MAP.get(value).unwrap_or(&value),
+                        name,
+                        index
+                    ));
+                }
+                BracketCategory::Vec => {
+                    let bracket_ty: &str = &field.bracket_ty.as_ref().unwrap().to_string();
+                    // Vec<u8>
+                    if mapped_ty == "u8" && bracket_ty == "Vec" {
+                        self.fields.push(format!("bytes {} = {};", name, index))
+                    } else {
+                        self.fields.push(format!(
+                            "{} {} {} = {};",
+                            RUST_TYPE_MAP[bracket_ty], mapped_ty, name, index
+                        ))
+                    }
+                }
+                BracketCategory::Other => self.fields.push(format!("{} {} = {};", mapped_ty, name, index)),
+            }
+        }
+    }
+
+    pub fn render(&mut self) -> Option<String> {
+        self.context.insert("fields", &self.fields);
+        let tera = get_tera("proto_gen/template/proto_file");
+        match tera.render("struct.tera", &self.context) {
+            Ok(r) => Some(r),
+            Err(e) => {
+                log::error!("{:?}", e);
+                None
+            }
+        }
+    }
+}

+ 128 - 0
shared-lib/lib-infra/src/proto_gen/util.rs

@@ -0,0 +1,128 @@
+use console::Style;
+
+use similar::{ChangeTag, TextDiff};
+use std::{
+    fs::{File, OpenOptions},
+    io::{Read, Write},
+    path::Path,
+};
+use walkdir::WalkDir;
+
+pub fn read_file(path: &str) -> Option<String> {
+    let mut file = File::open(path).unwrap_or_else(|_| panic!("Unable to open file at {}", path));
+    let mut content = String::new();
+    match file.read_to_string(&mut content) {
+        Ok(_) => Some(content),
+        Err(e) => {
+            log::error!("{}, with error: {:?}", path, e);
+            Some("".to_string())
+        }
+    }
+}
+
+pub fn save_content_to_file_with_diff_prompt(content: &str, output_file: &str) {
+    if Path::new(output_file).exists() {
+        let old_content = read_file(output_file).unwrap();
+        let new_content = content.to_owned();
+        let write_to_file = || match OpenOptions::new()
+            .create(true)
+            .write(true)
+            .append(false)
+            .truncate(true)
+            .open(output_file)
+        {
+            Ok(ref mut file) => {
+                file.write_all(new_content.as_bytes()).unwrap();
+            }
+            Err(err) => {
+                panic!("Failed to open log file: {}", err);
+            }
+        };
+        if new_content != old_content {
+            print_diff(old_content, new_content.clone());
+            write_to_file()
+        }
+    } else {
+        match OpenOptions::new().create(true).write(true).open(output_file) {
+            Ok(ref mut file) => file.write_all(content.as_bytes()).unwrap(),
+            Err(err) => panic!("Open or create to {} fail: {}", output_file, err),
+        }
+    }
+}
+
+pub fn print_diff(old_content: String, new_content: String) {
+    let diff = TextDiff::from_lines(&old_content, &new_content);
+    for op in diff.ops() {
+        for change in diff.iter_changes(op) {
+            let (sign, style) = match change.tag() {
+                ChangeTag::Delete => ("-", Style::new().red()),
+                ChangeTag::Insert => ("+", Style::new().green()),
+                ChangeTag::Equal => (" ", Style::new()),
+            };
+
+            match change.tag() {
+                ChangeTag::Delete => {
+                    print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change));
+                }
+                ChangeTag::Insert => {
+                    print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change));
+                }
+                ChangeTag::Equal => {}
+            };
+        }
+        println!("---------------------------------------------------");
+    }
+}
+
+#[allow(dead_code)]
+pub fn is_crate_dir(e: &walkdir::DirEntry) -> bool {
+    let cargo = e.path().file_stem().unwrap().to_str().unwrap().to_string();
+    cargo == *"Cargo"
+}
+
+#[allow(dead_code)]
+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"
+}
+
+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();
+    }
+}
+
+#[allow(dead_code)]
+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);
+    }
+}
+
+#[allow(dead_code)]
+pub fn suffix_relative_to_path(path: &str, base: &str) -> String {
+    let base = Path::new(base);
+    let path = Path::new(path);
+    path.strip_prefix(base).unwrap().to_str().unwrap().to_owned()
+}

+ 2 - 2
shared-lib/lib-ws/build.rs

@@ -1,5 +1,5 @@
-use lib_infra::pb_gen;
+use lib_infra::pb;
 
 fn main() {
-    pb_gen::gen("lib-ws", "./src/protobuf/proto");
+    pb::gen_files("lib-ws", "./src/protobuf/proto");
 }