Skip to content

Commit 5557c7b

Browse files
committed
add basic doc generate
1 parent 7cbf1b2 commit 5557c7b

File tree

13 files changed

+518
-36
lines changed

13 files changed

+518
-36
lines changed

crates/code_analysis/src/db_index/type/type_decl.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,17 @@ impl LuaTypeDeclId {
215215
pub fn get_name(&self) -> &str {
216216
&self.id
217217
}
218+
219+
pub fn get_simple_name(&self) -> &str {
220+
let basic_name = self.get_name();
221+
let just_name = if let Some(i) = basic_name.rfind('.') {
222+
&basic_name[i + 1..]
223+
} else {
224+
&basic_name
225+
};
226+
227+
&just_name
228+
}
218229
}
219230

220231
impl Serialize for LuaTypeDeclId {

crates/code_analysis/src/vfs/loader.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
};
77
use wax::Pattern;
88

9-
use log::error;
9+
use log::{error, info};
1010
use walkdir::WalkDir;
1111

1212
#[derive(Debug)]
@@ -85,13 +85,23 @@ pub fn load_workspace_files(
8585
}
8686

8787
pub fn read_file_with_encoding(path: &Path, encoding: &str) -> Option<String> {
88-
let content = fs::read(path).ok()?;
88+
let origin_content = fs::read(path).ok()?;
8989
let encoding = Encoding::for_label(encoding.as_bytes()).unwrap_or(UTF_8);
90-
91-
let (content, has_error) = encoding.decode_with_bom_removal(&content);
90+
let (content, has_error) = encoding.decode_with_bom_removal(&origin_content);
9291
if has_error {
9392
error!("Error decoding file: {:?}", path);
94-
return None;
93+
if encoding == UTF_8 {
94+
return None;
95+
}
96+
97+
info!("Try utf-8 encoding");
98+
let (content, _, hash_error) = UTF_8.decode(&origin_content);
99+
if hash_error {
100+
error!("Try utf8 fail, error decoding file: {:?}", path);
101+
return None;
102+
}
103+
104+
return Some(content.to_string());
95105
}
96106

97107
Some(content.to_string())

crates/emmylua_doc_cli/src/cmd_args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct CmdArgs {
1616
default_value = "./output",
1717
long = "output",
1818
short = "o",
19-
help = "The output path of the markdown file"
19+
help = "The output path of the docs file"
2020
)]
2121
pub output: std::path::PathBuf,
2222
}

