mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: crates (#4258)
* chore: rename flowy-folder2 to flowy-folder * chore: rename flowy-document2 to flowy-document * chore: fix test * chore: move lib-infra crate * chore: remove shared-lib * chore: fix clippy
This commit is contained in:
49
frontend/rust-lib/build-tool/flowy-codegen/Cargo.toml
Normal file
49
frontend/rust-lib/build-tool/flowy-codegen/Cargo.toml
Normal file
@ -0,0 +1,49 @@
|
||||
[package]
|
||||
name = "flowy-codegen"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.17"
|
||||
serde = { version = "1.0", features = ["derive"]}
|
||||
serde_json.workspace = true
|
||||
flowy-ast.workspace = true
|
||||
quote = "1.0"
|
||||
|
||||
cmd_lib = { version = "1.3.0", optional = true }
|
||||
protoc-rust = { version = "2", optional = true }
|
||||
walkdir = { version = "2", optional = true }
|
||||
similar = { version = "1.3.0", optional = true }
|
||||
syn = { version = "1.0.109", features = ["extra-traits", "parsing", "derive", "full"] }
|
||||
fancy-regex = { version = "0.10.0", optional = true }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
tera = { version = "1.17.1", optional = true}
|
||||
itertools = { version = "0.10", optional = true }
|
||||
phf = { version = "0.8.0", features = ["macros"], optional = true }
|
||||
console = {version = "0.14.1", optional = true}
|
||||
protoc-bin-vendored = { version = "3.0", optional = true }
|
||||
toml = {version = "0.5.11", optional = true}
|
||||
|
||||
|
||||
[features]
|
||||
proto_gen = [
|
||||
"similar",
|
||||
"fancy-regex",
|
||||
"lazy_static",
|
||||
"tera",
|
||||
"itertools",
|
||||
"phf",
|
||||
"walkdir",
|
||||
"console",
|
||||
"toml",
|
||||
"cmd_lib",
|
||||
"protoc-rust",
|
||||
"walkdir",
|
||||
"protoc-bin-vendored",
|
||||
]
|
||||
dart_event = ["walkdir", "tera", ]
|
||||
dart = ["proto_gen", "dart_event"]
|
||||
ts_event = ["walkdir", "tera", ]
|
||||
ts = ["proto_gen", "ts_event"]
|
41
frontend/rust-lib/build-tool/flowy-codegen/src/ast.rs
Normal file
41
frontend/rust-lib/build-tool/flowy-codegen/src/ast.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use flowy_ast::EventEnumAttrs;
|
||||
use quote::format_ident;
|
||||
|
||||
#[allow(dead_code)]
|
||||
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>,
|
||||
pub event_error: String,
|
||||
}
|
||||
|
||||
impl EventASTContext {
|
||||
#[allow(dead_code)]
|
||||
pub fn from(enum_attrs: &EventEnumAttrs) -> EventASTContext {
|
||||
let command_name = enum_attrs.enum_item_name.clone();
|
||||
if command_name.is_empty() {
|
||||
panic!("Invalid command name: {}", enum_attrs.enum_item_name);
|
||||
}
|
||||
|
||||
let event = format_ident!("{}", &command_name);
|
||||
let splits = command_name.split('_').collect::<Vec<&str>>();
|
||||
|
||||
let event_ty = format_ident!("{}", enum_attrs.enum_name);
|
||||
let event_request_struct = format_ident!("{}Event", &splits.join(""));
|
||||
|
||||
let event_input = enum_attrs.event_input();
|
||||
let event_output = enum_attrs.event_output();
|
||||
let event_error = enum_attrs.event_error();
|
||||
|
||||
EventASTContext {
|
||||
event,
|
||||
event_ty,
|
||||
event_request_struct,
|
||||
event_input,
|
||||
event_output,
|
||||
event_error,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use syn::Item;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use flowy_ast::ASTResult;
|
||||
|
||||
use crate::ast::EventASTContext;
|
||||
use crate::flowy_toml::{parse_crate_config_from, CrateConfig};
|
||||
use crate::util::{is_crate_dir, is_hidden, path_string_with_component, read_file};
|
||||
|
||||
use super::event_template::*;
|
||||
|
||||
pub fn gen(crate_name: &str) {
|
||||
if std::env::var("CARGO_MAKE_WORKING_DIRECTORY").is_err() {
|
||||
println!("CARGO_MAKE_WORKING_DIRECTORY was not set, skip generate dart pb");
|
||||
return;
|
||||
}
|
||||
|
||||
if std::env::var("FLUTTER_FLOWY_SDK_PATH").is_err() {
|
||||
println!("FLUTTER_FLOWY_SDK_PATH was not set, skip generate dart pb");
|
||||
return;
|
||||
}
|
||||
|
||||
let crate_path = std::fs::canonicalize(".")
|
||||
.unwrap()
|
||||
.as_path()
|
||||
.display()
|
||||
.to_string();
|
||||
let event_crates = parse_dart_event_files(vec![crate_path]);
|
||||
let event_ast = event_crates
|
||||
.iter()
|
||||
.flat_map(parse_event_crate)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref());
|
||||
let mut render_result = DART_IMPORTED.to_owned();
|
||||
for (index, render_ctx) in event_render_ctx.into_iter().enumerate() {
|
||||
let mut event_template = EventTemplate::new();
|
||||
|
||||
if let Some(content) = event_template.render(render_ctx, index) {
|
||||
render_result.push_str(content.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
let dart_event_folder: PathBuf = [
|
||||
&std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap(),
|
||||
&std::env::var("FLUTTER_FLOWY_SDK_PATH").unwrap(),
|
||||
"lib",
|
||||
"dispatch",
|
||||
"dart_event",
|
||||
crate_name,
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
if !dart_event_folder.as_path().exists() {
|
||||
std::fs::create_dir_all(dart_event_folder.as_path()).unwrap();
|
||||
}
|
||||
|
||||
let dart_event_file_path =
|
||||
path_string_with_component(&dart_event_folder, vec!["dart_event.dart"]);
|
||||
println!("cargo:rerun-if-changed={}", dart_event_file_path);
|
||||
|
||||
match std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&dart_event_file_path)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
file.write_all(render_result.as_bytes()).unwrap();
|
||||
File::flush(file).unwrap();
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("Failed to open file: {}, {:?}", dart_event_file_path, err);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const DART_IMPORTED: &str = r#"
|
||||
/// Auto generate. Do not edit
|
||||
part of '../../dispatch.dart';
|
||||
"#;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DartEventCrate {
|
||||
crate_path: PathBuf,
|
||||
event_files: Vec<String>,
|
||||
}
|
||||
|
||||
impl DartEventCrate {
|
||||
pub fn from_config(config: &CrateConfig) -> Self {
|
||||
DartEventCrate {
|
||||
crate_path: config.crate_path.clone(),
|
||||
event_files: config.flowy_config.event_files.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_dart_event_files(crate_paths: Vec<String>) -> Vec<DartEventCrate> {
|
||||
let mut dart_event_crates: Vec<DartEventCrate> = vec![];
|
||||
crate_paths.iter().for_each(|path| {
|
||||
let crates = WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_entry(|e| !is_hidden(e))
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(is_crate_dir)
|
||||
.flat_map(|e| parse_crate_config_from(&e))
|
||||
.map(|crate_config| DartEventCrate::from_config(&crate_config))
|
||||
.collect::<Vec<DartEventCrate>>();
|
||||
dart_event_crates.extend(crates);
|
||||
});
|
||||
dart_event_crates
|
||||
}
|
||||
|
||||
pub fn parse_event_crate(event_crate: &DartEventCrate) -> Vec<EventASTContext> {
|
||||
event_crate
|
||||
.event_files
|
||||
.iter()
|
||||
.flat_map(|event_file| {
|
||||
let file_path =
|
||||
path_string_with_component(&event_crate.crate_path, vec![event_file.as_str()]);
|
||||
|
||||
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()
|
||||
.flat_map(|item| match item {
|
||||
Item::Enum(item_enum) => {
|
||||
let ast_result = ASTResult::new();
|
||||
let attrs = flowy_ast::enum_from_ast(
|
||||
&ast_result,
|
||||
&item_enum.ident,
|
||||
&item_enum.variants,
|
||||
&item_enum.attrs,
|
||||
);
|
||||
ast_result.check().unwrap();
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| !attr.attrs.event_attrs.ignore)
|
||||
.enumerate()
|
||||
.map(|(_index, variant)| EventASTContext::from(&variant.attrs))
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
_ => vec![],
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<EventASTContext>>()
|
||||
}
|
||||
|
||||
pub fn ast_to_event_render_ctx(ast: &[EventASTContext]) -> Vec<EventRenderContext> {
|
||||
ast
|
||||
.iter()
|
||||
.map(|event_ast| {
|
||||
let input_deserializer = event_ast
|
||||
.event_input
|
||||
.as_ref()
|
||||
.map(|event_input| event_input.get_ident().unwrap().to_string());
|
||||
|
||||
let output_deserializer = event_ast
|
||||
.event_output
|
||||
.as_ref()
|
||||
.map(|event_output| event_output.get_ident().unwrap().to_string());
|
||||
|
||||
EventRenderContext {
|
||||
input_deserializer,
|
||||
output_deserializer,
|
||||
error_deserializer: event_ast.event_error.clone(),
|
||||
event: event_ast.event.to_string(),
|
||||
event_ty: event_ast.event_ty.to_string(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<EventRenderContext>>()
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
use crate::util::get_tera;
|
||||
use tera::Context;
|
||||
|
||||
pub struct EventTemplate {
|
||||
tera_context: Context,
|
||||
}
|
||||
|
||||
pub struct EventRenderContext {
|
||||
pub input_deserializer: Option<String>,
|
||||
pub output_deserializer: Option<String>,
|
||||
pub error_deserializer: String,
|
||||
pub event: String,
|
||||
pub event_ty: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl EventTemplate {
|
||||
pub fn new() -> Self {
|
||||
EventTemplate {
|
||||
tera_context: Context::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, ctx: EventRenderContext, index: usize) -> Option<String> {
|
||||
self.tera_context.insert("index", &index);
|
||||
let dart_class_name = format!("{}{}", ctx.event_ty, ctx.event);
|
||||
let event = format!("{}.{}", ctx.event_ty, ctx.event);
|
||||
self.tera_context.insert("event_class", &dart_class_name);
|
||||
self.tera_context.insert("event", &event);
|
||||
|
||||
self
|
||||
.tera_context
|
||||
.insert("has_input", &ctx.input_deserializer.is_some());
|
||||
match ctx.input_deserializer {
|
||||
None => self.tera_context.insert("input_deserializer", "Unit"),
|
||||
Some(ref input) => self.tera_context.insert("input_deserializer", input),
|
||||
}
|
||||
|
||||
// eprintln!(
|
||||
// "😁 {:?} / {:?}",
|
||||
// &ctx.input_deserializer, &ctx.output_deserializer
|
||||
// );
|
||||
|
||||
let has_output = ctx.output_deserializer.is_some();
|
||||
self.tera_context.insert("has_output", &has_output);
|
||||
|
||||
match ctx.output_deserializer {
|
||||
None => self.tera_context.insert("output_deserializer", "Unit"),
|
||||
Some(ref output) => self.tera_context.insert("output_deserializer", output),
|
||||
}
|
||||
|
||||
self
|
||||
.tera_context
|
||||
.insert("error_deserializer", &ctx.error_deserializer);
|
||||
|
||||
let tera = get_tera("dart_event");
|
||||
match tera.render("event_template.tera", &self.tera_context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
class {{ event_class }} {
|
||||
{%- if has_input %}
|
||||
{{ input_deserializer }} request;
|
||||
{{ event_class }}(this.request);
|
||||
{%- else %}
|
||||
{{ event_class }}();
|
||||
{%- endif %}
|
||||
|
||||
Future<Either<{{ output_deserializer }}, {{ error_deserializer }}>> send() {
|
||||
|
||||
{%- if has_input %}
|
||||
final request = FFIRequest.create()
|
||||
..event = {{ event }}.toString()
|
||||
..payload = requestToBytes(this.request);
|
||||
|
||||
return Dispatch.asyncRequest(request)
|
||||
.then((bytesResult) => bytesResult.fold(
|
||||
|
||||
{%- if has_output %}
|
||||
(okBytes) => left({{ output_deserializer }}.fromBuffer(okBytes)),
|
||||
{%- else %}
|
||||
(bytes) => left(unit),
|
||||
{%- endif %}
|
||||
(errBytes) => right({{ error_deserializer }}.fromBuffer(errBytes)),
|
||||
));
|
||||
|
||||
{%- else %}
|
||||
final request = FFIRequest.create()
|
||||
..event = {{ event }}.toString();
|
||||
{%- if has_input %}
|
||||
..payload = bytes;
|
||||
{%- endif %}
|
||||
|
||||
return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
|
||||
{%- if has_output %}
|
||||
(okBytes) => left({{ output_deserializer }}.fromBuffer(okBytes)),
|
||||
{%- else %}
|
||||
(bytes) => left(unit),
|
||||
{%- endif %}
|
||||
(errBytes) => right({{ error_deserializer }}.fromBuffer(errBytes)),
|
||||
));
|
||||
{%- endif %}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod dart_event;
|
||||
mod event_template;
|
||||
|
||||
pub use dart_event::*;
|
68
frontend/rust-lib/build-tool/flowy-codegen/src/flowy_toml.rs
Normal file
68
frontend/rust-lib/build-tool/flowy-codegen/src/flowy_toml.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(serde::Deserialize, Clone, Debug)]
|
||||
pub struct FlowyConfig {
|
||||
#[serde(default)]
|
||||
pub event_files: Vec<String>,
|
||||
|
||||
// Collect AST from the file or directory specified by proto_input to generate the proto files.
|
||||
#[serde(default)]
|
||||
pub proto_input: Vec<String>,
|
||||
|
||||
// Output path for the generated proto files. The default value is default_proto_output()
|
||||
#[serde(default = "default_proto_output")]
|
||||
pub proto_output: String,
|
||||
|
||||
// Create a crate that stores the generated protobuf Rust structures. The default value is default_protobuf_crate()
|
||||
#[serde(default = "default_protobuf_crate")]
|
||||
pub protobuf_crate_path: String,
|
||||
}
|
||||
|
||||
fn default_proto_output() -> String {
|
||||
let mut path = PathBuf::from("resources");
|
||||
path.push("proto");
|
||||
path.to_str().unwrap().to_owned()
|
||||
}
|
||||
|
||||
fn default_protobuf_crate() -> String {
|
||||
let mut path = PathBuf::from("src");
|
||||
path.push("protobuf");
|
||||
path.to_str().unwrap().to_owned()
|
||||
}
|
||||
|
||||
impl FlowyConfig {
|
||||
pub fn from_toml_file(path: &Path) -> 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_path: PathBuf,
|
||||
pub crate_folder: String,
|
||||
pub flowy_config: FlowyConfig,
|
||||
}
|
||||
|
||||
pub fn parse_crate_config_from(entry: &walkdir::DirEntry) -> Option<CrateConfig> {
|
||||
let mut config_path = entry.path().parent().unwrap().to_path_buf();
|
||||
config_path.push("Flowy.toml");
|
||||
if !config_path.as_path().exists() {
|
||||
return None;
|
||||
}
|
||||
let crate_path = entry.path().parent().unwrap().to_path_buf();
|
||||
let flowy_config = FlowyConfig::from_toml_file(config_path.as_path());
|
||||
let crate_folder = crate_path
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
Some(CrateConfig {
|
||||
crate_path,
|
||||
crate_folder,
|
||||
flowy_config,
|
||||
})
|
||||
}
|
21
frontend/rust-lib/build-tool/flowy-codegen/src/lib.rs
Normal file
21
frontend/rust-lib/build-tool/flowy-codegen/src/lib.rs
Normal file
@ -0,0 +1,21 @@
|
||||
#[cfg(feature = "proto_gen")]
|
||||
pub mod protobuf_file;
|
||||
|
||||
#[cfg(feature = "dart_event")]
|
||||
pub mod dart_event;
|
||||
|
||||
#[cfg(feature = "ts_event")]
|
||||
pub mod ts_event;
|
||||
|
||||
#[cfg(any(feature = "proto_gen", feature = "dart_event", feature = "ts_event"))]
|
||||
mod flowy_toml;
|
||||
|
||||
pub(crate) mod ast;
|
||||
#[cfg(any(feature = "proto_gen", feature = "dart_event", feature = "ts_event"))]
|
||||
pub mod util;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct ProtoCache {
|
||||
pub structs: Vec<String>,
|
||||
pub enums: Vec<String>,
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
#![allow(unused_attributes)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
use crate::protobuf_file::template::{EnumTemplate, StructTemplate, RUST_TYPE_MAP};
|
||||
use crate::protobuf_file::{parse_crate_info_from_path, ProtoFile, ProtobufCrateContext};
|
||||
use crate::util::*;
|
||||
use fancy_regex::Regex;
|
||||
use flowy_ast::*;
|
||||
use lazy_static::lazy_static;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs::File, io::Read, path::Path};
|
||||
use syn::Item;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn parse_protobuf_context_from(crate_paths: Vec<String>) -> Vec<ProtobufCrateContext> {
|
||||
let crate_infos = parse_crate_info_from_path(crate_paths);
|
||||
crate_infos
|
||||
.into_iter()
|
||||
.map(|crate_info| {
|
||||
let proto_output_path = crate_info.proto_output_path();
|
||||
let files = crate_info
|
||||
.proto_input_paths()
|
||||
.iter()
|
||||
.flat_map(|proto_crate_path| parse_files_protobuf(proto_crate_path, &proto_output_path))
|
||||
.collect::<Vec<ProtoFile>>();
|
||||
|
||||
ProtobufCrateContext::from_crate_info(crate_info, files)
|
||||
})
|
||||
.collect::<Vec<ProtobufCrateContext>>()
|
||||
}
|
||||
|
||||
fn parse_files_protobuf(proto_crate_path: &Path, proto_output_path: &Path) -> 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 = format!("{}.proto", &file_name);
|
||||
let proto_file_path = path_string_with_component(proto_output_path, vec![&proto_file]);
|
||||
let proto_syntax = find_proto_syntax(proto_file_path.as_ref());
|
||||
|
||||
let mut proto_content = String::new();
|
||||
|
||||
// The types that are not defined in the current file.
|
||||
let mut ref_types: Vec<String> = vec![];
|
||||
structs.iter().for_each(|s| {
|
||||
let mut struct_template = StructTemplate::new();
|
||||
struct_template.set_message_struct_name(&s.name);
|
||||
|
||||
s.fields
|
||||
.iter()
|
||||
.filter(|field| field.pb_attrs.pb_index().is_some())
|
||||
.for_each(|field| {
|
||||
ref_types.push(field.ty_as_str());
|
||||
struct_template.set_field(field);
|
||||
});
|
||||
|
||||
let s = struct_template.render().unwrap();
|
||||
|
||||
proto_content.push_str(s.as_ref());
|
||||
proto_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_content.push_str(s.as_ref());
|
||||
ref_types.push(e.name.clone());
|
||||
|
||||
proto_content.push('\n');
|
||||
});
|
||||
|
||||
if !enums.is_empty() || !structs.is_empty() {
|
||||
let structs: Vec<String> = structs.iter().map(|s| s.name.clone()).collect();
|
||||
let enums: Vec<String> = enums.iter().map(|e| e.name.clone()).collect();
|
||||
ref_types.retain(|s| !structs.contains(s));
|
||||
ref_types.retain(|s| !enums.contains(s));
|
||||
|
||||
let info = ProtoFile {
|
||||
file_path: path.clone(),
|
||||
file_name: file_name.clone(),
|
||||
ref_types,
|
||||
structs,
|
||||
enums,
|
||||
syntax: proto_syntax,
|
||||
content: proto_content,
|
||||
};
|
||||
gen_proto_vec.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
gen_proto_vec
|
||||
}
|
||||
|
||||
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 ast_result = ASTResult::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(&ast_result, &item_struct.fields);
|
||||
|
||||
if fields
|
||||
.iter()
|
||||
.filter(|f| f.pb_attrs.pb_index().is_some())
|
||||
.count()
|
||||
> 0
|
||||
{
|
||||
proto_structs.push(Struct {
|
||||
name: item_struct.ident.to_string(),
|
||||
fields,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
ast_result.check().unwrap();
|
||||
proto_structs
|
||||
}
|
||||
|
||||
pub fn get_ast_enums(ast: &syn::File) -> Vec<FlowyEnum> {
|
||||
let mut flowy_enums: Vec<FlowyEnum> = vec![];
|
||||
let ast_result = ASTResult::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(
|
||||
&ast_result,
|
||||
&item_enum.ident,
|
||||
&item_enum.variants,
|
||||
&ast.attrs,
|
||||
);
|
||||
flowy_enums.push(FlowyEnum {
|
||||
name: item_enum.ident.to_string(),
|
||||
attrs,
|
||||
});
|
||||
}
|
||||
});
|
||||
ast_result.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_syntax(path: &str) -> String {
|
||||
if !Path::new(path).exists() {
|
||||
return String::from("syntax = \"proto3\";\n");
|
||||
}
|
||||
|
||||
let mut result = String::new();
|
||||
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());
|
||||
}
|
||||
|
||||
// if let Ok(Some(m)) = IMPORT_REGEX.find(line) {
|
||||
// result.push_str(m.as_str());
|
||||
// result.push('\n');
|
||||
// }
|
||||
});
|
||||
|
||||
result.push('\n');
|
||||
result
|
||||
}
|
@ -0,0 +1,300 @@
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_attributes)]
|
||||
#![allow(dead_code)]
|
||||
mod ast;
|
||||
mod proto_gen;
|
||||
mod proto_info;
|
||||
mod template;
|
||||
|
||||
use crate::util::path_string_with_component;
|
||||
use itertools::Itertools;
|
||||
use log::info;
|
||||
pub use proto_gen::*;
|
||||
pub use proto_info::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn gen(crate_name: &str) {
|
||||
let crate_path = std::fs::canonicalize(".")
|
||||
.unwrap()
|
||||
.as_path()
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
// 1. generate the proto files to proto_file_dir
|
||||
#[cfg(feature = "proto_gen")]
|
||||
let proto_crates = gen_proto_files(crate_name, &crate_path);
|
||||
|
||||
for proto_crate in proto_crates {
|
||||
let mut proto_file_paths = vec![];
|
||||
let mut file_names = vec![];
|
||||
let proto_file_output_path = proto_crate
|
||||
.proto_output_path()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let protobuf_output_path = proto_crate
|
||||
.protobuf_crate_path()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
for (path, file_name) in WalkDir::new(&proto_file_output_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);
|
||||
proto_file_paths.push(path);
|
||||
file_names.push(file_name);
|
||||
}
|
||||
}
|
||||
let protoc_bin_path = protoc_bin_vendored::protoc_bin_path().unwrap();
|
||||
|
||||
// 2. generate the protobuf files(Dart)
|
||||
#[cfg(feature = "dart")]
|
||||
generate_dart_protobuf_files(
|
||||
crate_name,
|
||||
&proto_file_output_path,
|
||||
&proto_file_paths,
|
||||
&file_names,
|
||||
&protoc_bin_path,
|
||||
);
|
||||
|
||||
#[cfg(feature = "ts")]
|
||||
generate_ts_protobuf_files(
|
||||
crate_name,
|
||||
&proto_file_output_path,
|
||||
&proto_file_paths,
|
||||
&file_names,
|
||||
&protoc_bin_path,
|
||||
);
|
||||
|
||||
// 3. generate the protobuf files(Rust)
|
||||
generate_rust_protobuf_files(
|
||||
&protoc_bin_path,
|
||||
&proto_file_paths,
|
||||
&proto_file_output_path,
|
||||
&protobuf_output_path,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_rust_protobuf_files(
|
||||
protoc_bin_path: &Path,
|
||||
proto_file_paths: &[String],
|
||||
proto_file_output_path: &str,
|
||||
protobuf_output_path: &str,
|
||||
) {
|
||||
protoc_rust::Codegen::new()
|
||||
.out_dir(protobuf_output_path)
|
||||
.protoc_path(protoc_bin_path)
|
||||
.inputs(proto_file_paths)
|
||||
.include(proto_file_output_path)
|
||||
.run()
|
||||
.expect("Running rust protoc failed.");
|
||||
}
|
||||
|
||||
#[cfg(feature = "ts")]
|
||||
fn generate_ts_protobuf_files(
|
||||
name: &str,
|
||||
proto_file_output_path: &str,
|
||||
paths: &[String],
|
||||
file_names: &Vec<String>,
|
||||
protoc_bin_path: &Path,
|
||||
) {
|
||||
let root = std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap_or("../../".to_string());
|
||||
let tauri_backend_service_path = std::env::var("TAURI_BACKEND_SERVICE_PATH")
|
||||
.unwrap_or("appflowy_tauri/src/services/backend".to_string());
|
||||
|
||||
let mut output = PathBuf::new();
|
||||
output.push(root);
|
||||
output.push(tauri_backend_service_path);
|
||||
output.push("models");
|
||||
output.push(name);
|
||||
|
||||
if !output.as_path().exists() {
|
||||
std::fs::create_dir_all(&output).unwrap();
|
||||
}
|
||||
let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned();
|
||||
paths.iter().for_each(|path| {
|
||||
// if let Err(err) = Command::new(protoc_bin_path.clone())
|
||||
// .arg(format!("--ts_out={}", output.to_str().unwrap()))
|
||||
// .arg(format!("--proto_path={}", proto_file_output_path))
|
||||
// .arg(path)
|
||||
// .spawn()
|
||||
// {
|
||||
// panic!("Generate ts pb file failed: {}, {:?}", path, err);
|
||||
// }
|
||||
|
||||
let result = cmd_lib::run_cmd! {
|
||||
${protoc_bin_path} --ts_out=${output} --proto_path=${proto_file_output_path} ${path}
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
panic!("Generate ts pb file failed with: {}, {:?}", path, result)
|
||||
};
|
||||
});
|
||||
|
||||
let ts_index = path_string_with_component(&output, vec!["index.ts"]);
|
||||
match std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&ts_index)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
let mut export = String::new();
|
||||
export.push_str("// Auto-generated, do not edit \n");
|
||||
for file_name in file_names {
|
||||
let c = format!("export * from \"./{}\";\n", file_name);
|
||||
export.push_str(c.as_ref());
|
||||
}
|
||||
|
||||
file.write_all(export.as_bytes()).unwrap();
|
||||
File::flush(file).unwrap();
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("Failed to open file: {}", err);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dart")]
|
||||
fn generate_dart_protobuf_files(
|
||||
name: &str,
|
||||
proto_file_output_path: &str,
|
||||
paths: &[String],
|
||||
file_names: &Vec<String>,
|
||||
protoc_bin_path: &Path,
|
||||
) {
|
||||
if std::env::var("CARGO_MAKE_WORKING_DIRECTORY").is_err() {
|
||||
log::error!("CARGO_MAKE_WORKING_DIRECTORY was not set, skip generate dart pb");
|
||||
return;
|
||||
}
|
||||
|
||||
if std::env::var("FLUTTER_FLOWY_SDK_PATH").is_err() {
|
||||
log::error!("FLUTTER_FLOWY_SDK_PATH was not set, skip generate dart pb");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut output = PathBuf::new();
|
||||
output.push(std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap());
|
||||
output.push(std::env::var("FLUTTER_FLOWY_SDK_PATH").unwrap());
|
||||
output.push("lib");
|
||||
output.push("protobuf");
|
||||
output.push(name);
|
||||
|
||||
if !output.as_path().exists() {
|
||||
std::fs::create_dir_all(&output).unwrap();
|
||||
}
|
||||
check_pb_dart_plugin();
|
||||
let protoc_bin_path = protoc_bin_path.to_str().unwrap().to_owned();
|
||||
paths.iter().for_each(|path| {
|
||||
let result = cmd_lib::run_cmd! {
|
||||
${protoc_bin_path} --dart_out=${output} --proto_path=${proto_file_output_path} ${path}
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
panic!("Generate dart pb file failed with: {}, {:?}", path, result)
|
||||
};
|
||||
});
|
||||
|
||||
let protobuf_dart = path_string_with_component(&output, vec!["protobuf.dart"]);
|
||||
|
||||
match std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(Path::new(&protobuf_dart))
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
let mut export = String::new();
|
||||
export.push_str("// Auto-generated, do not edit \n");
|
||||
for file_name in file_names {
|
||||
let c = format!("export './{}.pb.dart';\n", file_name);
|
||||
export.push_str(c.as_ref());
|
||||
}
|
||||
|
||||
file.write_all(export.as_bytes()).unwrap();
|
||||
File::flush(file).unwrap();
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("Failed to open file: {}", err);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_pb_dart_plugin() {
|
||||
if cfg!(target_os = "windows") {
|
||||
//Command::new("cmd")
|
||||
// .arg("/C")
|
||||
// .arg(cmd)
|
||||
// .status()
|
||||
// .expect("failed to execute process");
|
||||
//panic!("{}", format!("\n❌ The protoc-gen-dart was not installed correctly."))
|
||||
} else {
|
||||
let exit_result = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("command -v protoc-gen-dart")
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
|
||||
if !exit_result.success() {
|
||||
let mut msg = "\n❌ Can't find protoc-gen-dart in $PATH:\n".to_string();
|
||||
let output = Command::new("sh").arg("-c").arg("echo $PATH").output();
|
||||
let paths = String::from_utf8(output.unwrap().stdout)
|
||||
.unwrap()
|
||||
.split(':')
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s)));
|
||||
|
||||
if let Ok(output) = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("which protoc-gen-dart")
|
||||
.output()
|
||||
{
|
||||
msg.push_str(&format!(
|
||||
"Installed protoc-gen-dart path: {:?}\n",
|
||||
String::from_utf8(output.stdout).unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
msg.push_str("✅ You can fix that by adding:");
|
||||
msg.push_str("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n");
|
||||
msg.push_str("to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)");
|
||||
panic!("{}", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "proto_gen")]
|
||||
fn gen_proto_files(crate_name: &str, crate_path: &str) -> Vec<ProtobufCrate> {
|
||||
let crate_context = ProtoGenerator::gen(crate_name, crate_path);
|
||||
let proto_crates = crate_context
|
||||
.iter()
|
||||
.map(|info| info.protobuf_crate.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
crate_context
|
||||
.into_iter()
|
||||
.flat_map(|info| info.files)
|
||||
.for_each(|file| {
|
||||
println!("cargo:rerun-if-changed={}", file.file_path);
|
||||
});
|
||||
|
||||
proto_crates
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
#![allow(unused_attributes)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
use crate::protobuf_file::ast::parse_protobuf_context_from;
|
||||
use crate::protobuf_file::proto_info::ProtobufCrateContext;
|
||||
use crate::protobuf_file::ProtoFile;
|
||||
use crate::util::*;
|
||||
use crate::ProtoCache;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use std::{fs::OpenOptions, io::Write};
|
||||
|
||||
pub struct ProtoGenerator();
|
||||
impl ProtoGenerator {
|
||||
pub fn gen(crate_name: &str, crate_path: &str) -> Vec<ProtobufCrateContext> {
|
||||
let crate_contexts = parse_protobuf_context_from(vec![crate_path.to_owned()]);
|
||||
write_proto_files(&crate_contexts);
|
||||
write_rust_crate_mod_file(&crate_contexts);
|
||||
|
||||
let proto_cache = ProtoCache::from_crate_contexts(&crate_contexts);
|
||||
let proto_cache_str = serde_json::to_string(&proto_cache).unwrap();
|
||||
|
||||
let crate_cache_dir = path_buf_with_component(&cache_dir(), vec![crate_name]);
|
||||
if !crate_cache_dir.as_path().exists() {
|
||||
std::fs::create_dir_all(&crate_cache_dir).unwrap();
|
||||
}
|
||||
|
||||
let protobuf_cache_path = path_string_with_component(&crate_cache_dir, vec!["proto_cache"]);
|
||||
|
||||
match std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&protobuf_cache_path)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
file.write_all(proto_cache_str.as_bytes()).unwrap();
|
||||
File::flush(file).unwrap();
|
||||
},
|
||||
Err(_err) => {
|
||||
panic!("Failed to open file: {}", protobuf_cache_path);
|
||||
},
|
||||
}
|
||||
|
||||
crate_contexts
|
||||
}
|
||||
}
|
||||
|
||||
fn write_proto_files(crate_contexts: &[ProtobufCrateContext]) {
|
||||
let file_path_content_map = crate_contexts
|
||||
.iter()
|
||||
.flat_map(|ctx| {
|
||||
ctx
|
||||
.files
|
||||
.iter()
|
||||
.map(|file| {
|
||||
(
|
||||
file.file_path.clone(),
|
||||
ProtoFileSymbol {
|
||||
file_name: file.file_name.clone(),
|
||||
symbols: file.symbols(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<String, ProtoFileSymbol>>()
|
||||
})
|
||||
.collect::<HashMap<String, ProtoFileSymbol>>();
|
||||
|
||||
for context in crate_contexts {
|
||||
let dir = context.protobuf_crate.proto_output_path();
|
||||
context.files.iter().for_each(|file| {
|
||||
// syntax
|
||||
let mut file_content = file.syntax.clone();
|
||||
|
||||
// import
|
||||
file_content.push_str(&gen_import_content(file, &file_path_content_map));
|
||||
|
||||
// content
|
||||
file_content.push_str(&file.content);
|
||||
|
||||
let proto_file = format!("{}.proto", &file.file_name);
|
||||
let proto_file_path = path_string_with_component(&dir, vec![&proto_file]);
|
||||
save_content_to_file_with_diff_prompt(&file_content, proto_file_path.as_ref());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_import_content(
|
||||
current_file: &ProtoFile,
|
||||
file_path_symbols_map: &HashMap<String, ProtoFileSymbol>,
|
||||
) -> String {
|
||||
let mut import_files: Vec<String> = vec![];
|
||||
file_path_symbols_map
|
||||
.iter()
|
||||
.for_each(|(file_path, proto_file_symbols)| {
|
||||
if file_path != ¤t_file.file_path {
|
||||
current_file.ref_types.iter().for_each(|ref_type| {
|
||||
if proto_file_symbols.symbols.contains(ref_type) {
|
||||
let import_file = format!("import \"{}.proto\";", proto_file_symbols.file_name);
|
||||
if !import_files.contains(&import_file) {
|
||||
import_files.push(import_file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if import_files.len() == 1 {
|
||||
format!("{}\n", import_files.pop().unwrap())
|
||||
} else {
|
||||
import_files.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
struct ProtoFileSymbol {
|
||||
file_name: String,
|
||||
symbols: Vec<String>,
|
||||
}
|
||||
|
||||
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(" #![allow(ambiguous_glob_reexports)]\n");
|
||||
|
||||
mod_file_content.push_str("// Auto-generated, do not edit\n");
|
||||
walk_dir(
|
||||
context.protobuf_crate.proto_output_path(),
|
||||
|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);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoCache {
|
||||
fn from_crate_contexts(crate_contexts: &[ProtobufCrateContext]) -> Self {
|
||||
let proto_files = crate_contexts
|
||||
.iter()
|
||||
.flat_map(|crate_info| &crate_info.files)
|
||||
.collect::<Vec<&ProtoFile>>();
|
||||
|
||||
let structs: Vec<String> = proto_files
|
||||
.iter()
|
||||
.flat_map(|info| info.structs.clone())
|
||||
.collect();
|
||||
let enums: Vec<String> = proto_files
|
||||
.iter()
|
||||
.flat_map(|info| info.enums.clone())
|
||||
.collect();
|
||||
Self { structs, enums }
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::flowy_toml::{parse_crate_config_from, CrateConfig, FlowyConfig};
|
||||
use crate::util::*;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
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 =
|
||||
path_string_with_component(&self.protobuf_crate.protobuf_crate_path(), vec!["mod.rs"]);
|
||||
let mut content = "#![cfg_attr(rustfmt, rustfmt::skip)]\n".to_owned();
|
||||
content.push_str(" #![allow(ambiguous_glob_reexports)]\n");
|
||||
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(Path::new(&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.crate_folder);
|
||||
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.crate_folder
|
||||
);
|
||||
crate_module_dir
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ProtobufCrate {
|
||||
pub crate_folder: String,
|
||||
pub crate_path: PathBuf,
|
||||
flowy_config: FlowyConfig,
|
||||
}
|
||||
|
||||
impl ProtobufCrate {
|
||||
pub fn from_config(config: CrateConfig) -> Self {
|
||||
ProtobufCrate {
|
||||
crate_path: config.crate_path,
|
||||
crate_folder: config.crate_folder,
|
||||
flowy_config: config.flowy_config,
|
||||
}
|
||||
}
|
||||
|
||||
// Return the file paths for each rust file that used to generate the proto file.
|
||||
pub fn proto_input_paths(&self) -> Vec<PathBuf> {
|
||||
self
|
||||
.flowy_config
|
||||
.proto_input
|
||||
.iter()
|
||||
.map(|name| path_buf_with_component(&self.crate_path, vec![name]))
|
||||
.collect::<Vec<PathBuf>>()
|
||||
}
|
||||
|
||||
// The protobuf_crate_path is used to store the generated protobuf Rust structures.
|
||||
pub fn protobuf_crate_path(&self) -> PathBuf {
|
||||
let crate_path = PathBuf::from(&self.flowy_config.protobuf_crate_path);
|
||||
create_dir_if_not_exist(&crate_path);
|
||||
crate_path
|
||||
}
|
||||
|
||||
// The proto_output_path is used to store the proto files
|
||||
pub fn proto_output_path(&self) -> PathBuf {
|
||||
let output_dir = PathBuf::from(&self.flowy_config.proto_output);
|
||||
create_dir_if_not_exist(&output_dir);
|
||||
output_dir
|
||||
}
|
||||
|
||||
pub fn proto_model_mod_file(&self) -> String {
|
||||
path_string_with_component(&self.protobuf_crate_path(), vec!["mod.rs"])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProtoFile {
|
||||
pub file_path: String,
|
||||
pub file_name: String,
|
||||
pub structs: Vec<String>,
|
||||
// store the type of current file using
|
||||
pub ref_types: Vec<String>,
|
||||
|
||||
pub enums: Vec<String>,
|
||||
// proto syntax. "proto3" or "proto2"
|
||||
pub syntax: String,
|
||||
|
||||
// proto message content
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl ProtoFile {
|
||||
pub fn symbols(&self) -> Vec<String> {
|
||||
let mut symbols = self.structs.clone();
|
||||
let mut enum_symbols = self.enums.clone();
|
||||
symbols.append(&mut enum_symbols);
|
||||
symbols
|
||||
}
|
||||
}
|
||||
|
||||
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(is_crate_dir)
|
||||
.flat_map(|e| parse_crate_config_from(&e))
|
||||
.map(ProtobufCrate::from_config)
|
||||
.collect::<Vec<ProtobufCrate>>();
|
||||
protobuf_crates.extend(crates);
|
||||
});
|
||||
protobuf_crates
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
use crate::util::get_tera;
|
||||
use itertools::Itertools;
|
||||
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("protobuf_file/template/derive_meta");
|
||||
match tera.render("derive_meta.tera", &self.context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
mod derive_meta;
|
||||
|
||||
pub use derive_meta::*;
|
@ -0,0 +1,5 @@
|
||||
mod derive_meta;
|
||||
mod proto_file;
|
||||
|
||||
pub use derive_meta::*;
|
||||
pub use proto_file::*;
|
@ -0,0 +1,5 @@
|
||||
enum {{ enum_name }} {
|
||||
{%- for item in items %}
|
||||
{{ item }}
|
||||
{%- endfor %}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
use crate::protobuf_file::ast::FlowyEnum;
|
||||
use crate::util::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("protobuf_file/template/proto_file");
|
||||
match tera.render("enum.tera", &self.context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -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,110 @@
|
||||
use crate::util::get_tera;
|
||||
use flowy_ast::*;
|
||||
use phf::phf_map;
|
||||
use tera::Context;
|
||||
|
||||
// Protobuf data type : https://developers.google.com/protocol-buffers/docs/proto3
|
||||
pub 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.pb_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("protobuf_file/template/proto_file");
|
||||
match tera.render("struct.tera", &self.context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
use crate::util::get_tera;
|
||||
use tera::Context;
|
||||
|
||||
pub struct EventTemplate {
|
||||
tera_context: Context,
|
||||
}
|
||||
|
||||
pub struct EventRenderContext {
|
||||
pub input_deserializer: Option<String>,
|
||||
pub output_deserializer: Option<String>,
|
||||
pub error_deserializer: String,
|
||||
pub event: String,
|
||||
pub event_ty: String,
|
||||
pub prefix: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl EventTemplate {
|
||||
pub fn new() -> Self {
|
||||
EventTemplate {
|
||||
tera_context: Context::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, ctx: EventRenderContext, index: usize) -> Option<String> {
|
||||
self.tera_context.insert("index", &index);
|
||||
let event_func_name = format!("{}{}", ctx.event_ty, ctx.event);
|
||||
self
|
||||
.tera_context
|
||||
.insert("event_func_name", &event_func_name);
|
||||
self
|
||||
.tera_context
|
||||
.insert("event_name", &format!("{}.{}", ctx.prefix, ctx.event_ty));
|
||||
self.tera_context.insert("event", &ctx.event);
|
||||
|
||||
self
|
||||
.tera_context
|
||||
.insert("has_input", &ctx.input_deserializer.is_some());
|
||||
match ctx.input_deserializer {
|
||||
None => {},
|
||||
Some(ref input) => self
|
||||
.tera_context
|
||||
.insert("input_deserializer", &format!("{}.{}", ctx.prefix, input)),
|
||||
}
|
||||
|
||||
let has_output = ctx.output_deserializer.is_some();
|
||||
self.tera_context.insert("has_output", &has_output);
|
||||
|
||||
match ctx.output_deserializer {
|
||||
None => self.tera_context.insert("output_deserializer", "void"),
|
||||
Some(ref output) => self
|
||||
.tera_context
|
||||
.insert("output_deserializer", &format!("{}.{}", ctx.prefix, output)),
|
||||
}
|
||||
|
||||
self.tera_context.insert(
|
||||
"error_deserializer",
|
||||
&format!("{}.{}", ctx.prefix, ctx.error_deserializer),
|
||||
);
|
||||
|
||||
let tera = get_tera("ts_event");
|
||||
match tera.render("event_template.tera", &self.tera_context) {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
|
||||
{%- if has_input %}
|
||||
export async function {{ event_func_name }}(payload: {{ input_deserializer }}): Promise<Result<{{ output_deserializer }}, {{ error_deserializer }}>> {
|
||||
{%- else %}
|
||||
export async function {{ event_func_name }}(): Promise<Result<{{ output_deserializer }}, {{ error_deserializer }}>> {
|
||||
{%- endif %}
|
||||
{%- if has_input %}
|
||||
let args = {
|
||||
request: {
|
||||
ty: {{ event_name }}[{{ event_name }}.{{ event }}],
|
||||
payload: Array.from(payload.serializeBinary()),
|
||||
},
|
||||
};
|
||||
{%- else %}
|
||||
let args = {
|
||||
request: {
|
||||
ty: {{ event_name }}[{{ event_name }}.{{ event }}],
|
||||
payload: Array.from([]),
|
||||
},
|
||||
};
|
||||
{%- endif %}
|
||||
|
||||
let result: { code: number; payload: Uint8Array } = await invoke("invoke_request", args);
|
||||
if (result.code == 0) {
|
||||
{%- if has_output %}
|
||||
let object = {{ output_deserializer }}.deserializeBinary(result.payload);
|
||||
return Ok(object);
|
||||
{%- else %}
|
||||
return Ok.EMPTY;
|
||||
{%- endif %}
|
||||
} else {
|
||||
let error = {{ error_deserializer }}.deserializeBinary(result.payload);
|
||||
console.log({{ event_func_name }}.name, error);
|
||||
return Err(error);
|
||||
}
|
||||
}
|
207
frontend/rust-lib/build-tool/flowy-codegen/src/ts_event/mod.rs
Normal file
207
frontend/rust-lib/build-tool/flowy-codegen/src/ts_event/mod.rs
Normal file
@ -0,0 +1,207 @@
|
||||
mod event_template;
|
||||
|
||||
use crate::ast::EventASTContext;
|
||||
use crate::flowy_toml::{parse_crate_config_from, CrateConfig};
|
||||
use crate::ts_event::event_template::{EventRenderContext, EventTemplate};
|
||||
use crate::util::{is_crate_dir, is_hidden, path_string_with_component, read_file};
|
||||
use flowy_ast::ASTResult;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use syn::Item;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn gen(crate_name: &str) {
|
||||
let root = std::env::var("CARGO_MAKE_WORKING_DIRECTORY").unwrap_or("../../".to_string());
|
||||
let tauri_backend_service_path = std::env::var("TAURI_BACKEND_SERVICE_PATH")
|
||||
.unwrap_or("appflowy_tauri/src/services/backend".to_string());
|
||||
|
||||
let crate_path = std::fs::canonicalize(".")
|
||||
.unwrap()
|
||||
.as_path()
|
||||
.display()
|
||||
.to_string();
|
||||
let event_crates = parse_ts_event_files(vec![crate_path]);
|
||||
let event_ast = event_crates
|
||||
.iter()
|
||||
.flat_map(parse_event_crate)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let event_render_ctx = ast_to_event_render_ctx(event_ast.as_ref());
|
||||
let mut render_result = TS_HEADER.to_string();
|
||||
for (index, render_ctx) in event_render_ctx.into_iter().enumerate() {
|
||||
let mut event_template = EventTemplate::new();
|
||||
|
||||
if let Some(content) = event_template.render(render_ctx, index) {
|
||||
render_result.push_str(content.as_ref())
|
||||
}
|
||||
}
|
||||
render_result.push_str(TS_FOOTER);
|
||||
|
||||
let ts_event_folder: PathBuf = [&root, &tauri_backend_service_path, "events", crate_name]
|
||||
.iter()
|
||||
.collect();
|
||||
if !ts_event_folder.as_path().exists() {
|
||||
std::fs::create_dir_all(ts_event_folder.as_path()).unwrap();
|
||||
}
|
||||
|
||||
let event_file = "event";
|
||||
let event_file_ext = "ts";
|
||||
let ts_event_file_path = path_string_with_component(
|
||||
&ts_event_folder,
|
||||
vec![&format!("{}.{}", event_file, event_file_ext)],
|
||||
);
|
||||
println!("cargo:rerun-if-changed={}", ts_event_file_path);
|
||||
|
||||
match std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&ts_event_file_path)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
file.write_all(render_result.as_bytes()).unwrap();
|
||||
File::flush(file).unwrap();
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("Failed to open file: {}, {:?}", ts_event_file_path, err);
|
||||
},
|
||||
}
|
||||
|
||||
let ts_index = path_string_with_component(&ts_event_folder, vec!["index.ts"]);
|
||||
match std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.append(false)
|
||||
.truncate(true)
|
||||
.open(&ts_index)
|
||||
{
|
||||
Ok(ref mut file) => {
|
||||
let mut export = String::new();
|
||||
export.push_str("// Auto-generated, do not edit \n");
|
||||
export.push_str(&format!("export * from '../../models/{}';\n", crate_name));
|
||||
export.push_str(&format!("export * from './{}';\n", event_file));
|
||||
file.write_all(export.as_bytes()).unwrap();
|
||||
File::flush(file).unwrap();
|
||||
},
|
||||
Err(err) => {
|
||||
panic!("Failed to open file: {}", err);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TsEventCrate {
|
||||
crate_path: PathBuf,
|
||||
event_files: Vec<String>,
|
||||
}
|
||||
|
||||
impl TsEventCrate {
|
||||
pub fn from_config(config: &CrateConfig) -> Self {
|
||||
TsEventCrate {
|
||||
crate_path: config.crate_path.clone(),
|
||||
event_files: config.flowy_config.event_files.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_ts_event_files(crate_paths: Vec<String>) -> Vec<TsEventCrate> {
|
||||
let mut ts_event_crates: Vec<TsEventCrate> = vec![];
|
||||
crate_paths.iter().for_each(|path| {
|
||||
let crates = WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_entry(|e| !is_hidden(e))
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(is_crate_dir)
|
||||
.flat_map(|e| parse_crate_config_from(&e))
|
||||
.map(|crate_config| TsEventCrate::from_config(&crate_config))
|
||||
.collect::<Vec<TsEventCrate>>();
|
||||
ts_event_crates.extend(crates);
|
||||
});
|
||||
ts_event_crates
|
||||
}
|
||||
|
||||
pub fn parse_event_crate(event_crate: &TsEventCrate) -> Vec<EventASTContext> {
|
||||
event_crate
|
||||
.event_files
|
||||
.iter()
|
||||
.flat_map(|event_file| {
|
||||
let file_path =
|
||||
path_string_with_component(&event_crate.crate_path, vec![event_file.as_str()]);
|
||||
|
||||
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()
|
||||
.flat_map(|item| match item {
|
||||
Item::Enum(item_enum) => {
|
||||
let ast_result = ASTResult::new();
|
||||
let attrs = flowy_ast::enum_from_ast(
|
||||
&ast_result,
|
||||
&item_enum.ident,
|
||||
&item_enum.variants,
|
||||
&item_enum.attrs,
|
||||
);
|
||||
ast_result.check().unwrap();
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| !attr.attrs.event_attrs.ignore)
|
||||
.enumerate()
|
||||
.map(|(_index, variant)| EventASTContext::from(&variant.attrs))
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
_ => vec![],
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<EventASTContext>>()
|
||||
}
|
||||
|
||||
pub fn ast_to_event_render_ctx(ast: &[EventASTContext]) -> Vec<EventRenderContext> {
|
||||
let mut import_objects = HashSet::new();
|
||||
ast.iter().for_each(|event_ast| {
|
||||
if let Some(input) = event_ast.event_input.as_ref() {
|
||||
import_objects.insert(input.get_ident().unwrap().to_string());
|
||||
}
|
||||
if let Some(output) = event_ast.event_output.as_ref() {
|
||||
import_objects.insert(output.get_ident().unwrap().to_string());
|
||||
}
|
||||
});
|
||||
|
||||
ast
|
||||
.iter()
|
||||
.map(|event_ast| {
|
||||
let input_deserializer = event_ast
|
||||
.event_input
|
||||
.as_ref()
|
||||
.map(|event_input| event_input.get_ident().unwrap().to_string());
|
||||
|
||||
let output_deserializer = event_ast
|
||||
.event_output
|
||||
.as_ref()
|
||||
.map(|event_output| event_output.get_ident().unwrap().to_string());
|
||||
|
||||
EventRenderContext {
|
||||
input_deserializer,
|
||||
output_deserializer,
|
||||
error_deserializer: event_ast.event_error.to_string(),
|
||||
event: event_ast.event.to_string(),
|
||||
event_ty: event_ast.event_ty.to_string(),
|
||||
prefix: "pb".to_string(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<EventRenderContext>>()
|
||||
}
|
||||
|
||||
const TS_HEADER: &str = r#"
|
||||
/// Auto generate. Do not edit
|
||||
import { Ok, Err, Result } from "ts-results";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import * as pb from "../..";
|
||||
"#;
|
||||
|
||||
const TS_FOOTER: &str = r#"
|
||||
"#;
|
190
frontend/rust-lib/build-tool/flowy-codegen/src/util.rs
Normal file
190
frontend/rust-lib/build-tool/flowy-codegen/src/util.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use console::Style;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
fs::{File, OpenOptions},
|
||||
io::{Read, Write},
|
||||
};
|
||||
use tera::Tera;
|
||||
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: &Path) {
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir_all(dir).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_string_with_component(path: &Path, components: Vec<&str>) -> String {
|
||||
path_buf_with_component(path, components)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn path_buf_with_component(path: &Path, components: Vec<&str>) -> PathBuf {
|
||||
let mut path_buf = path.to_path_buf();
|
||||
for component in components {
|
||||
path_buf.push(component);
|
||||
}
|
||||
path_buf
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn walk_dir<P: AsRef<Path>, F1, F2>(dir: P, 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()
|
||||
}
|
||||
|
||||
pub fn get_tera(directory: &str) -> Tera {
|
||||
let mut root = format!("{}/src/", env!("CARGO_MANIFEST_DIR"));
|
||||
root.push_str(directory);
|
||||
|
||||
let root_absolute_path = match std::fs::canonicalize(&root) {
|
||||
Ok(p) => p.as_path().display().to_string(),
|
||||
Err(e) => {
|
||||
panic!("❌ Canonicalize file path {} failed {:?}", root, e);
|
||||
},
|
||||
};
|
||||
|
||||
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 cache_dir() -> PathBuf {
|
||||
let mut path_buf = PathBuf::from_str(env!("CARGO_MANIFEST_DIR")).unwrap();
|
||||
path_buf.push(".cache");
|
||||
path_buf
|
||||
}
|
Reference in New Issue
Block a user