mvn-tree-visualizer 1.3.0__py3-none-any.whl → 1.5.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.
@@ -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,281 @@
1
+ """Enhanced HTML templates with the interactive features."""
2
+
3
+ from typing import Any, Dict
4
+
5
+ from .themes import MAX_ZOOM, MIN_ZOOM, STANDARD_COLORS, ZOOM_SCALE_SENSITIVITY, 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 class="control-group">
97
+ <span class="control-label">Navigation:</span>
98
+ <button id="zoomInButton" class="toggle-btn">Zoom In (+)</button>
99
+ <button id="zoomOutButton" class="toggle-btn">Zoom Out (-)</button>
100
+ <button id="resetZoomButton" class="toggle-btn">Reset (Ctrl+R)</button>
101
+ </div>
102
+ </div>
103
+
104
+ <div id="graphDiv"></div>
105
+
106
+ <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.5.0/dist/svg-pan-zoom.min.js"></script>
107
+ <script type="module">
108
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11.9.0/dist/mermaid.esm.min.mjs';
109
+
110
+ // Initialize mermaid with theme configuration
111
+ mermaid.initialize({mermaid_config_js});
112
+
113
+ // Global variables
114
+ let panZoomInstance = null;
115
+
116
+ const MIN_ZOOM = {MIN_ZOOM};
117
+ const MAX_ZOOM = {MAX_ZOOM};
118
+ const ZOOM_SCALE_SENSITIVITY = {ZOOM_SCALE_SENSITIVITY};
119
+
120
+ const drawDiagram = async function () {{
121
+ const element = document.querySelector('#graphDiv');
122
+ const graphDefinition = `{{{{diagram_definition}}}}`;
123
+
124
+ try {{
125
+ const {{ svg }} = await mermaid.render('mySvgId', graphDefinition);
126
+ element.innerHTML = svg.replace(/[ ]*max-width:[ 0-9\\.]*px;/i , '');
127
+
128
+ // Initialize pan & zoom with improved settings for large diagrams
129
+ panZoomInstance = svgPanZoom('#mySvgId', {{
130
+ zoomEnabled: true,
131
+ controlIconsEnabled: true,
132
+ fit: true,
133
+ center: true,
134
+ minZoom: MIN_ZOOM, // Allow zooming out further for large diagrams
135
+ maxZoom: MAX_ZOOM, // Allow much higher zoom for detailed inspection
136
+ zoomScaleSensitivity: ZOOM_SCALE_SENSITIVITY, // Smoother zoom increments
137
+ mouseWheelZoomEnabled: true,
138
+ preventMouseEventsDefault: true,
139
+ beforeZoom: function(oldScale, newScale) {{
140
+ // Prevent zooming beyond reasonable limits
141
+ return newScale >= MIN_ZOOM && newScale <= MAX_ZOOM;
142
+ }}
143
+ }});
144
+
145
+ // Setup node interactions
146
+ setupNodeInteractions();
147
+
148
+ }} catch (error) {{
149
+ console.error('Error rendering diagram:', error);
150
+ element.innerHTML = `<p style="color: red; padding: 20px;">Error rendering diagram: ${{error.message}}</p>`;
151
+ }}
152
+ }};
153
+
154
+ const setupNodeInteractions = function() {{
155
+ const nodes = document.querySelectorAll('#mySvgId .node');
156
+
157
+ nodes.forEach(node => {{
158
+ node.style.cursor = 'pointer';
159
+ }});
160
+ }};
161
+
162
+ // Button event listeners
163
+ document.getElementById('downloadButton').addEventListener('click', function() {{
164
+ downloadSVG();
165
+ }});
166
+
167
+ document.getElementById('zoomInButton').addEventListener('click', function() {{
168
+ if (panZoomInstance) {{
169
+ panZoomInstance.zoomIn();
170
+ }}
171
+ }});
172
+
173
+ document.getElementById('zoomOutButton').addEventListener('click', function() {{
174
+ if (panZoomInstance) {{
175
+ panZoomInstance.zoomOut();
176
+ }}
177
+ }});
178
+
179
+ document.getElementById('resetZoomButton').addEventListener('click', function() {{
180
+ if (panZoomInstance) {{
181
+ panZoomInstance.reset();
182
+ }}
183
+ }});
184
+
185
+ const downloadSVG = function() {{
186
+ const svg = document.querySelector('#mySvgId');
187
+ let svgData = new XMLSerializer().serializeToString(svg);
188
+
189
+ // Clean up pan & zoom controls
190
+ svgData = svgData.replace(/<g\\b[^>]*\\bclass="svg-pan-zoom-.*?".*?>.*?<\\/g>/g, '');
191
+ svgData = svgData.replace(/<\\/g><\\/svg>/, '</svg>');
192
+
193
+ const svgBlob = new Blob([svgData], {{type: 'image/svg+xml;charset=utf-8'}});
194
+ const svgUrl = URL.createObjectURL(svgBlob);
195
+ const downloadLink = document.createElement('a');
196
+ downloadLink.href = svgUrl;
197
+ downloadLink.download = 'dependency-diagram.svg';
198
+ document.body.appendChild(downloadLink);
199
+ downloadLink.click();
200
+ document.body.removeChild(downloadLink);
201
+ URL.revokeObjectURL(svgUrl);
202
+ }};
203
+
204
+ // Initialize the diagram
205
+ await drawDiagram();
206
+
207
+ // Keyboard shortcuts
208
+ document.addEventListener('keydown', (e) => {{
209
+ if (e.ctrlKey || e.metaKey) {{
210
+ switch(e.key) {{
211
+ case 's':
212
+ e.preventDefault();
213
+ downloadSVG();
214
+ break;
215
+ case 'r':
216
+ e.preventDefault();
217
+ if (panZoomInstance) {{
218
+ panZoomInstance.reset();
219
+ }}
220
+ break;
221
+ case '=':
222
+ case '+':
223
+ e.preventDefault();
224
+ if (panZoomInstance) {{
225
+ panZoomInstance.zoomIn();
226
+ }}
227
+ break;
228
+ case '-':
229
+ e.preventDefault();
230
+ if (panZoomInstance) {{
231
+ panZoomInstance.zoomOut();
232
+ }}
233
+ break;
234
+ }}
235
+ }} else {{
236
+ // Non-Ctrl shortcuts
237
+ switch(e.key) {{
238
+ case '+':
239
+ case '=':
240
+ if (panZoomInstance) {{
241
+ panZoomInstance.zoomIn();
242
+ }}
243
+ break;
244
+ case '-':
245
+ if (panZoomInstance) {{
246
+ panZoomInstance.zoomOut();
247
+ }}
248
+ break;
249
+ }}
250
+ }}
251
+ }});
252
+
253
+ </script>
254
+ </body>
255
+ </html>"""
256
+
257
+
258
+ def _dict_to_js_object(d: Dict[str, Any], indent: int = 0) -> str:
259
+ """Convert Python dict to JavaScript object string."""
260
+ if not isinstance(d, dict):
261
+ if isinstance(d, str):
262
+ return f'"{d}"'
263
+ elif isinstance(d, bool):
264
+ return str(d).lower()
265
+ else:
266
+ return str(d)
267
+
268
+ items = []
269
+ for key, value in d.items():
270
+ if isinstance(value, dict):
271
+ value_str = _dict_to_js_object(value, indent + 1)
272
+ elif isinstance(value, str):
273
+ value_str = f'"{value}"'
274
+ elif isinstance(value, bool):
275
+ value_str = str(value).lower()
276
+ else:
277
+ value_str = str(value)
278
+
279
+ items.append(f'"{key}": {value_str}')
280
+
281
+ 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 ..TEMPLATE import HTML_TEMPLATE
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
- template = Environment(loader=BaseLoader).from_string(HTML_TEMPLATE)
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
- # generate a `graph LR` format for Mermaid
28
+ """Convert dependency tree to enhanced Mermaid diagram with styling."""
22
29
  lines: List[str] = dependency_tree.strip().split("\n")
23
- mermaid_lines: Set[str] = set()
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
- mermaid_lines.add(f"\t{node_label};")
53
+ safe_node_id: str = _sanitize_node_id(f"{artifact_id}_{version}")
38
54
  else:
39
55
  node_label: str = artifact_id
40
- mermaid_lines.add(f"\t{artifact_id};")
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((node_label, 0)) # The second element is the depth
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
- mermaid_lines.add(f"\t{previous_dependency[-1][0]} --> {node_label};")
58
- previous_dependency.append((node_label, depth))
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
- mermaid_lines.add(f"\t{previous_dependency[-1][0]} --> {node_label};")
64
- previous_dependency.append((node_label, depth))
65
- return "graph LR\n" + "\n".join(mermaid_lines)
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,202 @@
1
+ """Theme configurations for mvn-tree-visualizer diagrams."""
2
+
3
+ from typing import Any, Dict
4
+
5
+ # Mermaid configuration constants for large project support
6
+ MAX_TEXT_SIZE_LARGE_PROJECTS = 900000000 # Increase max text size for large dependency trees
7
+ MAX_EDGES_LARGE_PROJECTS = 20000 # Increase max edges for large projects
8
+
9
+ # Zoom configuration constants for enhanced navigation
10
+ MIN_ZOOM = 0.01 # Minimum zoom level for large diagram overview
11
+ MAX_ZOOM = 50 # Maximum zoom level for detailed inspection
12
+ ZOOM_SCALE_SENSITIVITY = 0.2 # Smoother zoom increments
13
+
14
+
15
+ class Theme:
16
+ """Base theme configuration class."""
17
+
18
+ def __init__(
19
+ self,
20
+ name: str,
21
+ mermaid_theme: str = "default",
22
+ background_color: str = "#ffffff",
23
+ custom_css: str = "",
24
+ mermaid_config: Dict[str, Any] = None,
25
+ ):
26
+ self.name = name
27
+ self.mermaid_theme = mermaid_theme
28
+ self.background_color = background_color
29
+ self.custom_css = custom_css
30
+ self.mermaid_config = mermaid_config or {}
31
+
32
+
33
+ # Standard color scheme for consistency across themes
34
+ # Root nodes: Blue shades, Intermediate nodes: Orange shades, Leaf nodes: Green shades
35
+ STANDARD_COLORS = {
36
+ "root_node": "#3B82F6", # Blue for root nodes
37
+ "intermediate_node": "#F59E0B", # Orange for intermediate nodes
38
+ "leaf_node": "#10B981", # Green for leaf nodes
39
+ }
40
+
41
+ # Predefined themes
42
+ THEMES = {
43
+ "minimal": Theme(
44
+ name="minimal",
45
+ mermaid_theme="neutral",
46
+ background_color="#ffffff",
47
+ custom_css="""
48
+ body {
49
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
50
+ margin: 0;
51
+ padding: 20px;
52
+ background-color: #ffffff;
53
+ color: #000000;
54
+ line-height: 1.6;
55
+ height: 100vh;
56
+ box-sizing: border-box;
57
+ }
58
+ #graphDiv {
59
+ background-color: #ffffff;
60
+ border: 1px solid #000000;
61
+ padding: 20px;
62
+ margin-bottom: 20px;
63
+ height: calc(100vh - 120px);
64
+ overflow: hidden;
65
+ }
66
+ .controls {
67
+ margin-bottom: 20px;
68
+ display: flex;
69
+ gap: 10px;
70
+ flex-wrap: wrap;
71
+ align-items: center;
72
+ }
73
+ .control-group {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 8px;
77
+ }
78
+ .control-label {
79
+ font-size: 12px;
80
+ font-weight: normal;
81
+ color: #000000;
82
+ text-transform: uppercase;
83
+ }
84
+ .toggle-btn {
85
+ background-color: #000000;
86
+ color: white;
87
+ border: none;
88
+ padding: 8px 16px;
89
+ cursor: pointer;
90
+ font-family: inherit;
91
+ font-size: 12px;
92
+ text-transform: uppercase;
93
+ letter-spacing: 1px;
94
+ transition: opacity 0.2s;
95
+ }
96
+ .toggle-btn:hover {
97
+ opacity: 0.8;
98
+ }
99
+ """,
100
+ mermaid_config={
101
+ "theme": "neutral",
102
+ "themeVariables": {
103
+ "primaryColor": STANDARD_COLORS["root_node"],
104
+ "primaryTextColor": "#000000",
105
+ "primaryBorderColor": "#000000",
106
+ "lineColor": "#000000",
107
+ "secondaryColor": "#f5f5f5",
108
+ "tertiaryColor": "#ffffff",
109
+ },
110
+ "maxTextSize": MAX_TEXT_SIZE_LARGE_PROJECTS, # Increase max text size for large dependency trees
111
+ "maxEdges": MAX_EDGES_LARGE_PROJECTS, # Increase max edges for large projects
112
+ },
113
+ ),
114
+ "dark": Theme(
115
+ name="dark",
116
+ mermaid_theme="forest",
117
+ background_color="#2d3748",
118
+ custom_css="""
119
+ body {
120
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
121
+ margin: 0;
122
+ padding: 20px;
123
+ background-color: #2d3748;
124
+ color: #f7fafc;
125
+ line-height: 1.6;
126
+ height: 100vh;
127
+ box-sizing: border-box;
128
+ }
129
+ #graphDiv {
130
+ background-color: #2d3748;
131
+ border: 1px solid #e2e8f0;
132
+ padding: 20px;
133
+ margin-bottom: 20px;
134
+ height: calc(100vh - 120px);
135
+ overflow: hidden;
136
+ }
137
+ .controls {
138
+ margin-bottom: 20px;
139
+ display: flex;
140
+ gap: 10px;
141
+ flex-wrap: wrap;
142
+ align-items: center;
143
+ }
144
+ .control-group {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 8px;
148
+ }
149
+ .control-label {
150
+ font-size: 12px;
151
+ font-weight: normal;
152
+ color: #f7fafc;
153
+ text-transform: uppercase;
154
+ }
155
+ .toggle-btn {
156
+ background-color: #f7fafc;
157
+ color: #2d3748;
158
+ border: none;
159
+ padding: 8px 16px;
160
+ cursor: pointer;
161
+ font-family: inherit;
162
+ font-size: 12px;
163
+ text-transform: uppercase;
164
+ letter-spacing: 1px;
165
+ transition: opacity 0.2s;
166
+ }
167
+ .toggle-btn:hover {
168
+ opacity: 0.8;
169
+ }
170
+ """,
171
+ mermaid_config={
172
+ "theme": "forest",
173
+ "themeVariables": {
174
+ "primaryColor": STANDARD_COLORS["root_node"],
175
+ "primaryTextColor": "#ffffff",
176
+ "primaryBorderColor": "#e2e8f0",
177
+ "lineColor": "#a0aec0",
178
+ "secondaryColor": "#4a5568",
179
+ "tertiaryColor": "#2d3748",
180
+ "background": "#2d3748",
181
+ "mainBkg": "#4a5568",
182
+ "nodeBkg": "#4a5568",
183
+ "clusterBkg": "#2d3748",
184
+ "edgeLabelBackground": "#2d3748",
185
+ "nodeTextColor": "#ffffff",
186
+ "textColor": "#ffffff",
187
+ },
188
+ "maxTextSize": MAX_TEXT_SIZE_LARGE_PROJECTS, # Increase max text size for large dependency trees
189
+ "maxEdges": MAX_EDGES_LARGE_PROJECTS, # Increase max edges for large projects
190
+ },
191
+ ),
192
+ }
193
+
194
+
195
+ def get_theme(theme_name: str) -> Theme:
196
+ """Get a theme by name, fallback to minimal if not found."""
197
+ return THEMES.get(theme_name, THEMES["minimal"])
198
+
199
+
200
+ def get_available_themes() -> list[str]:
201
+ """Get list of available theme names."""
202
+ return list(THEMES.keys())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mvn-tree-visualizer
3
- Version: 1.3.0
3
+ Version: 1.5.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,12 @@ 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
- - **📋 Version Display:** Show or hide dependency versions in both HTML and JSON outputs using the `--show-versions` flag.
60
- - **⚡ Easy to Use:** A simple command-line interface that gets the job done with minimal configuration.
61
- - **📂 File Merging:** Automatically finds and merges multiple `maven_dependency_file` files from different subdirectories.
62
- - **🎨 Customizable Output:** Specify the output file name and location.
63
- - **💾 SVG Export:** Download the generated diagram as an SVG file directly from the HTML page.
60
+ - **📋 Version Display:** Toggle dependency versions in outputs with `--show-versions`
61
+ - **💾 Enhanced Downloads:** SVG and PNG export directly from browser
62
+ - **📂 Smart File Handling:** Automatically finds and merges multiple `maven_dependency_file` files from different subdirectories.
63
+ - **🎯 Color Coding:** Visual distinction between root, intermediate, and leaf dependencies
64
64
 
65
65
  ## How to Use
66
66
 
@@ -93,6 +93,15 @@ mvn_tree_visualizer --filename "maven_dependency_file" --output "dependencies.js
93
93
  mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --show-versions
94
94
  ```
95
95
 
96
+ #### With Custom Themes
97
+ ```bash
98
+ # Dark theme for low-light environments
99
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --theme dark
100
+
101
+ # Default minimal theme (clean monospace design)
102
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html"
103
+ ```
104
+
96
105
  #### JSON Output with Versions
97
106
  ```bash
98
107
  mvn_tree_visualizer --filename "maven_dependency_file" --output "dependencies.json" --format json --show-versions
@@ -129,12 +138,20 @@ Each example includes:
129
138
  | `--filename` | The name of the file containing the Maven dependency tree | `maven_dependency_file` |
130
139
  | `--output` | The name of the output file | `diagram.html` |
131
140
  | `--format` | The output format (`html` or `json`) | `html` |
141
+ | `--theme` | Theme for HTML diagrams (`minimal`, `dark`) | `minimal` |
132
142
  | `--show-versions` | Show dependency versions in the diagram | `False` |
133
143
  | `--watch` | Watch for file changes and auto-regenerate diagram | `False` |
134
144
  | `--directory` | The directory to scan for the Maven dependency file(s) | current directory |
135
145
  | `--keep-tree` | Keep the intermediate `dependency_tree.txt` file | `False` |
136
146
  | `--help` | Show the help message and exit | - |
137
147
 
148
+ ### Theme Options
149
+
150
+ - **`minimal`**: Clean monospace design with simple black borders (default)
151
+ - **`dark`**: Same minimal styling but with white text on black background
152
+
153
+ 📖 **See the complete [Theme Documentation](docs/THEMES.md) for detailed information about themes and interactive features.**
154
+
138
155
  ## Performance
139
156
 
140
157
  **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=33K5ujgB85xejcSUKMNAwirpLfnGMGfrnhqyLFOy69k,8219
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=AwK4gw5U8Ag-RmeD8Y11LXLRSnVzMFZhWja__c2iF4M,9813
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=T7vdNVTHPtYYkh4HusGG1MUWk1mJcqFBjCc5IK5l6Dc,6338
9
10
  mvn_tree_visualizer/validation.py,sha256=UQ194gHiVS8UnJpp90sCM-Vjn3aeXT6scdwOplAoaSE,3689
10
- mvn_tree_visualizer/outputs/html_output.py,sha256=QwUZRzNUCKrpdOq6BHadIJSU47W_1Kat-ouzwue-olA,2857
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.3.0.dist-info/METADATA,sha256=YHhhNT8CjZUB_rSbdOip-7anjiGD7NxmoYPdl4bZw50,7834
13
- mvn_tree_visualizer-1.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- mvn_tree_visualizer-1.3.0.dist-info/entry_points.txt,sha256=Mu3QZhrlvbYuCxqmluVGi2efgKjkQY6T8Opf-vdb7hU,68
15
- mvn_tree_visualizer-1.3.0.dist-info/licenses/LICENSE,sha256=4zi6unpe17RUDMBu7ebh14jdbyvyeT-UA3n8Zl7aW74,1075
16
- mvn_tree_visualizer-1.3.0.dist-info/RECORD,,
13
+ mvn_tree_visualizer-1.5.0.dist-info/METADATA,sha256=DhMPseZxQHVHuPOVHPXtdA7h6vBUWc3gKAPbhaBDIxU,8477
14
+ mvn_tree_visualizer-1.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ mvn_tree_visualizer-1.5.0.dist-info/entry_points.txt,sha256=Mu3QZhrlvbYuCxqmluVGi2efgKjkQY6T8Opf-vdb7hU,68
16
+ mvn_tree_visualizer-1.5.0.dist-info/licenses/LICENSE,sha256=4zi6unpe17RUDMBu7ebh14jdbyvyeT-UA3n8Zl7aW74,1075
17
+ mvn_tree_visualizer-1.5.0.dist-info/RECORD,,
@@ -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>"""