crates/emmylua_doc_cli/src/init.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ pub fn load_workspace(workspace_folders: Vec<&str>) -> Option<EmmyLuaAnalysis> {
1111
.iter()
1212
.map(|s| PathBuf::from(s))
1313
.collect::<Vec<_>>();
14+
for path in &paths {
15+
analysis.add_workspace_root(path.clone());
16+
}
17+
1418
let main_path = paths.get(0)?.clone();
1519
let config_files = vec![
1620
main_path.join(".luarc.json"),

crates/emmylua_doc_cli/src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
use cmd_args::CmdArgs;
2+
use structopt::StructOpt;
3+
14
mod cmd_args;
25
mod init;
36
mod markdown_generator;
47

58
fn main() {
6-
println!("Hello, world!");
9+
let args = CmdArgs::from_args();
10+
let analysis = init::load_workspace(vec![args.input.to_str().unwrap()]);
11+
if let Some(mut analysis) = analysis {
12+
markdown_generator::generate_markdown(&mut analysis, &args.input, &args.output);
13+
}
714
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use tera::Tera;
2+
3+
use super::MkdocsIndex;
4+
5+
pub fn generate_index(tl: &Tera, mkdocs: &MkdocsIndex, output: &std::path::PathBuf) -> Option<()> {
6+
let mut context = tera::Context::new();
7+
if !mkdocs.types.is_empty() {
8+
context.insert("types", &mkdocs.types);
9+
}
10+
if !mkdocs.modules.is_empty() {
11+
context.insert("modules", &mkdocs.modules);
12+
}
13+
let index_path = output.join("docs/index.md");
14+
let index_text = match tl.render("index_template.tl", &context) {
15+
Ok(text) => text,
16+
Err(e) => {
17+
eprintln!("Failed to render index: {}", e);
18+
return None;
19+
}
20+
};
21+
22+
std::fs::write(index_path, index_text).ok()?;
23+
24+
let mkdocs_yml_path = output.join("mkdocs.yml");
25+
let mkdocs_yml_text = match tl.render("mkdocs_template.tl", &context) {
26+
Ok(text) => text,
27+
Err(e) => {
28+
eprintln!("Failed to render mkdocs.yml: {}", e);
29+
return None;
30+
}
31+
};
32+
33+
std::fs::write(mkdocs_yml_path, mkdocs_yml_text).ok()?;
34+
35+
Some(())
36+
}

crates/emmylua_doc_cli/src/markdown_generator/mod.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,42 @@
1+
mod index_gen;
12
mod mod_gen;
23
mod render;
34
mod typ_gen;
45

56
use std::path::PathBuf;
67

78
use code_analysis::EmmyLuaAnalysis;
9+
use serde::{Deserialize, Serialize};
810
use tera::Tera;
911

1012
#[allow(unused)]
11-
pub fn generate_markdown(analysis: &mut EmmyLuaAnalysis, output: &PathBuf) -> Option<()> {
12-
let types_out = output.join("types");
13-
let module_out = output.join("modules");
13+
pub fn generate_markdown(
14+
analysis: &mut EmmyLuaAnalysis,
15+
input: &PathBuf,
16+
output: &PathBuf,
17+
) -> Option<()> {
18+
let docs_dir = output.join("docs");
19+
let types_out = docs_dir.join("types");
20+
if !types_out.exists() {
21+
println!("Creating types directory: {:?}", types_out);
22+
std::fs::create_dir_all(&types_out).ok()?;
23+
} else {
24+
println!("Clearing types directory: {:?}", types_out);
25+
std::fs::remove_dir_all(&types_out).ok()?;
26+
std::fs::create_dir_all(&types_out).ok()?;
27+
}
28+
29+
let module_out = docs_dir.join("modules");
30+
if !module_out.exists() {
31+
println!("Creating modules directory: {:?}", module_out);
32+
std::fs::create_dir_all(&module_out).ok()?;
33+
} else {
34+
println!("Clearing modules directory: {:?}", module_out);
35+
std::fs::remove_dir_all(&module_out).ok()?;
36+
std::fs::create_dir_all(&module_out).ok()?;
37+
}
38+
39+
let mut mkdocs_index = MkdocsIndex::default();
1440
let resources = analysis.get_resource_dir()?;
1541
let tl_template = resources.join("template/**/*.tl");
1642
let tl = match Tera::new(tl_template.to_string_lossy().as_ref()) {
@@ -24,8 +50,47 @@ pub fn generate_markdown(analysis: &mut EmmyLuaAnalysis, output: &PathBuf) -> Op
2450
let type_index = db.get_type_index();
2551
let types = type_index.get_all_types();
2652
for type_decl in types {
27-
typ_gen::generate_type_markdown(db, &tl, type_decl, &types_out);
53+
typ_gen::generate_type_markdown(db, &tl, type_decl, &input, &types_out, &mut mkdocs_index);
2854
}
2955

56+
let module_index = db.get_module_index();
57+
let modules = module_index.get_module_infos();
58+
for module in modules {
59+
mod_gen::generate_module_markdown(db, &tl, module, &input, &module_out, &mut mkdocs_index);
60+
}
61+
62+
index_gen::generate_index(&tl, &mkdocs_index, output);
3063
Some(())
3164
}
65+
66+
#[derive(Debug, Serialize, Deserialize)]
67+
struct MemberDisplay {
68+
pub name: String,
69+
pub display: String,
70+
pub description: String,
71+
}
72+
73+
#[derive(Debug, Serialize, Deserialize, Default)]
74+
struct MkdocsIndex {
75+
pub types: Vec<IndexStruct>,
76+
pub modules: Vec<IndexStruct>,
77+
}
78+
79+
#[derive(Debug, Serialize, Deserialize)]
80+
struct IndexStruct {
81+
pub name: String,
82+
pub file: String,
83+
}
84+
85+
fn escape_type_name(name: &str) -> String {
86+
name.chars()
87+
.map(|c| {
88+
// Windows 无效文件名字符
89+
if "<>:\"/\\|?*".contains(c) {
90+
'_'
91+
} else {
92+
c
93+
}
94+
})
95+
.collect()
96+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use std::path::Path;
2+
3+
use code_analysis::{
4+
humanize_type, DbIndex, FileId, LuaMemberKey, LuaMemberOwner, LuaPropertyOwnerId, LuaType,
5+
ModuleInfo,
6+
};
7+
use emmylua_parser::VisibilityKind;
8+
use tera::{Context, Tera};
9+
10+
use crate::markdown_generator::{escape_type_name, IndexStruct, MemberDisplay};
11+
12+
use super::{
13+
render::{render_const_type, render_function_type},
14+
MkdocsIndex,
15+
};
16+
17+
pub fn generate_module_markdown(
18+
db: &DbIndex,
19+
tl: &Tera,
20+
module: &ModuleInfo,
21+
input: &Path,
22+
output: &Path,
23+
mkdocs_index: &mut MkdocsIndex,
24+
) -> Option<()> {
25+
check_filter(db, module.file_id, input)?;
26+
27+
let mut context = tera::Context::new();
28+
context.insert("module_name", &module.name);
29+
30+
let export_typ = module.export_type.clone()?;
31+
match &export_typ {
32+
LuaType::Def(type_id) => {
33+
let member_owner = LuaMemberOwner::Type(type_id.clone());
34+
let type_simple_name = type_id.get_simple_name();
35+
generate_member_owner_module(db, member_owner, type_simple_name, &mut context);
36+
}
37+
LuaType::TableConst(t) => {
38+
let member_owner = LuaMemberOwner::Element(t.clone());
39+
generate_member_owner_module(db, member_owner, "M", &mut context);
40+
}
41+
LuaType::Instance(i) => {
42+
let member_owner = LuaMemberOwner::Element(i.get_range().clone());
43+
generate_member_owner_module(db, member_owner, "M", &mut context);
44+
}
45+
_ => {}
46+
}
47+
48+
let render_text = match tl.render("lua_module_template.tl", &context) {
49+
Ok(text) => text,
50+
Err(e) => {
51+
eprintln!("Failed to render template: {}", e);
52+
return None;
53+
}
54+
};
55+
56+
let file_name = format!("{}.md", escape_type_name(&module.full_module_name));
57+
mkdocs_index.modules.push(IndexStruct {
58+
name: module.full_module_name.clone(),
59+
file: format!("modules/{}", file_name.clone()),
60+
});
61+
62+
let outpath = output.join(file_name);
63+
println!("output module file: {}", outpath.display());
64+
match std::fs::write(outpath, render_text) {
65+
Ok(_) => {}
66+
Err(e) => {
67+
eprintln!("Failed to write file: {}", e);
68+
return None;
69+
}
70+
}
71+
Some(())
72+
}
73+
74+
fn check_filter(db: &DbIndex, file_id: FileId, workspace: &Path) -> Option<()> {
75+
let file_path = db.get_vfs().get_file_path(&file_id)?;
76+
if !file_path.starts_with(workspace) {
77+
return None;
78+
}
79+
Some(())
80+
}
81+
82+
fn generate_member_owner_module(
83+
db: &DbIndex,
84+
member_owner: LuaMemberOwner,
85+
owner_name: &str,
86+
context: &mut Context,
87+
) -> Option<()> {
88+
let member_map = db.get_member_index().get_member_map(member_owner);
89+
let mut method_members: Vec<MemberDisplay> = Vec::new();
90+
let mut field_members: Vec<MemberDisplay> = Vec::new();
91+
if let Some(member_map) = member_map {
92+
for (member_name, member_id) in member_map {
93+
let member = db.get_member_index().get_member(member_id)?;
94+
let member_typ = member.get_decl_type();
95+
let member_property_id = LuaPropertyOwnerId::Member(member_id.clone());
96+
let member_property = db.get_property_index().get_property(member_property_id);
97+
if let Some(member_property) = member_property {
98+
if member_property.visibility.unwrap_or(VisibilityKind::Public)
99+
!= VisibilityKind::Public
100+
{
101+
continue;
102+
}
103+
}
104+
105+
let description = if let Some(member_property) = member_property {
106+
*member_property
107+
.description
108+
.clone()
109+
.unwrap_or("".to_string().into())
110+
} else {
111+
"".to_string()
112+
};
113+
114+
let name = match member_name {
115+
LuaMemberKey::Name(name) => name,
116+
_ => continue,
117+
};
118+
119+
let title_name = format!("{}.{}", owner_name, name);
120+
if member_typ.is_function() {
121+
let func_name = format!("{}.{}", owner_name, name);
122+
let display = render_function_type(db, member_typ, &func_name, false);
123+
method_members.push(MemberDisplay {
124+
name: title_name,
125+
display,
126+
description,
127+
});
128+
} else if member_typ.is_const() {
129+
let display = render_const_type(db, &member_typ);
130+
field_members.push(MemberDisplay {
131+
name: title_name,
132+
display: format!("{}.{}: {}", owner_name, name, display),
133+
description,
134+
});
135+
} else {
136+
let typ_display = humanize_type(db, &member_typ);
137+
field_members.push(MemberDisplay {
138+
name: title_name,
139+
display: format!("{}.{} : {}", owner_name, name, typ_display),
140+
description,
141+
});
142+
}
143+
}
144+
}
145+
if !method_members.is_empty() {
146+
context.insert("methods", &method_members);
147+
}
148+
149+
if !field_members.is_empty() {
150+
context.insert("fields", &field_members);
151+
}
152+
153+
Some(())
154+
}

0 commit comments

Comments
 (0)