mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
auto generate proto file from rust struct
This commit is contained in:
10
scripts/flowy-tool/.gitignore
vendored
Normal file
10
scripts/flowy-tool/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
22
scripts/flowy-tool/Cargo.toml
Normal file
22
scripts/flowy-tool/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "flowy-tool"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
walkdir = "2.3.1"
|
||||
syn = { version = "1.0.60", features = ["extra-traits", "parsing", "derive", "full"]}
|
||||
tera = { version = "1.5.0" }
|
||||
log = "0.4.11"
|
||||
env_logger = "0.8.2"
|
||||
shell = { git="https://github.com/google/rust-shell.git"}
|
||||
flowy-ast = { path = "../../rust-lib/flowy-ast" }
|
||||
console = "0.14.0"
|
||||
fancy-regex = "0.5.0"
|
||||
lazy_static = "1.4.0"
|
||||
phf = { version = "0.8.0", features = ["macros"] }
|
||||
similar = "1.2.2"
|
||||
dialoguer = "0.8.0"
|
68
scripts/flowy-tool/src/main.rs
Normal file
68
scripts/flowy-tool/src/main.rs
Normal file
@ -0,0 +1,68 @@
|
||||
mod proto;
|
||||
mod util;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
fn main() {
|
||||
std::env::set_var("RUST_LOG", "Debug");
|
||||
env_logger::init();
|
||||
|
||||
let matches = app().get_matches();
|
||||
|
||||
if let Some(ref matches) = matches.subcommand_matches("pb-gen") {
|
||||
let rust_source = matches.value_of("rust_source").unwrap();
|
||||
let build_cache = matches.value_of("build_cache").unwrap();
|
||||
let rust_mod_dir = matches.value_of("rust_mod_dir").unwrap();
|
||||
let flutter_mod_dir = matches.value_of("flutter_mod_dir").unwrap();
|
||||
let proto_file_output = matches.value_of("proto_file_output").unwrap();
|
||||
|
||||
proto::ProtoGen::new()
|
||||
.set_rust_source_dir(rust_source)
|
||||
.set_build_cache_dir(build_cache)
|
||||
.set_rust_mod_dir(rust_mod_dir)
|
||||
.set_flutter_mod_dir(flutter_mod_dir)
|
||||
.set_proto_file_output_dir(proto_file_output)
|
||||
.gen();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app<'a, 'b>() -> App<'a, 'b> {
|
||||
let app = App::new("flowy-tool")
|
||||
.version("0.1")
|
||||
.author("nathan")
|
||||
.about("flowy tool")
|
||||
.subcommand(
|
||||
App::new("pb-gen")
|
||||
.about("Generate proto file from rust code")
|
||||
.arg(
|
||||
Arg::with_name("rust_source")
|
||||
.long("rust_source")
|
||||
.value_name("DIRECTORY")
|
||||
.help("The directory to the rust code"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("build_cache")
|
||||
.long("build_cache")
|
||||
.value_name("PATH")
|
||||
.help("Caching information used by flowy-derive"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rust_mod_dir")
|
||||
.long("rust_mod_dir")
|
||||
.value_name("DIRECTORY"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("flutter_mod_dir")
|
||||
.long("flutter_mod_dir")
|
||||
.value_name("DIRECTORY"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("proto_file_output")
|
||||
.long("proto_file_output")
|
||||
.value_name("DIRECTORY")
|
||||
.help("The path is used to save the generated proto file"),
|
||||
),
|
||||
);
|
||||
|
||||
app
|
||||
}
|
190
scripts/flowy-tool/src/proto/ast.rs
Normal file
190
scripts/flowy-tool/src/proto/ast.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use crate::proto::helper::*;
|
||||
use crate::proto::template::{EnumTemplate, StructTemplate};
|
||||
use crate::util::*;
|
||||
use flowy_ast::*;
|
||||
use syn::Item;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn parse_crate_protobuf(root: &str, proto_output_dir: &str) -> Vec<CrateProtoInfo> {
|
||||
log::info!("Generate proto file from {}", root);
|
||||
let domains_info = get_crate_domain_directory(root);
|
||||
domains_info
|
||||
.iter()
|
||||
.map(|domain| {
|
||||
let files = parse_files_protobuf(&domain.path, proto_output_dir);
|
||||
CrateProtoInfo::new(&domain, files)
|
||||
})
|
||||
.collect::<Vec<CrateProtoInfo>>()
|
||||
}
|
||||
|
||||
fn parse_files_protobuf(root: &str, proto_output_dir: &str) -> Vec<FileProtoInfo> {
|
||||
let mut gen_proto_vec: Vec<FileProtoInfo> = vec![];
|
||||
// file_stem https://doc.rust-lang.org/std/path/struct.Path.html#method.file_stem
|
||||
for (path, file_name) in WalkDir::new(root)
|
||||
.into_iter()
|
||||
.filter_entry(|e| !is_hidden(e))
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_dir() == false)
|
||||
.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()).expect("Unable to parse file");
|
||||
let structs = get_ast_structs(&ast);
|
||||
|
||||
// println!("😁 {} - {}", path, file_name);
|
||||
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_str("\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_str("\n");
|
||||
});
|
||||
|
||||
if !enums.is_empty() || !structs.is_empty() {
|
||||
let info = FileProtoInfo {
|
||||
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_str("\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| match item {
|
||||
Item::Struct(item_struct) => {
|
||||
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
|
||||
match item {
|
||||
Item::Enum(item_enum) => {
|
||||
flowy_enums.push(FlowyEnum {
|
||||
name: item_enum.ident.to_string(),
|
||||
attrs: flowy_ast::enum_from_ast(&ctxt, &item_enum.variants),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
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>>,
|
||||
}
|
||||
|
||||
use fancy_regex::Regex;
|
||||
use lazy_static::lazy_static;
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
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_line) = SYNTAX_REGEX.find(line) {
|
||||
if let Some(m) = some_line {
|
||||
result.push_str(m.as_str());
|
||||
result.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(some_line) = IMPORT_REGEX.find(line) {
|
||||
if let Some(m) = some_line {
|
||||
result.push_str(m.as_str());
|
||||
result.push_str("\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
}
|
71
scripts/flowy-tool/src/proto/helper.rs
Normal file
71
scripts/flowy-tool/src/proto/helper.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub struct CrateInfo {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub struct CrateProtoInfo {
|
||||
pub files: Vec<FileProtoInfo>,
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl CrateProtoInfo {
|
||||
pub fn new(info: &CrateInfo, files: Vec<FileProtoInfo>) -> Self {
|
||||
Self {
|
||||
files,
|
||||
name: info.name.to_owned(),
|
||||
path: info.path.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileProtoInfo {
|
||||
pub file_name: String,
|
||||
pub structs: Vec<String>,
|
||||
pub enums: Vec<String>,
|
||||
pub generated_content: String,
|
||||
}
|
||||
|
||||
pub fn get_crate_domain_directory(root: &str) -> Vec<CrateInfo> {
|
||||
WalkDir::new(root)
|
||||
.into_iter()
|
||||
.filter_entry(|e| !is_hidden(e))
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| is_domain_dir(e))
|
||||
.map(|e| CrateInfo {
|
||||
//TODO: get the crate name from toml file
|
||||
name: e
|
||||
.path()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
path: e.path().to_str().unwrap().to_string(),
|
||||
})
|
||||
.collect::<Vec<CrateInfo>>()
|
||||
}
|
||||
|
||||
pub fn is_domain_dir(e: &walkdir::DirEntry) -> bool {
|
||||
let domain = e.path().file_stem().unwrap().to_str().unwrap().to_string();
|
||||
if e.file_type().is_dir() && domain == "domain".to_string() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_hidden(entry: &walkdir::DirEntry) -> bool {
|
||||
entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map(|s| s.starts_with("."))
|
||||
.unwrap_or(false)
|
||||
}
|
6
scripts/flowy-tool/src/proto/mod.rs
Normal file
6
scripts/flowy-tool/src/proto/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
mod ast;
|
||||
mod helper;
|
||||
mod proto_gen;
|
||||
mod template;
|
||||
|
||||
pub use proto_gen::*;
|
176
scripts/flowy-tool/src/proto/proto_gen.rs
Normal file
176
scripts/flowy-tool/src/proto/proto_gen.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use crate::proto::ast::*;
|
||||
use crate::proto::helper::*;
|
||||
use crate::{proto::template::*, util::*};
|
||||
use flowy_ast::*;
|
||||
use std::{fs::OpenOptions, io::Write};
|
||||
use syn::Item;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub struct ProtoGen {
|
||||
rust_source_dir: Option<String>,
|
||||
proto_file_output_dir: Option<String>,
|
||||
rust_mod_dir: Option<String>,
|
||||
flutter_mod_dir: Option<String>,
|
||||
build_cache_dir: Option<String>,
|
||||
}
|
||||
|
||||
impl ProtoGen {
|
||||
pub fn new() -> Self {
|
||||
ProtoGen {
|
||||
rust_source_dir: None,
|
||||
proto_file_output_dir: None,
|
||||
rust_mod_dir: None,
|
||||
flutter_mod_dir: None,
|
||||
build_cache_dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rust_source_dir(mut self, dir: &str) -> Self {
|
||||
self.rust_source_dir = Some(dir.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_proto_file_output_dir(mut self, dir: &str) -> Self {
|
||||
self.proto_file_output_dir = Some(dir.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_rust_mod_dir(mut self, dir: &str) -> Self {
|
||||
self.rust_mod_dir = Some(dir.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_flutter_mod_dir(mut self, dir: &str) -> Self {
|
||||
self.flutter_mod_dir = Some(dir.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_build_cache_dir(mut self, build_cache_dir: &str) -> Self {
|
||||
self.build_cache_dir = Some(build_cache_dir.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gen(&self) {
|
||||
let infos = parse_crate_protobuf(
|
||||
self.rust_source_dir.as_ref().unwrap().as_ref(),
|
||||
self.proto_file_output_dir.as_ref().unwrap().as_ref(),
|
||||
);
|
||||
self.write_proto_files(&infos);
|
||||
self.gen_derive(&infos);
|
||||
self.update_rust_flowy_protobuf_mod_file(&infos);
|
||||
}
|
||||
|
||||
fn gen_derive(&self, crate_infos: &Vec<CrateProtoInfo>) {
|
||||
let file_proto_infos = crate_infos
|
||||
.iter()
|
||||
.map(|ref crate_info| &crate_info.files)
|
||||
.flatten()
|
||||
.collect::<Vec<&FileProtoInfo>>();
|
||||
|
||||
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 derive_file = self.build_cache_dir.as_ref().unwrap().clone();
|
||||
|
||||
let mut derive_template = ProtobufDeriveCache::new(structs, enums);
|
||||
let new_content = derive_template.render().unwrap();
|
||||
let old_content = read_file(derive_file.as_ref()).unwrap();
|
||||
if new_content.clone() == old_content {
|
||||
return;
|
||||
}
|
||||
// println!("{}", diff_lines(&old_content, &new_content));
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&derive_file)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
file.write_all(new_content.as_bytes()).unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("Failed to open log file: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_proto_files(&self, crate_infos: &Vec<CrateProtoInfo>) {
|
||||
for crate_info in crate_infos {
|
||||
crate_info.files.iter().for_each(|info| {
|
||||
// let dir = format!(
|
||||
// "{}/{}",
|
||||
// self.proto_file_output_dir.as_ref().unwrap(),
|
||||
// &crate_info.name,
|
||||
// );
|
||||
let dir = format!("{}", self.proto_file_output_dir.as_ref().unwrap(),);
|
||||
|
||||
if !std::path::Path::new(&dir).exists() {
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
let proto_file_path = format!("{}/{}.proto", dir, &info.file_name);
|
||||
let new_content = info.generated_content.clone();
|
||||
save_content_to_file_with_diff_prompt(
|
||||
&new_content,
|
||||
proto_file_path.as_ref(),
|
||||
false,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_rust_flowy_protobuf_mod_file(&self, crate_infos: &Vec<CrateProtoInfo>) {
|
||||
for crate_info in crate_infos {
|
||||
// let dir = format!(
|
||||
// "{}/{}-pb",
|
||||
// self.rust_mod_dir.as_ref().unwrap(),
|
||||
// &crate_info.name,
|
||||
// );
|
||||
|
||||
let dir = format!("{}/model", self.rust_mod_dir.as_ref().unwrap(),);
|
||||
if !std::path::Path::new(&dir).exists() {
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
}
|
||||
let mod_path = format!("{}/mod.rs", dir);
|
||||
|
||||
match OpenOptions::new()
|
||||
.create(false)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&mod_path)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
let mut mod_file_content = String::new();
|
||||
for (_, file_name) in
|
||||
WalkDir::new(self.proto_file_output_dir.as_ref().unwrap().clone())
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_dir() == false)
|
||||
.map(|e| {
|
||||
(
|
||||
e.path().to_str().unwrap().to_string(),
|
||||
e.path().file_stem().unwrap().to_str().unwrap().to_string(),
|
||||
)
|
||||
})
|
||||
{
|
||||
let c = format!("\nmod {}; \npub use {}::*; \n", &file_name, &file_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
use crate::util::get_tera;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
pub struct ProtobufDeriveCache {
|
||||
context: Context,
|
||||
structs: Vec<String>,
|
||||
enums: Vec<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl ProtobufDeriveCache {
|
||||
pub fn new(structs: Vec<String>, enums: Vec<String>) -> Self {
|
||||
return ProtobufDeriveCache {
|
||||
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("build_cache");
|
||||
match tera.render("derive_cache.tera", &self.context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
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,
|
||||
}
|
||||
}
|
3
scripts/flowy-tool/src/proto/template/build_cache/mod.rs
Normal file
3
scripts/flowy-tool/src/proto/template/build_cache/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod derive_cache;
|
||||
|
||||
pub use derive_cache::*;
|
5
scripts/flowy-tool/src/proto/template/mod.rs
Normal file
5
scripts/flowy-tool/src/proto/template/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod build_cache;
|
||||
mod proto_file;
|
||||
|
||||
pub use build_cache::*;
|
||||
pub use proto_file::*;
|
@ -0,0 +1,5 @@
|
||||
enum {{ enum_name }} {
|
||||
{%- for item in items %}
|
||||
{{ item }}
|
||||
{%- endfor %}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
use crate::proto::ast::FlowyEnum;
|
||||
use crate::util::get_tera;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
pub struct EnumTemplate {
|
||||
context: Context,
|
||||
items: Vec<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl EnumTemplate {
|
||||
pub fn new() -> Self {
|
||||
return 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.name, item.attrs.value))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Option<String> {
|
||||
self.context.insert("items", &self.items);
|
||||
let tera = get_tera("proto_file");
|
||||
match tera.render("enum.tera", &self.context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
scripts/flowy-tool/src/proto/template/proto_file/mod.rs
Normal file
5
scripts/flowy-tool/src/proto/template/proto_file/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod enum_template;
|
||||
mod struct_template;
|
||||
|
||||
pub use enum_template::*;
|
||||
pub use struct_template::*;
|
@ -0,0 +1,5 @@
|
||||
message {{ struct_name }} {
|
||||
{%- for field in fields %}
|
||||
{{ field }}
|
||||
{%- endfor %}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
use crate::util::get_tera;
|
||||
use flowy_ast::*;
|
||||
use phf::phf_map;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
// 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 {
|
||||
return 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];
|
||||
}
|
||||
|
||||
match field.bracket_category {
|
||||
Some(ref category) => match category {
|
||||
BracketCategory::Opt => 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();
|
||||
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)),
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Option<String> {
|
||||
self.context.insert("fields", &self.fields);
|
||||
let tera = get_tera("proto_file");
|
||||
match tera.render("struct.tera", &self.context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
107
scripts/flowy-tool/src/util/file.rs
Normal file
107
scripts/flowy-tool/src/util/file.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use console::Style;
|
||||
use dialoguer::Confirm;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
};
|
||||
use tera::Tera;
|
||||
|
||||
pub fn read_file(path: &str) -> Option<String> {
|
||||
let mut file = File::open(path).expect("Unable to open file");
|
||||
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, force_write: bool) {
|
||||
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.clone(), new_content.clone());
|
||||
if force_write {
|
||||
write_to_file()
|
||||
} else {
|
||||
if Confirm::new().with_prompt("Override?").interact().unwrap() {
|
||||
write_to_file()
|
||||
} else {
|
||||
log::info!("never mind then :(");
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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 file fail: {}", 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!("---------------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tera(directory: &str) -> Tera {
|
||||
let mut root = "./scripts/flowy-tool/src/proto/template/".to_owned();
|
||||
root.push_str(directory);
|
||||
|
||||
let root_absolute_path = std::fs::canonicalize(root)
|
||||
.unwrap()
|
||||
.as_path()
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
let template_path = format!("{}/**/*.tera", root_absolute_path);
|
||||
match Tera::new(template_path.as_ref()) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
log::error!("Parsing error(s): {}", e);
|
||||
::std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
3
scripts/flowy-tool/src/util/mod.rs
Normal file
3
scripts/flowy-tool/src/util/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod file;
|
||||
|
||||
pub use file::*;
|
29
scripts/makefile/protobuf.toml
Normal file
29
scripts/makefile/protobuf.toml
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
[tasks.pb]
|
||||
dependencies = ["gen_pb_file", "gen_rust_pb"]
|
||||
|
||||
[tasks.gen_pb_file]
|
||||
script = [
|
||||
"""
|
||||
pb_gen_bin=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/scripts/flowy-tool/Cargo.toml
|
||||
rust_source=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/
|
||||
|
||||
build_cache=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
|
||||
proto_file_output=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-protobuf/define
|
||||
rust_mod_dir=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-protobuf/src/
|
||||
flutter_mod_dir=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/flutter-lib/packages/flowy_protobuf/lib/
|
||||
|
||||
cargo run --manifest-path ${pb_gen_bin} pb-gen --rust_source=${rust_source} --build_cache=${build_cache} --proto_file_output=${proto_file_output} --rust_mod_dir=${rust_mod_dir} --flutter_mod_dir=${flutter_mod_dir}
|
||||
""",
|
||||
]
|
||||
script_runner = "@shell"
|
||||
|
||||
[tasks.gen_rust_pb]
|
||||
script = [
|
||||
"""
|
||||
protoc --rust_out=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-protobuf/src/model \
|
||||
--proto_path=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-protobuf/define \
|
||||
${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-protobuf/define/*.proto
|
||||
""",
|
||||
]
|
||||
script_runner = "@shell"
|
Reference in New Issue
Block a user