cod8a 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cod8a/__init__.py +3 -0
- cod8a/cli.py +93 -0
- cod8a/cod8a.py +4 -0
- cod8a/dotnet/CodeAnalysis/CodeAnalyzer.csproj +14 -0
- cod8a/dotnet/CodeAnalysis/CodeAnalyzer.csproj.lscache +262 -0
- cod8a/dotnet/CodeAnalysis/Parsers/BaseParser.cs +17 -0
- cod8a/dotnet/CodeAnalysis/Parsers/FileParser.cs +150 -0
- cod8a/dotnet/CodeAnalysis/Parsers/ProjectParser.cs +37 -0
- cod8a/dotnet/CodeAnalysis/Parsers/SolutionParser.cs +17 -0
- cod8a/dotnet/CodeAnalysis/Program.cs +58 -0
- cod8a/dotnet/CodeAnalysis/models/FileStructure.cs +92 -0
- cod8a/dotnet/CodeAnalysis/models/ProjectStructure.cs +29 -0
- cod8a/dotnet/CodeAnalysis/models/SolutionStructure.cs +28 -0
- cod8a/dotnet/Test/CodeAnalyzerTest.csproj +25 -0
- cod8a/dotnet/Test/Mermaid/ClassDiagramTest.cs +71 -0
- cod8a/enums/__init__.py +0 -0
- cod8a/enums/diagram_type.py +10 -0
- cod8a/generators/mermaid/class_diagram.py +165 -0
- cod8a/generators/mermaid/flowchart_diagram.py +51 -0
- cod8a/generators/mermaid/sequence_diagram.py +80 -0
- cod8a/helpers/__init__.py +0 -0
- cod8a/helpers/cli_helper.py +79 -0
- cod8a/models/__init__.py +0 -0
- cod8a/models/models.py +60 -0
- cod8a/parsers/dotnet_parser.py +100 -0
- cod8a/parsers/python_parser.py +138 -0
- cod8a-0.1.0.dist-info/METADATA +134 -0
- cod8a-0.1.0.dist-info/RECORD +31 -0
- cod8a-0.1.0.dist-info/WHEEL +4 -0
- cod8a-0.1.0.dist-info/entry_points.txt +4 -0
- cod8a-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
2
|
+
using System;
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Text;
|
|
5
|
+
|
|
6
|
+
namespace CodeAnalyzer.Models
|
|
7
|
+
{
|
|
8
|
+
/// <summary>
|
|
9
|
+
/// Represents the structure of a file, including its identifier and associated namespace details.
|
|
10
|
+
/// </summary>
|
|
11
|
+
public class FileStructure
|
|
12
|
+
{
|
|
13
|
+
public int Id { get; set; }
|
|
14
|
+
/// <summary>
|
|
15
|
+
/// <see langword="nameof"/>of the namespace, e.g., <c>MyNamespace</c>.
|
|
16
|
+
/// </summary>
|
|
17
|
+
public string Name { get; set; }
|
|
18
|
+
|
|
19
|
+
/// <summary>
|
|
20
|
+
/// Gets or sets the collection of using directives associated with the current context.
|
|
21
|
+
/// </summary>
|
|
22
|
+
public List<UsingDirective> UsingDirectives { get; set; }
|
|
23
|
+
|
|
24
|
+
/// <summary>
|
|
25
|
+
/// Gets or sets the collection of classes associated with the current instance.
|
|
26
|
+
/// </summary>
|
|
27
|
+
public List<ClassStructure> Classes { get; set; }
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// <summary>
|
|
32
|
+
/// Represents the relationship the file has with other file(s)
|
|
33
|
+
/// </summary>
|
|
34
|
+
/// <param name="Id">Id of the instance</param>
|
|
35
|
+
/// <param name="Type">Type of relationship</param>
|
|
36
|
+
/// <param name="Parent">Base item</param>
|
|
37
|
+
public record RelationShip(int Id, string Type, string AssociatedItem);
|
|
38
|
+
|
|
39
|
+
public record UsingDirective(int Id, string Name);
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/// <summary>
|
|
43
|
+
/// Represents a class, including its name and contained methods.
|
|
44
|
+
/// </summary>
|
|
45
|
+
/// <param name="Id"></param>
|
|
46
|
+
/// <param name="Name"> Gets or sets the name associated with the object. </param>
|
|
47
|
+
/// <param name="Methods"> Gets or sets the collection of methods associated with this instance. </param>
|
|
48
|
+
/// <param name="Fields"> Gets or sets the collection of field definitions associated with the structure. </param>
|
|
49
|
+
/// <param name="Type"> Gets or sets the type associated with the current instance. </param>
|
|
50
|
+
/// <param name="Relationships"> Gets or sets the collection of relationships associated with the current object. </param>
|
|
51
|
+
/// <param name="Summary"> Gets or sets the summary description for the current object. </param>
|
|
52
|
+
public record ClassStructure(int Id, string Name, List<MethodStructure> Methods, List<FieldStructure> Fields, string Type, List<RelationShip>? Relationships, string Summary);
|
|
53
|
+
|
|
54
|
+
/// <summary>
|
|
55
|
+
/// Represents the metadata for a method, including its name, access modifier, return type, and parameters.
|
|
56
|
+
/// </summary>
|
|
57
|
+
/// <param name="Id"></param>
|
|
58
|
+
/// <param name="Name"> Gets or sets the name associated with the function. </param>
|
|
59
|
+
/// <param name="Modifier"> Gets or sets the modifier associated with the function. </param>
|
|
60
|
+
/// <param name="ReturnType"> Gets or sets the return type associated with the function. </param>
|
|
61
|
+
/// <param name="Parameters"> Gets or sets the collection of parameters associated with the function. </param>
|
|
62
|
+
/// <param name="Summary"> Gets or sets the summary description for the current object. </param>
|
|
63
|
+
/// <remarks>Use this class to describe the signature and characteristics of a method in code analysis,
|
|
64
|
+
/// code generation, or documentation scenarios. The class does not represent the method's implementation or
|
|
65
|
+
/// behavior.</remarks>
|
|
66
|
+
public record MethodStructure(int Id, string Name, string Modifier, string ReturnType, List<ParameterStructure> Parameters, string Summary);
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/// <param name="Id"></param>
|
|
71
|
+
/// <param name="Name"> Gets or sets the name associated with the field. </param>
|
|
72
|
+
/// <param name="Modifier"> Gets or sets the modifier associated with the field. </param>
|
|
73
|
+
/// <param name="Type"> Gets or sets the type associated with the field. </param>
|
|
74
|
+
/// <param name="Summary"> Gets or sets the summary description for the current object. </param>
|
|
75
|
+
public record FieldStructure(int Id, string Name, string Modifier, string Type, string Summary);
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/// <summary>
|
|
82
|
+
/// Represents a parameter definition, including its name, modifier, and type information.
|
|
83
|
+
/// </summary>
|
|
84
|
+
/// <param name="Name"> Gets or sets the name associated with the object. </param>
|
|
85
|
+
/// <param name="Modifier"> Gets or sets the modifier associated with the current instance. </param>
|
|
86
|
+
/// <param name="Type"> Gets or sets the type associated with the current instance. </param>
|
|
87
|
+
/// <param name="Summary"> Gets or sets the summary description for the current object. </param>
|
|
88
|
+
/// <remarks>Use this class to describe parameters for methods, constructors, or other callable members.
|
|
89
|
+
/// The properties provide metadata about the parameter's identifier, its modifier (such as 'ref', 'out', or 'in'),
|
|
90
|
+
/// and its data type.</remarks>
|
|
91
|
+
public record ParameterStructure(string Name, string Modifier, string Type, string Summary);
|
|
92
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Text;
|
|
4
|
+
|
|
5
|
+
namespace CodeAnalyzer.Models
|
|
6
|
+
{
|
|
7
|
+
public class ProjectStructure
|
|
8
|
+
{
|
|
9
|
+
/// <summary>
|
|
10
|
+
/// Id of the Project
|
|
11
|
+
/// </summary>
|
|
12
|
+
public int Id { get; set; }
|
|
13
|
+
|
|
14
|
+
/// <summary>
|
|
15
|
+
/// Name of the Project
|
|
16
|
+
/// </summary>
|
|
17
|
+
public string Name { get; set; }
|
|
18
|
+
|
|
19
|
+
/// <summary>
|
|
20
|
+
/// Project Description
|
|
21
|
+
/// </summary>
|
|
22
|
+
public string Description { get; set; }
|
|
23
|
+
|
|
24
|
+
/// <summary>
|
|
25
|
+
/// Gets or sets the collection of files associated with the Project.
|
|
26
|
+
/// </summary>
|
|
27
|
+
public List<FileStructure> Files { get; set; }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.Text;
|
|
4
|
+
|
|
5
|
+
namespace CodeAnalyzer.Models
|
|
6
|
+
{
|
|
7
|
+
public class SolutionStructure
|
|
8
|
+
{
|
|
9
|
+
/// <summary>
|
|
10
|
+
/// Gets or sets the ID of the Solution
|
|
11
|
+
/// </summary>
|
|
12
|
+
public int Id { get; set; }
|
|
13
|
+
/// <summary>
|
|
14
|
+
/// Gets or sets the name of the Solution
|
|
15
|
+
/// </summary>
|
|
16
|
+
public string Name { get; set; }
|
|
17
|
+
|
|
18
|
+
/// <summary>
|
|
19
|
+
/// Gets or sets the description associated with the object.
|
|
20
|
+
/// </summary>
|
|
21
|
+
public string Description { get; set; }
|
|
22
|
+
|
|
23
|
+
/// <summary>
|
|
24
|
+
/// Gets or sets the collection of projects within this solution
|
|
25
|
+
/// </summary>
|
|
26
|
+
public List<ProjectStructure> Projects { get; set; }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<TargetFramework>net10.0</TargetFramework>
|
|
5
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
6
|
+
<Nullable>enable</Nullable>
|
|
7
|
+
<IsPackable>false</IsPackable>
|
|
8
|
+
</PropertyGroup>
|
|
9
|
+
|
|
10
|
+
<ItemGroup>
|
|
11
|
+
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
|
12
|
+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
|
13
|
+
<PackageReference Include="xunit" Version="2.9.3" />
|
|
14
|
+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
|
15
|
+
</ItemGroup>
|
|
16
|
+
|
|
17
|
+
<ItemGroup>
|
|
18
|
+
<Using Include="Xunit" />
|
|
19
|
+
</ItemGroup>
|
|
20
|
+
|
|
21
|
+
<ItemGroup>
|
|
22
|
+
<ProjectReference Include="..\CodeAnalysis\CodeAnalyzer.csproj" />
|
|
23
|
+
</ItemGroup>
|
|
24
|
+
|
|
25
|
+
</Project>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
4
|
+
using Xunit;
|
|
5
|
+
using CodeAnalyzer.Parsers;
|
|
6
|
+
using CodeAnalyzer.Models;
|
|
7
|
+
|
|
8
|
+
namespace MermaidTests.Mermaid
|
|
9
|
+
{
|
|
10
|
+
public class ClassDiagramTest
|
|
11
|
+
{
|
|
12
|
+
[Fact]
|
|
13
|
+
public void Parse_GeneratesCorrectRelationshipsForMermaid()
|
|
14
|
+
{
|
|
15
|
+
// Arrange
|
|
16
|
+
var testCode = @"
|
|
17
|
+
namespace TestNamespace
|
|
18
|
+
{
|
|
19
|
+
public interface IEmployee { }
|
|
20
|
+
|
|
21
|
+
public class Employee : IEmployee
|
|
22
|
+
{
|
|
23
|
+
public string Name { get; set; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public class Manager
|
|
27
|
+
{
|
|
28
|
+
public Employee Employee;
|
|
29
|
+
}
|
|
30
|
+
}";
|
|
31
|
+
|
|
32
|
+
var filePath = Path.GetTempFileName() + ".cs";
|
|
33
|
+
File.WriteAllText(filePath, testCode);
|
|
34
|
+
|
|
35
|
+
try
|
|
36
|
+
{
|
|
37
|
+
var parser = new FileParser<FileStructure>
|
|
38
|
+
{
|
|
39
|
+
FilePath = filePath,
|
|
40
|
+
Name = Path.GetFileName(filePath)
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Act
|
|
44
|
+
var result = parser.Parse();
|
|
45
|
+
|
|
46
|
+
// Assert
|
|
47
|
+
Assert.NotNull(result);
|
|
48
|
+
|
|
49
|
+
var employeeClass = result.Classes.First(c => c.Name == "Employee");
|
|
50
|
+
var managerClass = result.Classes.First(c => c.Name == "Manager");
|
|
51
|
+
|
|
52
|
+
// Test Inheritance/Interface Relationship (Mermaid uses this for <|-- and <|..)
|
|
53
|
+
Assert.Single(employeeClass.Relationships);
|
|
54
|
+
var interfaceRel = employeeClass.Relationships.First();
|
|
55
|
+
Assert.Equal("Interface", interfaceRel.Type);
|
|
56
|
+
Assert.Equal("IEmployee", interfaceRel.AssociatedItem);
|
|
57
|
+
|
|
58
|
+
// Test Composition/Uses Relationship (Mermaid uses this for --> contains/uses)
|
|
59
|
+
Assert.Single(managerClass.Fields);
|
|
60
|
+
var employeeField = managerClass.Fields.First();
|
|
61
|
+
Assert.Equal("Employee", employeeField.Type);
|
|
62
|
+
Assert.Equal("Employee", employeeField.Name);
|
|
63
|
+
}
|
|
64
|
+
finally
|
|
65
|
+
{
|
|
66
|
+
File.Delete(filePath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
cod8a/enums/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from typing import List, Dict, Any, Union
|
|
4
|
+
|
|
5
|
+
from cod8a.models.models import ClassStructure, FieldStructure, FileStructure, ProjectStructure
|
|
6
|
+
|
|
7
|
+
class ClassDiagramGenerator:
|
|
8
|
+
"""
|
|
9
|
+
Generates a Mermaid class diagram from a JSON representation of code structure.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def generate(self, data: Union[FileStructure, ProjectStructure, List[FileStructure]]) -> str:
|
|
13
|
+
if not isinstance(data, FileStructure | ProjectStructure | list):
|
|
14
|
+
return "Error: Invalid data structure"
|
|
15
|
+
|
|
16
|
+
# files = self._extract_files(data)
|
|
17
|
+
classes = []
|
|
18
|
+
all_classes = self._extract_classes(data, classes)
|
|
19
|
+
mermaid_lines = ["classDiagram"]
|
|
20
|
+
# 1. Generate Class Definitions
|
|
21
|
+
for cls in all_classes:
|
|
22
|
+
mermaid_lines.extend(self._generate_class_block(cls))
|
|
23
|
+
|
|
24
|
+
# 2. Generate Relationships
|
|
25
|
+
mermaid_lines.extend(self._generate_relationships(all_classes))
|
|
26
|
+
|
|
27
|
+
return "\n".join(mermaid_lines)
|
|
28
|
+
|
|
29
|
+
def _extract_files(self, data: Union[FileStructure | ProjectStructure]) -> List[Union[FileStructure | ProjectStructure]]:
|
|
30
|
+
files : list[FileStructure] = []
|
|
31
|
+
if isinstance(data, FileStructure):
|
|
32
|
+
if "files" in data:
|
|
33
|
+
for file in data["files"]:
|
|
34
|
+
files.extend(file)
|
|
35
|
+
|
|
36
|
+
else:
|
|
37
|
+
files.extend(data)
|
|
38
|
+
|
|
39
|
+
return files
|
|
40
|
+
|
|
41
|
+
def _extract_classes(self, data: Union[FileStructure | ProjectStructure], classes: list[ClassStructure]) -> List[ClassStructure]:
|
|
42
|
+
seen_classes = set()
|
|
43
|
+
if isinstance(data, FileStructure):
|
|
44
|
+
for cls in data.classes:
|
|
45
|
+
cls_name = cls.name
|
|
46
|
+
if cls_name and cls_name not in seen_classes:
|
|
47
|
+
classes.append(cls)
|
|
48
|
+
seen_classes.add(cls_name)
|
|
49
|
+
elif isinstance(data, list):
|
|
50
|
+
for file in data:
|
|
51
|
+
self._extract_classes(file, classes)
|
|
52
|
+
elif isinstance(data, ProjectStructure):
|
|
53
|
+
for file in data.files:
|
|
54
|
+
self._extract_classes(file, classes)
|
|
55
|
+
else:
|
|
56
|
+
print("Invalid file structure")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
return classes
|
|
60
|
+
|
|
61
|
+
def _generate_class_block(self, cls: ClassStructure) -> List[str]:
|
|
62
|
+
lines = [f" class {cls.name} {{"]
|
|
63
|
+
|
|
64
|
+
# Fields
|
|
65
|
+
for field in cls.fields:
|
|
66
|
+
f_name = field.name
|
|
67
|
+
f_type = field.type
|
|
68
|
+
f_mod = field.modifier.lower()
|
|
69
|
+
|
|
70
|
+
visibility = self._get_visibility(f_mod)
|
|
71
|
+
lines.append(f" {visibility}{f_type} {f_name}")
|
|
72
|
+
|
|
73
|
+
# Methods
|
|
74
|
+
for method in cls.methods:
|
|
75
|
+
m_name = method.name
|
|
76
|
+
m_type = method.return_type
|
|
77
|
+
m_mod = method.modifier.lower()
|
|
78
|
+
|
|
79
|
+
visibility = self._get_visibility(m_mod)
|
|
80
|
+
params_list = method.parameters
|
|
81
|
+
param_str = ", ".join([f"{p.type} {p.name}" if p.type else p.name for p in params_list])
|
|
82
|
+
|
|
83
|
+
method_line = f" {visibility}{m_name}({param_str})"
|
|
84
|
+
if m_type:
|
|
85
|
+
method_line += f" {m_type}"
|
|
86
|
+
lines.append(method_line)
|
|
87
|
+
|
|
88
|
+
lines.append(" }")
|
|
89
|
+
lines.append("")
|
|
90
|
+
return lines
|
|
91
|
+
|
|
92
|
+
def _get_fields_to_show(self, cls: ClassStructure) -> List[FieldStructure]:
|
|
93
|
+
# Try to find <param name="xxx"> tags which are common in C# records
|
|
94
|
+
params = re.findall(r'<param[\s\n\r]+name\s*=\s*["\\]+([^"\\]+)["\\]+', cls.summary, re.IGNORECASE)
|
|
95
|
+
|
|
96
|
+
fields_to_show = []
|
|
97
|
+
existing_fields = {f.name: f for f in cls.fields}
|
|
98
|
+
|
|
99
|
+
if params:
|
|
100
|
+
for p_name in params:
|
|
101
|
+
if p_name in existing_fields:
|
|
102
|
+
fields_to_show.append(existing_fields[p_name])
|
|
103
|
+
else:
|
|
104
|
+
# No params in summary
|
|
105
|
+
for f in cls.fields:
|
|
106
|
+
fields_to_show.append(f)
|
|
107
|
+
return fields_to_show
|
|
108
|
+
|
|
109
|
+
def _get_visibility(self, modifier: str) -> str:
|
|
110
|
+
if "public" in modifier:
|
|
111
|
+
return "+"
|
|
112
|
+
if "private" in modifier:
|
|
113
|
+
return "-"
|
|
114
|
+
if "protected" in modifier:
|
|
115
|
+
return "#"
|
|
116
|
+
if "internal" in modifier:
|
|
117
|
+
return "~"
|
|
118
|
+
return "+" # Default to public
|
|
119
|
+
|
|
120
|
+
def _generate_relationships(self, classes: List[ClassStructure]) -> List[str]:
|
|
121
|
+
rel_lines = []
|
|
122
|
+
class_names = {c.name for c in classes}
|
|
123
|
+
|
|
124
|
+
for cls in classes:
|
|
125
|
+
cls_name = cls.name
|
|
126
|
+
# Check for inheritance/implementation
|
|
127
|
+
for relation in cls.associated_item:
|
|
128
|
+
connector = "<|--" # Default to inheritance
|
|
129
|
+
label = "inherits"
|
|
130
|
+
if "interface" in relation.type.lower() or "implements" in relation.type.lower():
|
|
131
|
+
label = "implements"
|
|
132
|
+
|
|
133
|
+
rel_lines.append(f" {relation.parent_name} {connector} {cls_name} : {label}")
|
|
134
|
+
|
|
135
|
+
# Composition/Association from Fields
|
|
136
|
+
for field in cls.fields:
|
|
137
|
+
# Extract base type from List<T> or T[]
|
|
138
|
+
match = re.search(r'<([^>]+)>', field.type)
|
|
139
|
+
base_type = match.group(1) if match else field.type
|
|
140
|
+
base_type = base_type.replace('[]', '').strip()
|
|
141
|
+
|
|
142
|
+
for name in class_names:
|
|
143
|
+
if name in base_type and cls_name != name:
|
|
144
|
+
connector = "-->"
|
|
145
|
+
label = "uses"
|
|
146
|
+
|
|
147
|
+
# Use composition for collections if it looks like it contains the class
|
|
148
|
+
if "List" in field.type or "[]" in field.type:
|
|
149
|
+
connector = "*--"
|
|
150
|
+
label = "contains"
|
|
151
|
+
|
|
152
|
+
rel_lines.append(f" {cls_name} {connector} {name} : {label}")
|
|
153
|
+
|
|
154
|
+
return sorted(list(set(rel_lines)))
|
|
155
|
+
|
|
156
|
+
def generate_class_diagram(data: FileStructure | ProjectStructure) -> str:
|
|
157
|
+
print("calling uml class generator ...")
|
|
158
|
+
generator = ClassDiagramGenerator()
|
|
159
|
+
return generator.generate(data)
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
import sys
|
|
163
|
+
if len(sys.argv) > 1:
|
|
164
|
+
with open(sys.argv[1], 'r') as f:
|
|
165
|
+
print(generate_class_diagram(f.read()))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
|
|
4
|
+
from cod8a.models.models import FileStructure, ProjectStructure
|
|
5
|
+
|
|
6
|
+
class FlowchartDiagramGenerator:
|
|
7
|
+
"""
|
|
8
|
+
Generates a readable Mermaid flowchart from code structure.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def generate(self, data: Union['FileStructure', 'ProjectStructure', List['FileStructure']], file_name: str) -> str:
|
|
12
|
+
mermaid_lines = ["graph TD"]
|
|
13
|
+
|
|
14
|
+
# Track unique IDs to avoid conflicts in Mermaid
|
|
15
|
+
self.counter = 0
|
|
16
|
+
|
|
17
|
+
# Process input
|
|
18
|
+
files =[]
|
|
19
|
+
if isinstance(data, ProjectStructure): files = data.files
|
|
20
|
+
elif isinstance(data, FileStructure): files = [data]
|
|
21
|
+
elif isinstance(data, list): files = data
|
|
22
|
+
|
|
23
|
+
for file in files:
|
|
24
|
+
file_id = f"file_{self._get_id()}"
|
|
25
|
+
mermaid_lines.append(f' {file_id}["File: {file.name}"]')
|
|
26
|
+
|
|
27
|
+
for cls in file.classes:
|
|
28
|
+
cls_id = f"cls_{self._get_id()}"
|
|
29
|
+
mermaid_lines.append(f' {file_id} --> {cls_id}["Class: {cls.name}"]')
|
|
30
|
+
|
|
31
|
+
# Exclude private Properties
|
|
32
|
+
fields = [f for f in cls.fields if f.modifier not in "private"]
|
|
33
|
+
# If too many fields, don't overwhelm the diagram truncate
|
|
34
|
+
fields = fields[:10] if len(fields) > 10 else fields
|
|
35
|
+
for field in fields:
|
|
36
|
+
f_id = f"f_{self._get_id()}"
|
|
37
|
+
mermaid_lines.append(f' {cls_id} --> {f_id}["{field.name} ({field.type})"]')
|
|
38
|
+
|
|
39
|
+
# Methods: Always helpful to see
|
|
40
|
+
for method in cls.methods:
|
|
41
|
+
m_id = f"m_{self._get_id()}"
|
|
42
|
+
mermaid_lines.append(f' {cls_id} --> {m_id}{{"{method.name}()"}}')
|
|
43
|
+
|
|
44
|
+
return "\n".join(mermaid_lines)
|
|
45
|
+
|
|
46
|
+
def _get_id(self) -> int:
|
|
47
|
+
self.counter += 1
|
|
48
|
+
return self.counter
|
|
49
|
+
|
|
50
|
+
def generate_flowchart_diagram(data, file_name) -> str:
|
|
51
|
+
return FlowchartDiagramGenerator().generate(data, file_name)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List, Union
|
|
3
|
+
|
|
4
|
+
from cod8a.models.models import ClassStructure, FileStructure, ProjectStructure
|
|
5
|
+
|
|
6
|
+
class SequenceDiagramGenerator:
|
|
7
|
+
""" Generates a generic Mermaid sequence diagram based on class structure. """
|
|
8
|
+
|
|
9
|
+
def _sanitize(self, name: str) -> str:
|
|
10
|
+
return re.sub(r'[^a-zA-Z0-9_ ]', '', name)
|
|
11
|
+
|
|
12
|
+
def generate(self, data: Union['FileStructure', 'ProjectStructure', list['FileStructure']]) -> str:
|
|
13
|
+
classes = self._extract_classes(data)
|
|
14
|
+
|
|
15
|
+
mermaid_lines = ["sequenceDiagram", " autonumber"]
|
|
16
|
+
|
|
17
|
+
aliases = {}
|
|
18
|
+
mermaid_lines.append(" participant C as User")
|
|
19
|
+
|
|
20
|
+
for i, cls in enumerate(classes):
|
|
21
|
+
alias = f"P{i}"
|
|
22
|
+
aliases[cls.name] = alias
|
|
23
|
+
mermaid_lines.append(f' participant {alias} as "{self._sanitize(cls.name)}"')
|
|
24
|
+
|
|
25
|
+
mermaid_lines.append("")
|
|
26
|
+
|
|
27
|
+
# Generic Interaction Logic
|
|
28
|
+
for cls in classes:
|
|
29
|
+
cls_alias = aliases[cls.name]
|
|
30
|
+
|
|
31
|
+
# Determine if this is a Model or a Service
|
|
32
|
+
# Models have fields, Services have methods
|
|
33
|
+
is_model = len(cls.fields) > len(cls.methods) and len(cls.fields) > 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if is_model:
|
|
37
|
+
# Exclude private properties
|
|
38
|
+
fields = [f for f in cls.fields if f.modifier != "private"]
|
|
39
|
+
mermaid_lines.append(f" C->>{cls_alias}: Create {cls.name}")
|
|
40
|
+
for field in fields[:5]: # Limit to 5 fields to avoid huge diagrams
|
|
41
|
+
if not field.name.startswith('_'):
|
|
42
|
+
mermaid_lines.append(f" {cls_alias}->>{cls_alias}: Set {self._sanitize(field.name)}")
|
|
43
|
+
else:
|
|
44
|
+
# Exclude private methods
|
|
45
|
+
methods = [m for m in cls.methods if m.modifier != "private"]
|
|
46
|
+
for method in methods:
|
|
47
|
+
if method.name.lower() in["dispose", "tostring", "equals", "gethashcode", "gettype"]:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
params = ", ".join([p.name for p in method.parameters[:2]])
|
|
51
|
+
mermaid_lines.append(f" C->>{cls_alias}: {self._sanitize(method.name)}({params})")
|
|
52
|
+
|
|
53
|
+
# If the method returns something meaningful, show the return
|
|
54
|
+
ret = self._clean_type(method.return_type)
|
|
55
|
+
if ret and ret != "void":
|
|
56
|
+
mermaid_lines.append(f" {cls_alias}-->>C: {ret}")
|
|
57
|
+
|
|
58
|
+
mermaid_lines.append("")
|
|
59
|
+
|
|
60
|
+
return "\n".join(mermaid_lines)
|
|
61
|
+
|
|
62
|
+
def _extract_classes(self, data) -> List['ClassStructure']:
|
|
63
|
+
# Generic extraction for any input type
|
|
64
|
+
if hasattr(data, 'files'): return [c for f in data.files for c in f.classes]
|
|
65
|
+
if isinstance(data, list): return [c for item in data for c in item.classes]
|
|
66
|
+
if hasattr(data, 'classes'): return data.classes
|
|
67
|
+
return[]
|
|
68
|
+
|
|
69
|
+
def _clean_type(self, type_str: str) -> str:
|
|
70
|
+
if not type_str: return ""
|
|
71
|
+
return re.split(r'[<\[]', type_str)[0].replace('?', '').split('.')[-1].strip()
|
|
72
|
+
|
|
73
|
+
def generate_sequence_diagram(data) -> str:
|
|
74
|
+
return SequenceDiagramGenerator().generate(data)
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
import sys
|
|
78
|
+
if len(sys.argv) > 1:
|
|
79
|
+
with open(sys.argv[1], 'r') as f:
|
|
80
|
+
print(generate_sequence_diagram(f.read()))
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from cod8a.parsers.dotnet_parser import DotnetParser
|
|
8
|
+
from cod8a.parsers.python_parser import PythonParser
|
|
9
|
+
from cod8a.models.models import FileStructure, ProjectStructure
|
|
10
|
+
|
|
11
|
+
# Path to the C# analyzer project
|
|
12
|
+
DOTNET_ANALYZER_PATH = os.path.join(os.path.dirname(__file__), "..", "dotnet", "CodeAnalysis", "CodeAnalyzer.csproj")
|
|
13
|
+
|
|
14
|
+
def _get_parser(path: str):
|
|
15
|
+
print(f"Checking analyzer ...")
|
|
16
|
+
isDotnetParser = any(path.endswith(ext) for ext in [".cs", ".csproj", ".sln"]) or os.path.isdir(path) and any(f.endswith(".cs") for _, _, files in os.walk(path) for f in files)
|
|
17
|
+
if isDotnetParser:
|
|
18
|
+
return DotnetParser(DOTNET_ANALYZER_PATH)
|
|
19
|
+
return PythonParser()
|
|
20
|
+
|
|
21
|
+
# Extracts code structure from the syntax tree
|
|
22
|
+
def extract_structure(path) -> Union[FileStructure | ProjectStructure | list[FileStructure]]:
|
|
23
|
+
target = path or os.getcwd()
|
|
24
|
+
parser = _get_parser(target)
|
|
25
|
+
|
|
26
|
+
pathExists = os.path.exists(path)
|
|
27
|
+
if not pathExists:
|
|
28
|
+
print("Not found")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
struct = parser.parse(path or os.getcwd())
|
|
32
|
+
|
|
33
|
+
return struct
|
|
34
|
+
|
|
35
|
+
# Save the generated diagram to the directory provided in '-o' command
|
|
36
|
+
# or root directory, if no output directory is provided (only on users authorization)
|
|
37
|
+
def save_diagram(struct, canon_type, content, output=None, path=None):
|
|
38
|
+
"""Helper to save mermaid diagram."""
|
|
39
|
+
try:
|
|
40
|
+
file_path = None
|
|
41
|
+
|
|
42
|
+
# Prepare filename (PascalCase)
|
|
43
|
+
raw_name = getattr(struct, "name", "diagram")
|
|
44
|
+
base_name = os.path.splitext(raw_name)[0]
|
|
45
|
+
# Split by non-alphanumeric and capitalize first letter of each part, preserving internal caps
|
|
46
|
+
pascal_name = "".join(word[0].upper() + word[1:] for word in re.split(r'[^a-zA-Z0-9]', base_name) if word)
|
|
47
|
+
|
|
48
|
+
if not pascal_name:
|
|
49
|
+
pascal_name = "GeneratedDiagram"
|
|
50
|
+
|
|
51
|
+
# Append diagram type suffix
|
|
52
|
+
suffix_map = {"class": "Class", "flowchart": "Flowchart", "sequence": "Sequence"}
|
|
53
|
+
pascal_name += suffix_map.get(canon_type, "")
|
|
54
|
+
|
|
55
|
+
if output:
|
|
56
|
+
# If output is a directory, use it as base
|
|
57
|
+
if os.path.isdir(output):
|
|
58
|
+
file_path = os.path.join(output, f"{pascal_name}.mmd")
|
|
59
|
+
else:
|
|
60
|
+
# If output is a file path, use it, ensuring .mmd extension
|
|
61
|
+
file_path = output
|
|
62
|
+
if not file_path.endswith(".mmd"):
|
|
63
|
+
file_path += ".mmd"
|
|
64
|
+
else:
|
|
65
|
+
# If no output provided, prompt the user
|
|
66
|
+
if click.confirm("\nDo you want to save the diagram to a file?", default=True):
|
|
67
|
+
# Save in the user's Downloads directory
|
|
68
|
+
save_dir = os.path.join(os.path.expanduser("~"), "Downloads")
|
|
69
|
+
file_path = os.path.join(save_dir, f"{pascal_name}.mmd")
|
|
70
|
+
|
|
71
|
+
if file_path:
|
|
72
|
+
# Ensure the directory exists
|
|
73
|
+
os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True)
|
|
74
|
+
with open(file_path, "w") as f:
|
|
75
|
+
f.write(content)
|
|
76
|
+
click.echo(f"Diagram exported to: {file_path}")
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
click.echo(f"Warning: Could not export diagram: {e}")
|
cod8a/models/__init__.py
ADDED
|
File without changes
|