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.
@@ -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
+
File without changes
@@ -0,0 +1,10 @@
1
+ from enum import Enum
2
+
3
+ class DiagramType(Enum):
4
+ CLASS = "class"
5
+ SEQUENCE = "sequence"
6
+ FLOWCHART = "flowchart"
7
+ USE_CASE = "use_case"
8
+ ACTIVITY = "activity"
9
+ COMPONENT = "component"
10
+ DEPLOYMENT = "deployment"
@@ -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}")
File without changes