Skip to content

Commit ebbdaa3

Browse files
authored
feat: handle enums, functions and implementation blocks & improve html output(#2)
* remove doc asset * feat: Enhance CLI and add support for test functions - Updated CLI to accept a path to the crate root and added an option to include test functions in the diagram. - Introduced new modules for handling enums, functions, and implementation blocks, each with HTML rendering capabilities. - Refactored logic to parse Rust files recursively, organizing items by type and allowing for the inclusion/exclusion of test items. - Improved HTML templates and CSS styles for better presentation of Rust structures, enums, functions, and implementation blocks. - Added responsive design improvements to the generated HTML output.
1 parent c570345 commit ebbdaa3

12 files changed

Lines changed: 1118 additions & 158 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
lfs: true
2222
- uses: dtolnay/rust-toolchain@master
2323
with:
24-
toolchain: 1.85
24+
toolchain: stable
2525
- uses: Swatinem/rust-cache@v2
2626

2727
- name: cargo build
@@ -37,7 +37,7 @@ jobs:
3737
lfs: true
3838
- uses: dtolnay/rust-toolchain@master
3939
with:
40-
toolchain: 1.85
40+
toolchain: stable
4141
components: rustfmt, clippy
4242
- uses: Swatinem/rust-cache@v2
4343

@@ -56,7 +56,7 @@ jobs:
5656
steps:
5757
- uses: dtolnay/rust-toolchain@master
5858
with:
59-
toolchain: 1.85
59+
toolchain: stable
6060
- uses: actions/checkout@v4
6161
- uses: Swatinem/rust-cache@v2
6262
- run: cargo fetch
@@ -75,7 +75,7 @@ jobs:
7575
steps:
7676
- uses: dtolnay/rust-toolchain@master
7777
with:
78-
toolchain: 1.85
78+
toolchain: stable
7979
- uses: Swatinem/rust-cache@v2
8080
- uses: actions/checkout@v4
8181
- name: Machete

docs/assets/example.png

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/cli.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use log::LevelFilter;
77
#[command(author, version, about)]
88
/// Generate a diagram from Rust source code
99
pub struct Cli {
10-
/// Path to main.rs or lib.rs
10+
/// Path to main.rs or lib.rs or the root of the crate
1111
#[clap(short, long)]
1212
pub path: Option<PathBuf>,
1313
/// Path to output the diagram
@@ -19,4 +19,7 @@ pub struct Cli {
1919
/// Name of the Diagram
2020
#[clap(short, long, default_value = "Diagram")]
2121
pub name: String,
22+
/// Include test functions in the diagram (excluded by default)
23+
#[clap(short = 't', long, default_value = "false")]
24+
pub include_tests: bool,
2225
}

src/items/enums.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use anyhow::Context as _;
2+
use serde::Serialize;
3+
use tinytemplate::TinyTemplate;
4+
5+
use super::ToHtml;
6+
7+
const ENUM_TEMPLATE: &str = r#"
8+
<div class="enum">
9+
<div class="enum-name">{name}</div>
10+
<div class="enum-variants">
11+
{{ for variant in variants }}
12+
<div class="enum-variant">
13+
<div class="enum-variant-name">{variant.name}</div>
14+
{{ if variant.data }}
15+
<div class="enum-variant-data">{variant.data}</div>
16+
{{ endif }}
17+
</div>
18+
{{ endfor }}
19+
</div>
20+
</div>
21+
"#;
22+
23+
#[derive(Serialize)]
24+
pub struct EnumContext {
25+
pub name: String,
26+
pub variants: Vec<EnumVariantContext>,
27+
}
28+
29+
#[derive(Serialize)]
30+
pub struct EnumVariantContext {
31+
pub name: String,
32+
pub data: Option<String>,
33+
}
34+
35+
impl ToHtml for EnumContext {
36+
fn to_html(&self) -> anyhow::Result<String> {
37+
let mut tt = TinyTemplate::new();
38+
tt.set_default_formatter(&tinytemplate::format_unescaped);
39+
40+
tt.add_template("enum", ENUM_TEMPLATE)
41+
.context("Failed to add template")?;
42+
43+
tt.render("enum", self).context("Failed to render template")
44+
}
45+
}

src/items/functions.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use anyhow::Context as _;
2+
use itertools::Itertools as _;
3+
use serde::Serialize;
4+
use syn::{Visibility, spanned::Spanned as _};
5+
use tinytemplate::TinyTemplate;
6+
7+
use super::ToHtml;
8+
9+
const FUNCTION_TEMPLATE: &str = r#"
10+
<div class="function">
11+
<div class="function-signature">
12+
{{ if visibility }}<span class="function-visibility">{visibility}</span> {{ endif }}
13+
{{ if modifiers }}<span class="function-modifiers">{modifiers}</span> {{ endif }}
14+
<span class="function-name">{name}</span>
15+
<span class="function-params">({params})</span>
16+
{{ if return_type }}<span class="function-return"> -> {return_type}</span>{{ endif }}
17+
</div>
18+
</div>
19+
"#;
20+
21+
#[derive(Serialize)]
22+
pub struct FunctionContext {
23+
pub name: String,
24+
pub params: String,
25+
pub return_type: Option<String>,
26+
pub visibility: Option<String>,
27+
pub modifiers: Option<String>,
28+
}
29+
30+
impl FunctionContext {
31+
/// Create a `FunctionContext` from a `syn::Signature` and attributes
32+
pub fn new(sig: &syn::Signature, vis: &Visibility) -> Self {
33+
let visibility = match vis {
34+
Visibility::Public(_) => Some("pub".to_owned()),
35+
Visibility::Restricted(_) | Visibility::Inherited => None,
36+
};
37+
38+
let mut modifiers = Vec::new();
39+
if sig.asyncness.is_some() {
40+
modifiers.push("async".to_owned());
41+
}
42+
if sig.constness.is_some() {
43+
modifiers.push("const".to_owned());
44+
}
45+
if sig.unsafety.is_some() {
46+
modifiers.push("unsafe".to_owned());
47+
}
48+
49+
let params = sig
50+
.inputs
51+
.iter()
52+
.map(|param| match param {
53+
syn::FnArg::Receiver(r) => {
54+
r.span().source_text().expect("Could not get source_text")
55+
}
56+
syn::FnArg::Typed(t) => t.span().source_text().expect("Could not get source_text"),
57+
})
58+
.join(", ");
59+
60+
let return_type = match &sig.output {
61+
syn::ReturnType::Default => None,
62+
syn::ReturnType::Type(_, ty) => {
63+
Some(ty.span().source_text().expect("Could not get source_text"))
64+
}
65+
};
66+
67+
Self {
68+
name: sig.ident.to_string(),
69+
params,
70+
return_type,
71+
visibility,
72+
modifiers: if modifiers.is_empty() {
73+
None
74+
} else {
75+
Some(modifiers.join(" "))
76+
},
77+
}
78+
}
79+
}
80+
81+
impl ToHtml for FunctionContext {
82+
fn to_html(&self) -> anyhow::Result<String> {
83+
let mut tt = TinyTemplate::new();
84+
tt.set_default_formatter(&tinytemplate::format_unescaped);
85+
86+
tt.add_template("function", FUNCTION_TEMPLATE)
87+
.context("Failed to add template")?;
88+
89+
tt.render("function", self)
90+
.context("Failed to render template")
91+
}
92+
}

src/items/impl_blocks.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use anyhow::Context;
2+
use serde::Serialize;
3+
use tinytemplate::TinyTemplate;
4+
5+
use super::ToHtml;
6+
7+
const IMPL_TEMPLATE: &str = r#"
8+
<div class="impl-block">
9+
<div class="impl-header">
10+
<span class="impl-type">impl</span>
11+
{{ if generics }}<span class="impl-generics">{generics}</span>{{ endif }}
12+
{{ if trait_name }}<span class="impl-trait">{trait_name}</span>{{ endif }}
13+
<span class="impl-target"> for {target_type}</span>
14+
</div>
15+
<div class="impl-content">
16+
{{ for function in functions }}
17+
{function}
18+
{{ endfor }}
19+
</div>
20+
</div>
21+
"#;
22+
23+
#[derive(Serialize)]
24+
pub struct ImplContext {
25+
pub target_type: String,
26+
pub trait_name: Option<String>,
27+
pub generics: Option<String>,
28+
pub functions: Vec<String>,
29+
}
30+
31+
impl ToHtml for ImplContext {
32+
fn to_html(&self) -> anyhow::Result<String> {
33+
let mut tt = TinyTemplate::new();
34+
tt.set_default_formatter(&tinytemplate::format_unescaped);
35+
36+
tt.add_template("impl", IMPL_TEMPLATE)
37+
.context("Failed to add template")?;
38+
39+
tt.render("impl", self).context("Failed to render template")
40+
}
41+
}

src/items/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
pub mod enums;
2+
pub mod functions;
3+
pub mod impl_blocks;
14
pub mod module;
25
pub mod structs;
36

47
pub trait ToHtml {
5-
fn to_html(&self) -> String;
8+
fn to_html(&self) -> anyhow::Result<String>;
69
}

src/items/module.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
use anyhow::Context;
12
use serde::Serialize;
23
use tinytemplate::TinyTemplate;
34

45
use super::ToHtml;
56

67
const MODULE_TEMPLATE: &str = r#"
78
<div class="module">
8-
<div class="module-name">{name}</div>
9+
<input type="checkbox" id="module-{name}" class="module-toggle" checked>
10+
<label for="module-{name}" class="module-header">
11+
<span class="toggle-icon">▼</span>
12+
<span class="module-name">{name}</span>
13+
</label>
914
<div class="module-contents">
10-
{contents}
15+
{contents}
1116
</div>
1217
</div>
1318
"#;
@@ -19,14 +24,14 @@ pub struct ModContext {
1924
}
2025

2126
impl ToHtml for ModContext {
22-
fn to_html(&self) -> String {
27+
fn to_html(&self) -> anyhow::Result<String> {
2328
let mut tt = TinyTemplate::new();
2429
tt.set_default_formatter(&tinytemplate::format_unescaped);
2530

2631
tt.add_template("module", MODULE_TEMPLATE)
27-
.expect("Failed to add template");
32+
.context("Failed to add template")?;
2833

2934
tt.render("module", self)
30-
.expect("Failed to render template")
35+
.context("Failed to render template")
3136
}
3237
}

src/items/structs.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use anyhow::Context as _;
12
use serde::Serialize;
23
use tinytemplate::TinyTemplate;
34

5+
use crate::items::ToHtml;
6+
47
const STRUCT_TEMPLATE: &str = r#"
58
<div class="struct">
69
<div class="struct-name">{name}</div>
@@ -36,14 +39,14 @@ pub struct StructFieldContext {
3639
pub type_: String,
3740
}
3841

39-
impl StructContext {
40-
pub fn to_html(&self) -> String {
42+
impl ToHtml for StructContext {
43+
fn to_html(&self) -> anyhow::Result<String> {
4144
let mut tt = TinyTemplate::new();
4245

4346
tt.add_template("struct", STRUCT_TEMPLATE)
44-
.expect("Failed to add template");
47+
.context("Failed to add template")?;
4548

4649
tt.render("struct", self)
47-
.expect("Failed to render template")
50+
.context("Failed to render template")
4851
}
4952
}

0 commit comments

Comments
 (0)