2022-02-09 18:39:58 +08:00

190 lines
6.1 KiB
Rust

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