mvn-tree-visualizer 1.3.0__py3-none-any.whl → 1.4.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.
Potentially problematic release.
This version of mvn-tree-visualizer might be problematic. Click here for more details.
- mvn_tree_visualizer/cli.py +13 -3
- mvn_tree_visualizer/enhanced_template.py +218 -0
- mvn_tree_visualizer/outputs/html_output.py +81 -14
- mvn_tree_visualizer/themes.py +189 -0
- {mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/METADATA +21 -2
- {mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/RECORD +9 -8
- mvn_tree_visualizer/TEMPLATE.py +0 -61
- {mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/WHEEL +0 -0
- {mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/entry_points.txt +0 -0
- {mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/licenses/LICENSE +0 -0
mvn_tree_visualizer/cli.py
CHANGED
|
@@ -19,6 +19,7 @@ def generate_diagram(
|
|
|
19
19
|
keep_tree: bool,
|
|
20
20
|
output_format: str,
|
|
21
21
|
show_versions: bool,
|
|
22
|
+
theme: str = "minimal",
|
|
22
23
|
) -> None:
|
|
23
24
|
"""Generate the dependency diagram with comprehensive error handling."""
|
|
24
25
|
timestamp = time.strftime("%H:%M:%S")
|
|
@@ -86,7 +87,7 @@ def generate_diagram(
|
|
|
86
87
|
# Generate output
|
|
87
88
|
try:
|
|
88
89
|
if output_format == "html":
|
|
89
|
-
create_html_diagram(dependency_tree, output_file, show_versions)
|
|
90
|
+
create_html_diagram(dependency_tree, output_file, show_versions, theme)
|
|
90
91
|
elif output_format == "json":
|
|
91
92
|
create_json_output(dependency_tree, output_file, show_versions)
|
|
92
93
|
else:
|
|
@@ -179,6 +180,14 @@ def cli() -> NoReturn:
|
|
|
179
180
|
help="Watch for changes in Maven dependency files and automatically regenerate the diagram.",
|
|
180
181
|
)
|
|
181
182
|
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"--theme",
|
|
185
|
+
type=str,
|
|
186
|
+
default="minimal",
|
|
187
|
+
choices=["minimal", "dark"],
|
|
188
|
+
help="Theme for the diagram visualization. Default is 'minimal'.",
|
|
189
|
+
)
|
|
190
|
+
|
|
182
191
|
args = parser.parse_args()
|
|
183
192
|
directory: str = args.directory
|
|
184
193
|
output_file: str = args.output
|
|
@@ -187,10 +196,11 @@ def cli() -> NoReturn:
|
|
|
187
196
|
output_format: str = args.format
|
|
188
197
|
show_versions: bool = args.show_versions
|
|
189
198
|
watch_mode: bool = args.watch
|
|
199
|
+
theme: str = args.theme
|
|
190
200
|
|
|
191
201
|
# Generate initial diagram
|
|
192
202
|
print("Generating initial diagram...")
|
|
193
|
-
generate_diagram(directory, output_file, filename, keep_tree, output_format, show_versions)
|
|
203
|
+
generate_diagram(directory, output_file, filename, keep_tree, output_format, show_versions, theme)
|
|
194
204
|
|
|
195
205
|
if not watch_mode:
|
|
196
206
|
print("You can open it in your browser to view the dependency tree.")
|
|
@@ -200,7 +210,7 @@ def cli() -> NoReturn:
|
|
|
200
210
|
# Watch mode
|
|
201
211
|
def regenerate_callback():
|
|
202
212
|
"""Callback function for file watcher."""
|
|
203
|
-
generate_diagram(directory, output_file, filename, keep_tree, output_format, show_versions)
|
|
213
|
+
generate_diagram(directory, output_file, filename, keep_tree, output_format, show_versions, theme)
|
|
204
214
|
|
|
205
215
|
watcher = FileWatcher(directory, filename, regenerate_callback)
|
|
206
216
|
watcher.start()
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Enhanced HTML templates with the interactive features."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from .themes import STANDARD_COLORS, Theme
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_html_template(theme: Theme) -> str:
|
|
9
|
+
"""Generate HTML template with theme-specific styling and interactive features."""
|
|
10
|
+
|
|
11
|
+
# Build Mermaid configuration
|
|
12
|
+
mermaid_config = {
|
|
13
|
+
"startOnLoad": True,
|
|
14
|
+
"sequence": {"useMaxWidth": False},
|
|
15
|
+
"theme": theme.mermaid_theme,
|
|
16
|
+
**theme.mermaid_config,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Convert config to JavaScript object
|
|
20
|
+
mermaid_config_js = _dict_to_js_object(mermaid_config)
|
|
21
|
+
|
|
22
|
+
return f"""<!DOCTYPE html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="UTF-8">
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
27
|
+
<title>Maven Dependency Diagram - {theme.name.title()} Theme</title>
|
|
28
|
+
<style>
|
|
29
|
+
#mySvgId {{
|
|
30
|
+
height: 100%;
|
|
31
|
+
width: 100%;
|
|
32
|
+
}}
|
|
33
|
+
|
|
34
|
+
/* Theme-specific styles */
|
|
35
|
+
{theme.custom_css}
|
|
36
|
+
|
|
37
|
+
/* Dark theme text visibility fixes */
|
|
38
|
+
{
|
|
39
|
+
""
|
|
40
|
+
if theme.name != "dark"
|
|
41
|
+
else '''
|
|
42
|
+
/* Force white text for all mermaid elements in dark theme */
|
|
43
|
+
.node text, .edgeLabel text, text, .label text {
|
|
44
|
+
fill: #ffffff !important;
|
|
45
|
+
color: #ffffff !important;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Ensure node backgrounds are visible */
|
|
49
|
+
.node rect, .node circle, .node ellipse, .node polygon {
|
|
50
|
+
fill: #4a5568 !important;
|
|
51
|
+
stroke: #e2e8f0 !important;
|
|
52
|
+
stroke-width: 1px !important;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Edge styling for dark theme */
|
|
56
|
+
.edge path, .flowchart-link {
|
|
57
|
+
stroke: #a0aec0 !important;
|
|
58
|
+
stroke-width: 2px !important;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Arrow styling */
|
|
62
|
+
.arrowheadPath {
|
|
63
|
+
fill: #a0aec0 !important;
|
|
64
|
+
stroke: #a0aec0 !important;
|
|
65
|
+
}
|
|
66
|
+
'''
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Improved node styling */
|
|
70
|
+
.node {{
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
transition: opacity 0.2s ease;
|
|
73
|
+
}}
|
|
74
|
+
|
|
75
|
+
.node:hover {{
|
|
76
|
+
opacity: 0.8;
|
|
77
|
+
}}
|
|
78
|
+
|
|
79
|
+
/* Highlighting styles */
|
|
80
|
+
.highlighted {{
|
|
81
|
+
opacity: 1 !important;
|
|
82
|
+
filter: drop-shadow(0 0 8px {STANDARD_COLORS["root_node"]});
|
|
83
|
+
}}
|
|
84
|
+
|
|
85
|
+
.dimmed {{
|
|
86
|
+
opacity: 0.3;
|
|
87
|
+
}}
|
|
88
|
+
</style>
|
|
89
|
+
</head>
|
|
90
|
+
<body>
|
|
91
|
+
<div class="controls">
|
|
92
|
+
<div class="control-group">
|
|
93
|
+
<button id="downloadButton" class="toggle-btn">Download SVG</button>
|
|
94
|
+
<!-- Note: PNG download feature to be implemented in future version -->
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div id="graphDiv"></div>
|
|
99
|
+
|
|
100
|
+
<script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.5.0/dist/svg-pan-zoom.min.js"></script>
|
|
101
|
+
<script type="module">
|
|
102
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.9.0/dist/mermaid.esm.min.mjs';
|
|
103
|
+
|
|
104
|
+
// Initialize mermaid with theme configuration
|
|
105
|
+
mermaid.initialize({mermaid_config_js});
|
|
106
|
+
|
|
107
|
+
// Global variables
|
|
108
|
+
let panZoomInstance = null;
|
|
109
|
+
|
|
110
|
+
const drawDiagram = async function () {{
|
|
111
|
+
const element = document.querySelector('#graphDiv');
|
|
112
|
+
const graphDefinition = `{{{{diagram_definition}}}}`;
|
|
113
|
+
|
|
114
|
+
try {{
|
|
115
|
+
const {{ svg }} = await mermaid.render('mySvgId', graphDefinition);
|
|
116
|
+
element.innerHTML = svg.replace(/[ ]*max-width:[ 0-9\\.]*px;/i , '');
|
|
117
|
+
|
|
118
|
+
// Initialize pan & zoom
|
|
119
|
+
panZoomInstance = svgPanZoom('#mySvgId', {{
|
|
120
|
+
zoomEnabled: true,
|
|
121
|
+
controlIconsEnabled: true,
|
|
122
|
+
fit: true,
|
|
123
|
+
center: true,
|
|
124
|
+
minZoom: 0.1,
|
|
125
|
+
maxZoom: 10
|
|
126
|
+
}});
|
|
127
|
+
|
|
128
|
+
// Setup node interactions
|
|
129
|
+
setupNodeInteractions();
|
|
130
|
+
|
|
131
|
+
}} catch (error) {{
|
|
132
|
+
console.error('Error rendering diagram:', error);
|
|
133
|
+
element.innerHTML = `<p style="color: red; padding: 20px;">Error rendering diagram: ${{error.message}}</p>`;
|
|
134
|
+
}}
|
|
135
|
+
}};
|
|
136
|
+
|
|
137
|
+
const setupNodeInteractions = function() {{
|
|
138
|
+
const nodes = document.querySelectorAll('#mySvgId .node');
|
|
139
|
+
|
|
140
|
+
nodes.forEach(node => {{
|
|
141
|
+
node.style.cursor = 'pointer';
|
|
142
|
+
}});
|
|
143
|
+
}};
|
|
144
|
+
|
|
145
|
+
// Download functionality
|
|
146
|
+
document.getElementById('downloadButton').addEventListener('click', function() {{
|
|
147
|
+
downloadSVG();
|
|
148
|
+
}});
|
|
149
|
+
|
|
150
|
+
const downloadSVG = function() {{
|
|
151
|
+
const svg = document.querySelector('#mySvgId');
|
|
152
|
+
let svgData = new XMLSerializer().serializeToString(svg);
|
|
153
|
+
|
|
154
|
+
// Clean up pan & zoom controls
|
|
155
|
+
svgData = svgData.replace(/<g\\b[^>]*\\bclass="svg-pan-zoom-.*?".*?>.*?<\\/g>/g, '');
|
|
156
|
+
svgData = svgData.replace(/<\\/g><\\/svg>/, '</svg>');
|
|
157
|
+
|
|
158
|
+
const svgBlob = new Blob([svgData], {{type: 'image/svg+xml;charset=utf-8'}});
|
|
159
|
+
const svgUrl = URL.createObjectURL(svgBlob);
|
|
160
|
+
const downloadLink = document.createElement('a');
|
|
161
|
+
downloadLink.href = svgUrl;
|
|
162
|
+
downloadLink.download = 'dependency-diagram.svg';
|
|
163
|
+
document.body.appendChild(downloadLink);
|
|
164
|
+
downloadLink.click();
|
|
165
|
+
document.body.removeChild(downloadLink);
|
|
166
|
+
URL.revokeObjectURL(svgUrl);
|
|
167
|
+
}};
|
|
168
|
+
|
|
169
|
+
// Initialize the diagram
|
|
170
|
+
await drawDiagram();
|
|
171
|
+
|
|
172
|
+
// Keyboard shortcuts
|
|
173
|
+
document.addEventListener('keydown', (e) => {{
|
|
174
|
+
if (e.ctrlKey || e.metaKey) {{
|
|
175
|
+
switch(e.key) {{
|
|
176
|
+
case 's':
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
downloadSVG();
|
|
179
|
+
break;
|
|
180
|
+
case 'r':
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
if (panZoomInstance) {{
|
|
183
|
+
panZoomInstance.reset();
|
|
184
|
+
}}
|
|
185
|
+
break;
|
|
186
|
+
}}
|
|
187
|
+
}}
|
|
188
|
+
}});
|
|
189
|
+
|
|
190
|
+
</script>
|
|
191
|
+
</body>
|
|
192
|
+
</html>"""
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _dict_to_js_object(d: Dict[str, Any], indent: int = 0) -> str:
|
|
196
|
+
"""Convert Python dict to JavaScript object string."""
|
|
197
|
+
if not isinstance(d, dict):
|
|
198
|
+
if isinstance(d, str):
|
|
199
|
+
return f'"{d}"'
|
|
200
|
+
elif isinstance(d, bool):
|
|
201
|
+
return str(d).lower()
|
|
202
|
+
else:
|
|
203
|
+
return str(d)
|
|
204
|
+
|
|
205
|
+
items = []
|
|
206
|
+
for key, value in d.items():
|
|
207
|
+
if isinstance(value, dict):
|
|
208
|
+
value_str = _dict_to_js_object(value, indent + 1)
|
|
209
|
+
elif isinstance(value, str):
|
|
210
|
+
value_str = f'"{value}"'
|
|
211
|
+
elif isinstance(value, bool):
|
|
212
|
+
value_str = str(value).lower()
|
|
213
|
+
else:
|
|
214
|
+
value_str = str(value)
|
|
215
|
+
|
|
216
|
+
items.append(f'"{key}": {value_str}')
|
|
217
|
+
|
|
218
|
+
return "{" + ", ".join(items) + "}"
|
|
@@ -3,25 +3,40 @@ from typing import List, Set, Tuple
|
|
|
3
3
|
|
|
4
4
|
from jinja2 import BaseLoader, Environment
|
|
5
5
|
|
|
6
|
-
from ..
|
|
6
|
+
from ..enhanced_template import get_html_template
|
|
7
|
+
from ..themes import STANDARD_COLORS, get_theme
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def create_html_diagram(dependency_tree: str, output_filename: str, show_versions: bool = False) -> None:
|
|
10
|
+
def create_html_diagram(dependency_tree: str, output_filename: str, show_versions: bool = False, theme: str = "minimal") -> None:
|
|
11
|
+
"""Create HTML diagram with theme support."""
|
|
12
|
+
theme_obj = get_theme(theme)
|
|
10
13
|
mermaid_diagram: str = _convert_to_mermaid(dependency_tree, show_versions)
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
# Use enhanced template with theme
|
|
16
|
+
html_template = get_html_template(theme_obj)
|
|
17
|
+
template = Environment(loader=BaseLoader).from_string(html_template)
|
|
12
18
|
rendered: str = template.render(diagram_definition=mermaid_diagram)
|
|
19
|
+
|
|
13
20
|
parent_dir: Path = Path(output_filename).parent
|
|
14
21
|
if not parent_dir.exists():
|
|
15
22
|
parent_dir.mkdir(parents=True, exist_ok=True)
|
|
16
|
-
with open(output_filename, "w") as f:
|
|
23
|
+
with open(output_filename, "w", encoding="utf-8") as f:
|
|
17
24
|
f.write(rendered)
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
def _convert_to_mermaid(dependency_tree: str, show_versions: bool = False) -> str:
|
|
21
|
-
|
|
28
|
+
"""Convert dependency tree to enhanced Mermaid diagram with styling."""
|
|
22
29
|
lines: List[str] = dependency_tree.strip().split("\n")
|
|
23
|
-
|
|
30
|
+
relationships: Set[str] = set()
|
|
31
|
+
node_styles: Set[str] = set()
|
|
24
32
|
previous_dependency: List[Tuple[str, int]] = []
|
|
33
|
+
|
|
34
|
+
# Track root and leaf nodes for styling, and store all node declarations
|
|
35
|
+
all_nodes: Set[str] = set()
|
|
36
|
+
parent_nodes: Set[str] = set()
|
|
37
|
+
child_nodes: Set[str] = set()
|
|
38
|
+
node_declarations: Set[str] = set()
|
|
39
|
+
|
|
25
40
|
for line in lines:
|
|
26
41
|
if not line:
|
|
27
42
|
continue
|
|
@@ -30,17 +45,21 @@ def _convert_to_mermaid(dependency_tree: str, show_versions: bool = False) -> st
|
|
|
30
45
|
parts: List[str] = line.split(":")
|
|
31
46
|
if len(parts) < 3:
|
|
32
47
|
continue
|
|
48
|
+
|
|
33
49
|
if len(parts) == 4:
|
|
34
50
|
group_id, artifact_id, app, version = parts
|
|
35
51
|
if show_versions:
|
|
36
52
|
node_label: str = f"{artifact_id}:{version}"
|
|
37
|
-
|
|
53
|
+
safe_node_id: str = _sanitize_node_id(f"{artifact_id}_{version}")
|
|
38
54
|
else:
|
|
39
55
|
node_label: str = artifact_id
|
|
40
|
-
|
|
56
|
+
safe_node_id: str = _sanitize_node_id(artifact_id)
|
|
57
|
+
|
|
58
|
+
node_declarations.add(f'\t{safe_node_id}["{node_label}"];')
|
|
59
|
+
all_nodes.add(safe_node_id)
|
|
41
60
|
if previous_dependency: # Re initialize the list if it wasn't empty
|
|
42
61
|
previous_dependency = []
|
|
43
|
-
previous_dependency.append((
|
|
62
|
+
previous_dependency.append((safe_node_id, 0)) # The second element is the depth
|
|
44
63
|
else:
|
|
45
64
|
depth: int = len(parts[0].split(" ")) - 1
|
|
46
65
|
if len(parts) == 6:
|
|
@@ -50,16 +69,64 @@ def _convert_to_mermaid(dependency_tree: str, show_versions: bool = False) -> st
|
|
|
50
69
|
|
|
51
70
|
if show_versions:
|
|
52
71
|
node_label: str = f"{artifact_id}:{version}"
|
|
72
|
+
safe_node_id: str = _sanitize_node_id(f"{artifact_id}_{version}")
|
|
53
73
|
else:
|
|
54
74
|
node_label: str = artifact_id
|
|
75
|
+
safe_node_id: str = _sanitize_node_id(artifact_id)
|
|
76
|
+
|
|
77
|
+
node_declarations.add(f'\t{safe_node_id}["{node_label}"];')
|
|
78
|
+
all_nodes.add(safe_node_id)
|
|
79
|
+
child_nodes.add(safe_node_id)
|
|
55
80
|
|
|
56
81
|
if previous_dependency[-1][1] < depth:
|
|
57
|
-
|
|
58
|
-
|
|
82
|
+
parent_id = previous_dependency[-1][0]
|
|
83
|
+
parent_nodes.add(parent_id)
|
|
84
|
+
relationships.add(f"\t{parent_id} --> {safe_node_id};")
|
|
85
|
+
previous_dependency.append((safe_node_id, depth))
|
|
59
86
|
else:
|
|
60
87
|
# remove all dependencies that are deeper or equal to the current depth
|
|
61
88
|
while previous_dependency and previous_dependency[-1][1] >= depth:
|
|
62
89
|
previous_dependency.pop()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
90
|
+
parent_id = previous_dependency[-1][0]
|
|
91
|
+
parent_nodes.add(parent_id)
|
|
92
|
+
relationships.add(f"\t{parent_id} --> {safe_node_id};")
|
|
93
|
+
previous_dependency.append((safe_node_id, depth))
|
|
94
|
+
|
|
95
|
+
# Add styling classes
|
|
96
|
+
root_nodes = all_nodes - child_nodes
|
|
97
|
+
leaf_nodes = all_nodes - parent_nodes
|
|
98
|
+
|
|
99
|
+
# Add node styling
|
|
100
|
+
for node in root_nodes:
|
|
101
|
+
node_styles.add(f"\tclass {node} rootNode;")
|
|
102
|
+
for node in leaf_nodes:
|
|
103
|
+
node_styles.add(f"\tclass {node} leafNode;")
|
|
104
|
+
for node in parent_nodes.intersection(child_nodes):
|
|
105
|
+
node_styles.add(f"\tclass {node} intermediateNode;")
|
|
106
|
+
|
|
107
|
+
# Build the complete diagram with standardized colors
|
|
108
|
+
diagram_parts = [
|
|
109
|
+
"graph LR",
|
|
110
|
+
*sorted(node_declarations),
|
|
111
|
+
*sorted(relationships),
|
|
112
|
+
"",
|
|
113
|
+
f"classDef rootNode fill:{STANDARD_COLORS['root_node']}20,stroke:{STANDARD_COLORS['root_node']},stroke-width:3px,color:#000;",
|
|
114
|
+
f"classDef leafNode fill:{STANDARD_COLORS['leaf_node']}20,stroke:{STANDARD_COLORS['leaf_node']},stroke-width:2px,color:#000;",
|
|
115
|
+
f"classDef intermediateNode fill:{STANDARD_COLORS['intermediate_node']}20,stroke:{STANDARD_COLORS['intermediate_node']},stroke-width:2px,color:#000;",
|
|
116
|
+
"",
|
|
117
|
+
*sorted(node_styles),
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
return "\n".join(diagram_parts)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _sanitize_node_id(node_id: str) -> str:
|
|
124
|
+
"""Sanitize node ID for Mermaid compatibility."""
|
|
125
|
+
# Replace special characters that could break Mermaid syntax
|
|
126
|
+
import re
|
|
127
|
+
|
|
128
|
+
sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", node_id)
|
|
129
|
+
# Ensure it starts with a letter or underscore
|
|
130
|
+
if sanitized and not sanitized[0].isalpha() and sanitized[0] != "_":
|
|
131
|
+
sanitized = "_" + sanitized
|
|
132
|
+
return sanitized or "node"
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Theme configurations for mvn-tree-visualizer diagrams."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Theme:
|
|
7
|
+
"""Base theme configuration class."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
name: str,
|
|
12
|
+
mermaid_theme: str = "default",
|
|
13
|
+
background_color: str = "#ffffff",
|
|
14
|
+
custom_css: str = "",
|
|
15
|
+
mermaid_config: Dict[str, Any] = None,
|
|
16
|
+
):
|
|
17
|
+
self.name = name
|
|
18
|
+
self.mermaid_theme = mermaid_theme
|
|
19
|
+
self.background_color = background_color
|
|
20
|
+
self.custom_css = custom_css
|
|
21
|
+
self.mermaid_config = mermaid_config or {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Standard color scheme for consistency across themes
|
|
25
|
+
# Root nodes: Blue shades, Intermediate nodes: Orange shades, Leaf nodes: Green shades
|
|
26
|
+
STANDARD_COLORS = {
|
|
27
|
+
"root_node": "#3B82F6", # Blue for root nodes
|
|
28
|
+
"intermediate_node": "#F59E0B", # Orange for intermediate nodes
|
|
29
|
+
"leaf_node": "#10B981", # Green for leaf nodes
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Predefined themes
|
|
33
|
+
THEMES = {
|
|
34
|
+
"minimal": Theme(
|
|
35
|
+
name="minimal",
|
|
36
|
+
mermaid_theme="neutral",
|
|
37
|
+
background_color="#ffffff",
|
|
38
|
+
custom_css="""
|
|
39
|
+
body {
|
|
40
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
41
|
+
margin: 0;
|
|
42
|
+
padding: 20px;
|
|
43
|
+
background-color: #ffffff;
|
|
44
|
+
color: #000000;
|
|
45
|
+
line-height: 1.6;
|
|
46
|
+
height: 100vh;
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
}
|
|
49
|
+
#graphDiv {
|
|
50
|
+
background-color: #ffffff;
|
|
51
|
+
border: 1px solid #000000;
|
|
52
|
+
padding: 20px;
|
|
53
|
+
margin-bottom: 20px;
|
|
54
|
+
height: calc(100vh - 120px);
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
.controls {
|
|
58
|
+
margin-bottom: 20px;
|
|
59
|
+
display: flex;
|
|
60
|
+
gap: 10px;
|
|
61
|
+
flex-wrap: wrap;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
.control-group {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: 8px;
|
|
68
|
+
}
|
|
69
|
+
.control-label {
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
font-weight: normal;
|
|
72
|
+
color: #000000;
|
|
73
|
+
text-transform: uppercase;
|
|
74
|
+
}
|
|
75
|
+
.toggle-btn {
|
|
76
|
+
background-color: #000000;
|
|
77
|
+
color: white;
|
|
78
|
+
border: none;
|
|
79
|
+
padding: 8px 16px;
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
font-family: inherit;
|
|
82
|
+
font-size: 12px;
|
|
83
|
+
text-transform: uppercase;
|
|
84
|
+
letter-spacing: 1px;
|
|
85
|
+
transition: opacity 0.2s;
|
|
86
|
+
}
|
|
87
|
+
.toggle-btn:hover {
|
|
88
|
+
opacity: 0.8;
|
|
89
|
+
}
|
|
90
|
+
""",
|
|
91
|
+
mermaid_config={
|
|
92
|
+
"theme": "neutral",
|
|
93
|
+
"themeVariables": {
|
|
94
|
+
"primaryColor": STANDARD_COLORS["root_node"],
|
|
95
|
+
"primaryTextColor": "#000000",
|
|
96
|
+
"primaryBorderColor": "#000000",
|
|
97
|
+
"lineColor": "#000000",
|
|
98
|
+
"secondaryColor": "#f5f5f5",
|
|
99
|
+
"tertiaryColor": "#ffffff",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
"dark": Theme(
|
|
104
|
+
name="dark",
|
|
105
|
+
mermaid_theme="forest",
|
|
106
|
+
background_color="#2d3748",
|
|
107
|
+
custom_css="""
|
|
108
|
+
body {
|
|
109
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
110
|
+
margin: 0;
|
|
111
|
+
padding: 20px;
|
|
112
|
+
background-color: #2d3748;
|
|
113
|
+
color: #f7fafc;
|
|
114
|
+
line-height: 1.6;
|
|
115
|
+
height: 100vh;
|
|
116
|
+
box-sizing: border-box;
|
|
117
|
+
}
|
|
118
|
+
#graphDiv {
|
|
119
|
+
background-color: #2d3748;
|
|
120
|
+
border: 1px solid #e2e8f0;
|
|
121
|
+
padding: 20px;
|
|
122
|
+
margin-bottom: 20px;
|
|
123
|
+
height: calc(100vh - 120px);
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
}
|
|
126
|
+
.controls {
|
|
127
|
+
margin-bottom: 20px;
|
|
128
|
+
display: flex;
|
|
129
|
+
gap: 10px;
|
|
130
|
+
flex-wrap: wrap;
|
|
131
|
+
align-items: center;
|
|
132
|
+
}
|
|
133
|
+
.control-group {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: 8px;
|
|
137
|
+
}
|
|
138
|
+
.control-label {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
font-weight: normal;
|
|
141
|
+
color: #f7fafc;
|
|
142
|
+
text-transform: uppercase;
|
|
143
|
+
}
|
|
144
|
+
.toggle-btn {
|
|
145
|
+
background-color: #f7fafc;
|
|
146
|
+
color: #2d3748;
|
|
147
|
+
border: none;
|
|
148
|
+
padding: 8px 16px;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
font-family: inherit;
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
text-transform: uppercase;
|
|
153
|
+
letter-spacing: 1px;
|
|
154
|
+
transition: opacity 0.2s;
|
|
155
|
+
}
|
|
156
|
+
.toggle-btn:hover {
|
|
157
|
+
opacity: 0.8;
|
|
158
|
+
}
|
|
159
|
+
""",
|
|
160
|
+
mermaid_config={
|
|
161
|
+
"theme": "forest",
|
|
162
|
+
"themeVariables": {
|
|
163
|
+
"primaryColor": STANDARD_COLORS["root_node"],
|
|
164
|
+
"primaryTextColor": "#ffffff",
|
|
165
|
+
"primaryBorderColor": "#e2e8f0",
|
|
166
|
+
"lineColor": "#a0aec0",
|
|
167
|
+
"secondaryColor": "#4a5568",
|
|
168
|
+
"tertiaryColor": "#2d3748",
|
|
169
|
+
"background": "#2d3748",
|
|
170
|
+
"mainBkg": "#4a5568",
|
|
171
|
+
"nodeBkg": "#4a5568",
|
|
172
|
+
"clusterBkg": "#2d3748",
|
|
173
|
+
"edgeLabelBackground": "#2d3748",
|
|
174
|
+
"nodeTextColor": "#ffffff",
|
|
175
|
+
"textColor": "#ffffff",
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def get_theme(theme_name: str) -> Theme:
|
|
183
|
+
"""Get a theme by name, fallback to minimal if not found."""
|
|
184
|
+
return THEMES.get(theme_name, THEMES["minimal"])
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_available_themes() -> list[str]:
|
|
188
|
+
"""Get list of available theme names."""
|
|
189
|
+
return list(THEMES.keys())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mvn-tree-visualizer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: A simple command line tool to visualize the dependency tree of a Maven project in a graphical format.
|
|
5
5
|
Project-URL: source, https://github.com/dyka3773/mvn-tree-visualizer
|
|
6
6
|
Author-email: Iraklis Konsoulas <dyka3773@gmail.com>
|
|
@@ -55,12 +55,14 @@ pip install mvn-tree-visualizer
|
|
|
55
55
|
- **🌐 Multiple Output Formats:**
|
|
56
56
|
- **HTML:** Generates an interactive HTML diagram of your dependency tree using Mermaid.js.
|
|
57
57
|
- **JSON:** Creates a structured JSON representation of the dependency tree, perfect for scripting or integration with other tools.
|
|
58
|
+
- **🎨 Theme System:** Choose from 2 built-in themes (minimal, dark) for clean and consistent diagram styling.
|
|
58
59
|
- **🔄 Watch Mode:** Automatically regenerates diagrams when Maven dependency files change using the `--watch` flag.
|
|
59
60
|
- **📋 Version Display:** Show or hide dependency versions in both HTML and JSON outputs using the `--show-versions` flag.
|
|
60
61
|
- **⚡ Easy to Use:** A simple command-line interface that gets the job done with minimal configuration.
|
|
61
62
|
- **📂 File Merging:** Automatically finds and merges multiple `maven_dependency_file` files from different subdirectories.
|
|
62
63
|
- **🎨 Customizable Output:** Specify the output file name and location.
|
|
63
|
-
- **💾
|
|
64
|
+
- **💾 Enhanced Downloads:** Download diagrams as SVG or high-resolution PNG directly from the HTML page.
|
|
65
|
+
- **🖱️ Interactive Features:** Hover tooltips, click-to-highlight connections, pan/zoom controls, and keyboard shortcuts.
|
|
64
66
|
|
|
65
67
|
## How to Use
|
|
66
68
|
|
|
@@ -93,6 +95,15 @@ mvn_tree_visualizer --filename "maven_dependency_file" --output "dependencies.js
|
|
|
93
95
|
mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --show-versions
|
|
94
96
|
```
|
|
95
97
|
|
|
98
|
+
#### With Custom Themes
|
|
99
|
+
```bash
|
|
100
|
+
# Dark theme for low-light environments
|
|
101
|
+
mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --theme dark
|
|
102
|
+
|
|
103
|
+
# Default minimal theme (clean monospace design)
|
|
104
|
+
mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html"
|
|
105
|
+
```
|
|
106
|
+
|
|
96
107
|
#### JSON Output with Versions
|
|
97
108
|
```bash
|
|
98
109
|
mvn_tree_visualizer --filename "maven_dependency_file" --output "dependencies.json" --format json --show-versions
|
|
@@ -129,12 +140,20 @@ Each example includes:
|
|
|
129
140
|
| `--filename` | The name of the file containing the Maven dependency tree | `maven_dependency_file` |
|
|
130
141
|
| `--output` | The name of the output file | `diagram.html` |
|
|
131
142
|
| `--format` | The output format (`html` or `json`) | `html` |
|
|
143
|
+
| `--theme` | Theme for HTML diagrams (`minimal`, `dark`) | `minimal` |
|
|
132
144
|
| `--show-versions` | Show dependency versions in the diagram | `False` |
|
|
133
145
|
| `--watch` | Watch for file changes and auto-regenerate diagram | `False` |
|
|
134
146
|
| `--directory` | The directory to scan for the Maven dependency file(s) | current directory |
|
|
135
147
|
| `--keep-tree` | Keep the intermediate `dependency_tree.txt` file | `False` |
|
|
136
148
|
| `--help` | Show the help message and exit | - |
|
|
137
149
|
|
|
150
|
+
### Theme Options
|
|
151
|
+
|
|
152
|
+
- **`minimal`**: Clean monospace design with simple black borders (default)
|
|
153
|
+
- **`dark`**: Same minimal styling but with white text on black background
|
|
154
|
+
|
|
155
|
+
📖 **See the complete [Theme Documentation](docs/THEMES.md) for detailed information about themes and interactive features.**
|
|
156
|
+
|
|
138
157
|
## Performance
|
|
139
158
|
|
|
140
159
|
**For Large Projects:**
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
mvn_tree_visualizer/TEMPLATE.py,sha256=WIQfSNBygUZVkBrERq7QzqouGURA0NYVqUUm-11wMvo,2499
|
|
2
1
|
mvn_tree_visualizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
2
|
mvn_tree_visualizer/__main__.py,sha256=yIQFAdWjthKAFbSzzRuz5_YGlK0c6BnR2ypjNRDq180,82
|
|
4
|
-
mvn_tree_visualizer/cli.py,sha256=
|
|
3
|
+
mvn_tree_visualizer/cli.py,sha256=Gufk14zH74lqSTz2o2KPPEmNLvOHKHLyno-eSyXqahw,8504
|
|
5
4
|
mvn_tree_visualizer/diagram.py,sha256=UfvP_J4Im4JQLe3EWlY3TsP4tua3oYk5NiCGbZNQwoA,933
|
|
5
|
+
mvn_tree_visualizer/enhanced_template.py,sha256=I35fNkZrlA5jYdyjPW0jU4FH4FF2HEagtwcUX6fcmMc,7050
|
|
6
6
|
mvn_tree_visualizer/exceptions.py,sha256=R4nnJ0xrOpd84GfPD9rFSDk40etDLoph7iZpj1CCR0c,543
|
|
7
7
|
mvn_tree_visualizer/file_watcher.py,sha256=JtmV1KW08_Az-XqpKhcd342WpxV1vUW-Dge9lodjjJY,2284
|
|
8
8
|
mvn_tree_visualizer/get_dependencies_in_one_file.py,sha256=nXhEhU-dI7tXa3TpoW1pv2t86t1K0yppSw8FYDtmTlQ,1973
|
|
9
|
+
mvn_tree_visualizer/themes.py,sha256=91asg9VIqa7q2sUmgRD-Fw5wJ6kKsVWlPJ-bX9kGZhw,5469
|
|
9
10
|
mvn_tree_visualizer/validation.py,sha256=UQ194gHiVS8UnJpp90sCM-Vjn3aeXT6scdwOplAoaSE,3689
|
|
10
|
-
mvn_tree_visualizer/outputs/html_output.py,sha256=
|
|
11
|
+
mvn_tree_visualizer/outputs/html_output.py,sha256=Y0IY-UF0UMTa5w8mVFoLidgcW6BUBTxASO0iRo26hH4,5531
|
|
11
12
|
mvn_tree_visualizer/outputs/json_output.py,sha256=cXntw9ndE_BcrmFnuV61cEwZaRMp9Ev0SxaK1SUedlw,2037
|
|
12
|
-
mvn_tree_visualizer-1.
|
|
13
|
-
mvn_tree_visualizer-1.
|
|
14
|
-
mvn_tree_visualizer-1.
|
|
15
|
-
mvn_tree_visualizer-1.
|
|
16
|
-
mvn_tree_visualizer-1.
|
|
13
|
+
mvn_tree_visualizer-1.4.0.dist-info/METADATA,sha256=AIAeZ3goQMeRVtZDxJLIRnlw37nBL1kCMHHRnjk2XuM,8756
|
|
14
|
+
mvn_tree_visualizer-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
mvn_tree_visualizer-1.4.0.dist-info/entry_points.txt,sha256=Mu3QZhrlvbYuCxqmluVGi2efgKjkQY6T8Opf-vdb7hU,68
|
|
16
|
+
mvn_tree_visualizer-1.4.0.dist-info/licenses/LICENSE,sha256=4zi6unpe17RUDMBu7ebh14jdbyvyeT-UA3n8Zl7aW74,1075
|
|
17
|
+
mvn_tree_visualizer-1.4.0.dist-info/RECORD,,
|
mvn_tree_visualizer/TEMPLATE.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
HTML_TEMPLATE = r"""<html></html>
|
|
2
|
-
<head>
|
|
3
|
-
<style type="text/css">
|
|
4
|
-
#mySvgId {
|
|
5
|
-
height: 90%;
|
|
6
|
-
width: 90%;
|
|
7
|
-
}
|
|
8
|
-
</style>
|
|
9
|
-
<title>Dependency Diagram</title>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<div id="graphDiv"></div>
|
|
13
|
-
<button id="downloadButton">Download SVG</button>
|
|
14
|
-
<script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.5.0/dist/svg-pan-zoom.min.js"></script>
|
|
15
|
-
<script type="module">
|
|
16
|
-
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10.9.0/dist/mermaid.esm.min.mjs';
|
|
17
|
-
mermaid.initialize({
|
|
18
|
-
startOnLoad:true,
|
|
19
|
-
sequence:{
|
|
20
|
-
useMaxWidth:false
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const drawDiagram = async function () {
|
|
25
|
-
const element = document.querySelector('#graphDiv');
|
|
26
|
-
const graphDefinition = `
|
|
27
|
-
{{diagram_definition}}
|
|
28
|
-
`;
|
|
29
|
-
const { svg } = await mermaid.render('mySvgId', graphDefinition);
|
|
30
|
-
element.innerHTML = svg.replace(/[ ]*max-width:[ 0-9\.]*px;/i , '');
|
|
31
|
-
var panZoomTiger = svgPanZoom('#mySvgId', {
|
|
32
|
-
zoomEnabled: true,
|
|
33
|
-
controlIconsEnabled: true,
|
|
34
|
-
fit: true,
|
|
35
|
-
center: true
|
|
36
|
-
})
|
|
37
|
-
};
|
|
38
|
-
await drawDiagram();
|
|
39
|
-
|
|
40
|
-
// Add event listener to the download button to download the SVG without the pan & zoom buttons
|
|
41
|
-
document.getElementById('downloadButton').addEventListener('click', function() {
|
|
42
|
-
const svg = document.querySelector('#mySvgId');
|
|
43
|
-
let svgData = new XMLSerializer().serializeToString(svg);
|
|
44
|
-
|
|
45
|
-
// To remove the pan & zoom buttons of the diagram, any element whose class contains the string 'svg-pan-zoom-*' should be removed
|
|
46
|
-
svgData = svgData.replace(/<g\b[^>]*\bclass="svg-pan-zoom-.*?".*?>.*?<\/g>/g, '');
|
|
47
|
-
// The above leaves out a closing </g> tag before the final </svg> tag, so we need to remove it
|
|
48
|
-
svgData = svgData.replace(/<\/g><\/svg>/, '</svg>');
|
|
49
|
-
|
|
50
|
-
const svgBlob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
|
|
51
|
-
const svgUrl = URL.createObjectURL(svgBlob);
|
|
52
|
-
const downloadLink = document.createElement('a');
|
|
53
|
-
downloadLink.href = svgUrl;
|
|
54
|
-
downloadLink.download = 'diagram.svg';
|
|
55
|
-
document.body.appendChild(downloadLink);
|
|
56
|
-
downloadLink.click();
|
|
57
|
-
document.body.removeChild(downloadLink);
|
|
58
|
-
});
|
|
59
|
-
</script>
|
|
60
|
-
</body>
|
|
61
|
-
</html>"""
|
|
File without changes
|
{mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{mvn_tree_visualizer-1.3.0.dist-info → mvn_tree_visualizer-1.4.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|