config event template

This commit is contained in:
appflowy 2021-07-07 14:14:37 +08:00
parent f1a229002f
commit 569da533a1
22 changed files with 456 additions and 130 deletions

View File

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

View File

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

View File

@ -255,14 +255,22 @@ pub enum Default {
Path(syn::ExprPath),
}
#[derive(Debug, Clone)]
pub struct EventAttrs {
input: Option<syn::Path>,
output: Option<syn::Path>,
pub ignore: bool,
}
#[derive(Debug, Clone)]
pub struct ASTEnumAttrVariant {
pub name: String,
pub value: String,
pub event_attrs: EventAttrs,
}
impl ASTEnumAttrVariant {
pub fn from_ast(_cx: &Ctxt, variant: &syn::Variant) -> Self {
pub fn from_ast(ctxt: &Ctxt, variant: &syn::Variant) -> Self {
let name = variant.ident.to_string();
let mut value = String::new();
if variant.discriminant.is_some() {
@ -278,8 +286,70 @@ impl ASTEnumAttrVariant {
_ => {},
}
}
ASTEnumAttrVariant { name, value }
let mut event_attrs = EventAttrs {
input: None,
output: None,
ignore: false,
};
variant.attrs.iter().for_each(|attr| match get_meta_items(ctxt, attr) {
Ok(meta_items) => {
for meta_item in meta_items {
match &meta_item {
Meta(NameValue(name_value)) => {
if name_value.path == EVENT_INPUT {
if let syn::Lit::Str(s) = &name_value.lit {
let input_type = parse_lit_str(s)
.map_err(|_| {
ctxt.error_spanned_by(
s,
format!("failed to parse request deserializer {:?}", s.value()),
)
})
.unwrap();
event_attrs.input = Some(input_type);
}
}
if name_value.path == EVENT_OUTPUT {
if let syn::Lit::Str(s) = &name_value.lit {
let output_type = parse_lit_str(s)
.map_err(|_| {
ctxt.error_spanned_by(
s,
format!("failed to parse response deserializer {:?}", s.value()),
)
})
.unwrap();
event_attrs.output = Some(output_type);
}
}
},
Meta(Path(word)) => {
if word == EVENT_IGNORE && attr.path == EVENT {
event_attrs.ignore = true;
}
},
Lit(s) => {
ctxt.error_spanned_by(s, "unexpected type in cqrs container attribute");
},
_ => {
ctxt.error_spanned_by(meta_item, "unexpected type in cqrs container attribute");
},
}
}
},
Err(_) => {},
});
ASTEnumAttrVariant {
name,
value,
event_attrs,
}
}
pub fn event_input(&self) -> Option<syn::Path> { self.event_attrs.input.clone() }
pub fn event_output(&self) -> Option<syn::Path> { self.event_attrs.output.clone() }
}
pub fn get_meta_items(cx: &Ctxt, attr: &syn::Attribute) -> Result<Vec<syn::NestedMeta>, ()> {

View File

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

View File

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

View File

@ -14,6 +14,11 @@ pub const SKIP_SERIALIZING: Symbol = Symbol("skip_serializing"); //#[pb(skip_ser
pub const PB_STRUCT: Symbol = Symbol("struct"); //#[pb(struct="some struct")]
pub const PB_ENUM: Symbol = Symbol("enum"); //#[pb(enum="some enum")]
pub const EVENT_INPUT: Symbol = Symbol("input");
pub const EVENT_OUTPUT: Symbol = Symbol("output");
pub const EVENT_IGNORE: Symbol = Symbol("ignore");
pub const EVENT: Symbol = Symbol("event");
impl PartialEq<Symbol> for Ident {
fn eq(&self, word: &Symbol) -> bool { self == word.0 }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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