mvn-tree-visualizer 1.1.0__py3-none-any.whl → 1.3.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.
@@ -1,13 +1,130 @@
1
1
  import argparse
2
+ import time
2
3
  from pathlib import Path
4
+ from typing import NoReturn
3
5
 
4
6
  from .diagram import create_diagram
7
+ from .exceptions import DependencyFileNotFoundError, DependencyParsingError, MvnTreeVisualizerError, OutputGenerationError
8
+ from .file_watcher import FileWatcher
5
9
  from .get_dependencies_in_one_file import merge_files
6
10
  from .outputs.html_output import create_html_diagram
7
11
  from .outputs.json_output import create_json_output
12
+ from .validation import find_dependency_files, print_maven_help, validate_dependency_files, validate_output_directory
8
13
 
9
14
 
10
- def cli():
15
+ def generate_diagram(
16
+ directory: str,
17
+ output_file: str,
18
+ filename: str,
19
+ keep_tree: bool,
20
+ output_format: str,
21
+ show_versions: bool,
22
+ ) -> None:
23
+ """Generate the dependency diagram with comprehensive error handling."""
24
+ timestamp = time.strftime("%H:%M:%S")
25
+
26
+ try:
27
+ # Validate inputs
28
+ validate_dependency_files(directory, filename)
29
+ validate_output_directory(output_file)
30
+
31
+ # Show what files we found
32
+ dependency_files = find_dependency_files(directory, filename)
33
+ if len(dependency_files) > 1:
34
+ print(f"[{timestamp}] Found {len(dependency_files)} dependency files")
35
+
36
+ # Setup paths
37
+ dir_to_create_files = Path(output_file).parent
38
+ dir_to_create_intermediate_files = Path(dir_to_create_files)
39
+ intermediate_file_path: Path = dir_to_create_intermediate_files / "dependency_tree.txt"
40
+
41
+ # Merge dependency files
42
+ try:
43
+ merge_files(
44
+ output_file=intermediate_file_path,
45
+ root_dir=directory,
46
+ target_filename=filename,
47
+ )
48
+ except FileNotFoundError as e:
49
+ raise DependencyParsingError(f"Error reading dependency file: {e}\nThe file may have been moved or deleted during processing.")
50
+ except PermissionError as e:
51
+ raise DependencyParsingError(f"Permission denied while reading dependency files: {e}\nPlease check file permissions and try again.")
52
+ except UnicodeDecodeError as e:
53
+ raise DependencyParsingError(
54
+ f"Error decoding dependency file content: {e}\n"
55
+ f"The file may contain invalid characters or use an unsupported encoding.\n"
56
+ f"Please ensure the file is in UTF-8 format."
57
+ )
58
+
59
+ # Validate merged content
60
+ if not intermediate_file_path.exists() or intermediate_file_path.stat().st_size == 0:
61
+ raise DependencyParsingError(
62
+ "Generated dependency tree file is empty.\n"
63
+ "This usually means the Maven dependency files contain no valid dependency information.\n"
64
+ "Please check that your Maven dependency files were generated correctly."
65
+ )
66
+
67
+ # Create diagram from merged content
68
+ try:
69
+ dependency_tree = create_diagram(
70
+ keep_tree=keep_tree,
71
+ intermediate_filename=str(intermediate_file_path),
72
+ )
73
+ except FileNotFoundError:
74
+ raise DependencyParsingError("Intermediate dependency tree file was not found.\nThis is an internal error - please report this issue.")
75
+ except Exception as e:
76
+ raise DependencyParsingError(f"Error processing dependency tree: {e}\nThe dependency file format may be invalid or corrupted.")
77
+
78
+ # Validate that we have content to work with
79
+ if not dependency_tree.strip():
80
+ raise DependencyParsingError(
81
+ "Dependency tree is empty after processing.\n"
82
+ "Please check that your Maven dependency files contain valid dependency information.\n"
83
+ "You can verify this by opening the files and checking their content."
84
+ )
85
+
86
+ # Generate output
87
+ try:
88
+ if output_format == "html":
89
+ create_html_diagram(dependency_tree, output_file, show_versions)
90
+ elif output_format == "json":
91
+ create_json_output(dependency_tree, output_file, show_versions)
92
+ else:
93
+ raise OutputGenerationError(f"Unsupported output format: {output_format}")
94
+ except PermissionError:
95
+ raise OutputGenerationError(
96
+ f"Permission denied writing to '{output_file}'.\nPlease check that you have write permissions to this location."
97
+ )
98
+ except OSError as e:
99
+ raise OutputGenerationError(
100
+ f"Error writing output file '{output_file}': {e}\nPlease check that you have enough disk space and write permissions."
101
+ )
102
+ except Exception as e:
103
+ raise OutputGenerationError(f"Error generating {output_format.upper()} output: {e}")
104
+
105
+ print(f"[{timestamp}] ✅ Diagram generated and saved to {output_file}")
106
+
107
+ except MvnTreeVisualizerError as e:
108
+ # Our custom errors already have helpful messages
109
+ print(f"[{timestamp}] ❌ Error: {e}")
110
+ if isinstance(e, DependencyFileNotFoundError):
111
+ print_maven_help()
112
+ except KeyboardInterrupt:
113
+ print(f"\n[{timestamp}] ⏹️ Operation cancelled by user")
114
+ except Exception as e:
115
+ # Unexpected errors
116
+ print(f"[{timestamp}] ❌ Unexpected error: {e}")
117
+ print("This is an internal error. Please report this issue with the following details:")
118
+ print(f" - Directory: {directory}")
119
+ print(f" - Filename: {filename}")
120
+ print(f" - Output: {output_file}")
121
+ print(f" - Format: {output_format}")
122
+ import traceback
123
+
124
+ traceback.print_exc()
125
+
126
+
127
+ def cli() -> NoReturn:
11
128
  parser = argparse.ArgumentParser(
12
129
  prog="mvn-tree-visualizer",
13
130
  description="Generate a dependency diagram from a file.",
@@ -50,37 +167,49 @@ def cli():
50
167
  help="Keep the dependency tree file after generating the diagram. Default is False.",
51
168
  )
52
169
 
170
+ parser.add_argument(
171
+ "--show-versions",
172
+ action="store_true",
173
+ help="Show dependency versions in the diagram. Applicable to both HTML and JSON output formats.",
174
+ )
175
+
176
+ parser.add_argument(
177
+ "--watch",
178
+ action="store_true",
179
+ help="Watch for changes in Maven dependency files and automatically regenerate the diagram.",
180
+ )
181
+
53
182
  args = parser.parse_args()
54
183
  directory: str = args.directory
55
184
  output_file: str = args.output
56
185
  filename: str = args.filename
57
186
  keep_tree: bool = args.keep_tree
58
187
  output_format: str = args.format
188
+ show_versions: bool = args.show_versions
189
+ watch_mode: bool = args.watch
59
190
 
60
- dir_to_create_files = Path(output_file).parent
191
+ # Generate initial diagram
192
+ print("Generating initial diagram...")
193
+ generate_diagram(directory, output_file, filename, keep_tree, output_format, show_versions)
61
194
 
62
- dir_to_create_intermediate_files = Path(dir_to_create_files)
195
+ if not watch_mode:
196
+ print("You can open it in your browser to view the dependency tree.")
197
+ print("Thank you for using mvn-tree-visualizer!")
198
+ return
63
199
 
64
- merge_files(
65
- output_file=dir_to_create_intermediate_files / "dependency_tree.txt",
66
- root_dir=directory,
67
- target_filename=filename,
68
- )
69
-
70
- dependency_tree = create_diagram(
71
- keep_tree=keep_tree,
72
- intermediate_filename="dependency_tree.txt",
73
- )
200
+ # Watch mode
201
+ def regenerate_callback():
202
+ """Callback function for file watcher."""
203
+ generate_diagram(directory, output_file, filename, keep_tree, output_format, show_versions)
74
204
 
75
- if output_format == "html":
76
- create_html_diagram(dependency_tree, output_file)
77
- elif output_format == "json":
78
- create_json_output(dependency_tree, output_file)
205
+ watcher = FileWatcher(directory, filename, regenerate_callback)
206
+ watcher.start()
79
207
 
80
- print(f"Diagram generated and saved to {output_file}")
81
- print("You can open it in your browser to view the dependency tree.")
82
- print("Thank you for using mvn-tree-visualizer!")
208
+ try:
209
+ watcher.wait()
210
+ finally:
211
+ print("Thank you for using mvn-tree-visualizer!")
83
212
 
84
213
 
85
214
  if __name__ == "__main__":
86
- cli()
215
+ cli()
@@ -1,13 +1,26 @@
1
+ import os
2
+
3
+
1
4
  def create_diagram(
2
5
  keep_tree: bool = False,
3
6
  intermediate_filename: str = "dependency_tree.txt",
4
- ):
5
- with open(intermediate_filename, "r") as file:
6
- dependency_tree = file.read()
7
+ ) -> str:
8
+ """Create diagram from dependency tree file."""
9
+ try:
10
+ with open(intermediate_filename, "r", encoding="utf-8") as file:
11
+ dependency_tree: str = file.read()
12
+ except FileNotFoundError:
13
+ raise FileNotFoundError(f"Dependency tree file '{intermediate_filename}' not found")
14
+ except PermissionError:
15
+ raise PermissionError(f"Permission denied reading '{intermediate_filename}'")
16
+ except UnicodeDecodeError as e:
17
+ raise UnicodeDecodeError(e.encoding, e.object, e.start, e.end, f"Error decoding '{intermediate_filename}': {e.reason}")
7
18
 
8
19
  if not keep_tree:
9
- import os
20
+ try:
21
+ os.remove(intermediate_filename)
22
+ except OSError:
23
+ # If we can't remove the intermediate file, it's not critical
24
+ pass
10
25
 
11
- os.remove(intermediate_filename)
12
-
13
26
  return dependency_tree
@@ -0,0 +1,25 @@
1
+ """Custom exceptions for mvn-tree-visualizer."""
2
+
3
+
4
+ class MvnTreeVisualizerError(Exception):
5
+ """Base exception for mvn-tree-visualizer errors."""
6
+
7
+ pass
8
+
9
+
10
+ class DependencyFileNotFoundError(MvnTreeVisualizerError):
11
+ """Raised when no dependency files are found."""
12
+
13
+ pass
14
+
15
+
16
+ class DependencyParsingError(MvnTreeVisualizerError):
17
+ """Raised when there's an error parsing dependency files."""
18
+
19
+ pass
20
+
21
+
22
+ class OutputGenerationError(MvnTreeVisualizerError):
23
+ """Raised when there's an error generating output files."""
24
+
25
+ pass
@@ -0,0 +1,71 @@
1
+ """File watcher functionality for monitoring Maven dependency files."""
2
+
3
+ import time
4
+ from typing import Callable
5
+
6
+ from watchdog.events import FileSystemEventHandler
7
+ from watchdog.observers import Observer
8
+
9
+
10
+ class DependencyFileHandler(FileSystemEventHandler):
11
+ """Handler for file system events to trigger diagram regeneration."""
12
+
13
+ def __init__(
14
+ self,
15
+ filename: str,
16
+ callback: Callable[[], None],
17
+ ):
18
+ """Initialize the file handler.
19
+
20
+ Args:
21
+ filename: Name of the file to monitor for changes
22
+ callback: Function to call when file changes are detected
23
+ """
24
+ self.filename = filename
25
+ self.callback = callback
26
+
27
+ def on_modified(self, event):
28
+ """Handle file modification events."""
29
+ if not event.is_directory and event.src_path.endswith(self.filename):
30
+ print(f"Detected change in {event.src_path}")
31
+ self.callback()
32
+
33
+
34
+ class FileWatcher:
35
+ """File system watcher for monitoring Maven dependency files."""
36
+
37
+ def __init__(self, directory: str, filename: str, callback: Callable[[], None]):
38
+ """Initialize the file watcher.
39
+
40
+ Args:
41
+ directory: Directory to watch for file changes
42
+ filename: Name of the file to monitor
43
+ callback: Function to call when file changes are detected
44
+ """
45
+ self.directory = directory
46
+ self.filename = filename
47
+ self.callback = callback
48
+ self.observer = Observer()
49
+ self.event_handler = DependencyFileHandler(filename, callback)
50
+
51
+ def start(self) -> None:
52
+ """Start watching for file changes."""
53
+ print(f"Watching for changes in '{self.filename}' files in '{self.directory}'...")
54
+ print("Press Ctrl+C to stop watching.")
55
+
56
+ self.observer.schedule(self.event_handler, self.directory, recursive=True)
57
+ self.observer.start()
58
+
59
+ def stop(self) -> None:
60
+ """Stop watching for file changes."""
61
+ print("\nStopping file watcher...")
62
+ self.observer.stop()
63
+ self.observer.join()
64
+
65
+ def wait(self) -> None:
66
+ """Wait for file changes (blocking)."""
67
+ try:
68
+ while True:
69
+ time.sleep(1)
70
+ except KeyboardInterrupt:
71
+ self.stop()
@@ -1,11 +1,40 @@
1
1
  import os
2
+ from pathlib import Path
3
+ from typing import Union
2
4
 
3
5
 
4
- def merge_files(output_file: str, root_dir: str = ".", target_filename: str = "maven_dependency_file"):
5
- with open(output_file, "w", encoding="utf-8") as outfile:
6
- for dirpath, _, filenames in os.walk(root_dir):
7
- for fname in filenames:
8
- if fname == target_filename:
9
- file_path = os.path.join(dirpath, fname)
10
- with open(file_path, "r", encoding="utf-8") as infile:
11
- outfile.write(infile.read())
6
+ def merge_files(output_file: Union[str, Path], root_dir: str = ".", target_filename: str = "maven_dependency_file") -> None:
7
+ """Merge all dependency files from the directory tree into a single file."""
8
+ files_found = 0
9
+
10
+ try:
11
+ with open(output_file, "w", encoding="utf-8") as outfile:
12
+ for dirpath, _, filenames in os.walk(root_dir):
13
+ for fname in filenames:
14
+ if fname == target_filename:
15
+ file_path: str = os.path.join(dirpath, fname)
16
+ try:
17
+ with open(file_path, "r", encoding="utf-8") as infile:
18
+ content = infile.read()
19
+ if content.strip(): # Only write non-empty content
20
+ outfile.write(content)
21
+ if not content.endswith("\n"):
22
+ outfile.write("\n")
23
+ files_found += 1
24
+ except UnicodeDecodeError as e:
25
+ raise UnicodeDecodeError(
26
+ e.encoding,
27
+ e.object,
28
+ e.start,
29
+ e.end,
30
+ f"Error reading '{file_path}': {e.reason}. Please ensure the file is in UTF-8 format.",
31
+ )
32
+ except PermissionError:
33
+ raise PermissionError(f"Permission denied reading '{file_path}'")
34
+ except PermissionError:
35
+ raise PermissionError(f"Permission denied writing to '{output_file}'")
36
+ except OSError as e:
37
+ raise OSError(f"Error writing to '{output_file}': {e}")
38
+
39
+ if files_found == 0:
40
+ raise FileNotFoundError(f"No '{target_filename}' files found in '{root_dir}' or its subdirectories")
@@ -1,49 +1,65 @@
1
1
  from pathlib import Path
2
+ from typing import List, Set, Tuple
3
+
2
4
  from jinja2 import BaseLoader, Environment
5
+
3
6
  from ..TEMPLATE import HTML_TEMPLATE
4
7
 
5
- def create_html_diagram(dependency_tree: str, output_filename: str):
6
- mermaid_diagram = _convert_to_mermaid(dependency_tree)
8
+
9
+ def create_html_diagram(dependency_tree: str, output_filename: str, show_versions: bool = False) -> None:
10
+ mermaid_diagram: str = _convert_to_mermaid(dependency_tree, show_versions)
7
11
  template = Environment(loader=BaseLoader).from_string(HTML_TEMPLATE)
8
- rendered = template.render(diagram_definition=mermaid_diagram)
9
- parent_dir = Path(output_filename).parent
12
+ rendered: str = template.render(diagram_definition=mermaid_diagram)
13
+ parent_dir: Path = Path(output_filename).parent
10
14
  if not parent_dir.exists():
11
15
  parent_dir.mkdir(parents=True, exist_ok=True)
12
16
  with open(output_filename, "w") as f:
13
17
  f.write(rendered)
14
18
 
15
- def _convert_to_mermaid(dependency_tree: str) -> str:
19
+
20
+ def _convert_to_mermaid(dependency_tree: str, show_versions: bool = False) -> str:
16
21
  # generate a `graph LR` format for Mermaid
17
- lines = dependency_tree.strip().split("\n")
18
- mermaid_lines = set()
19
- previous_dependency = []
22
+ lines: List[str] = dependency_tree.strip().split("\n")
23
+ mermaid_lines: Set[str] = set()
24
+ previous_dependency: List[Tuple[str, int]] = []
20
25
  for line in lines:
21
26
  if not line:
22
27
  continue
23
28
  if line.startswith("[INFO] "):
24
29
  line = line[7:] # Remove the "[INFO] " prefix
25
- parts = line.split(":")
30
+ parts: List[str] = line.split(":")
26
31
  if len(parts) < 3:
27
32
  continue
28
33
  if len(parts) == 4:
29
34
  group_id, artifact_id, app, version = parts
30
- mermaid_lines.add(f"\t{artifact_id};")
35
+ if show_versions:
36
+ node_label: str = f"{artifact_id}:{version}"
37
+ mermaid_lines.add(f"\t{node_label};")
38
+ else:
39
+ node_label: str = artifact_id
40
+ mermaid_lines.add(f"\t{artifact_id};")
31
41
  if previous_dependency: # Re initialize the list if it wasn't empty
32
42
  previous_dependency = []
33
- previous_dependency.append((artifact_id, 0)) # The second element is the depth
43
+ previous_dependency.append((node_label, 0)) # The second element is the depth
34
44
  else:
35
- depth = len(parts[0].split(" ")) - 1
45
+ depth: int = len(parts[0].split(" ")) - 1
36
46
  if len(parts) == 6:
37
47
  dirty_group_id, artifact_id, app, ejb_client, version, dependency = parts
38
48
  else:
39
49
  dirty_group_id, artifact_id, app, version, dependency = parts
50
+
51
+ if show_versions:
52
+ node_label: str = f"{artifact_id}:{version}"
53
+ else:
54
+ node_label: str = artifact_id
55
+
40
56
  if previous_dependency[-1][1] < depth:
41
- mermaid_lines.add(f"\t{previous_dependency[-1][0]} --> {artifact_id};")
42
- previous_dependency.append((artifact_id, depth))
57
+ mermaid_lines.add(f"\t{previous_dependency[-1][0]} --> {node_label};")
58
+ previous_dependency.append((node_label, depth))
43
59
  else:
44
60
  # remove all dependencies that are deeper or equal to the current depth
45
61
  while previous_dependency and previous_dependency[-1][1] >= depth:
46
62
  previous_dependency.pop()
47
- mermaid_lines.add(f"\t{previous_dependency[-1][0]} --> {artifact_id};")
48
- previous_dependency.append((artifact_id, depth))
63
+ mermaid_lines.add(f"\t{previous_dependency[-1][0]} --> {node_label};")
64
+ previous_dependency.append((node_label, depth))
49
65
  return "graph LR\n" + "\n".join(mermaid_lines)
@@ -1,9 +1,11 @@
1
1
  import json
2
+ from typing import Any, Dict, List, Tuple
2
3
 
3
- def create_json_output(dependency_tree: str, output_filename: str):
4
- lines = dependency_tree.strip().split("\n")
5
- tree = {}
6
- node_stack = [] # Stack to keep track of nodes and their depth
4
+
5
+ def create_json_output(dependency_tree: str, output_filename: str, show_versions: bool = False) -> None:
6
+ lines: List[str] = dependency_tree.strip().split("\n")
7
+ tree: Dict[str, Any] = {}
8
+ node_stack: List[Tuple[Dict[str, Any], int]] = [] # Stack to keep track of nodes and their depth
7
9
 
8
10
  for line in lines:
9
11
  if not line:
@@ -11,33 +13,43 @@ def create_json_output(dependency_tree: str, output_filename: str):
11
13
  if line.startswith("[INFO] "):
12
14
  line = line[7:] # Remove the "[INFO] " prefix
13
15
 
14
- parts = line.split(":")
16
+ parts: List[str] = line.split(":")
15
17
  if len(parts) < 3:
16
18
  continue
17
19
 
18
20
  # Root node
19
21
  if len(parts) == 4:
20
22
  group_id, artifact_id, _, version = parts
21
- node = {"id": f"{group_id}:{artifact_id}:{version}", "children": []}
23
+ if show_versions:
24
+ node_id: str = f"{artifact_id}:{version}"
25
+ else:
26
+ node_id: str = artifact_id
27
+ node: Dict[str, Any] = {"id": node_id, "children": []}
22
28
  tree = node
23
29
  node_stack = [(node, 0)] # Reset stack with root node at depth 0
24
30
  # Child node
25
31
  else:
26
32
  # This depth calculation is based on the mermaid logic's whitespace parsing
27
- depth = len(parts[0].split(" ")) - 1
33
+ depth: int = len(parts[0].split(" ")) - 1
28
34
 
29
35
  if len(parts) == 6:
30
36
  _, artifact_id, _, _, version, _ = parts
31
37
  else:
32
38
  _, artifact_id, _, version, _ = parts
33
39
 
34
- node = {"id": f"{artifact_id}:{version}", "children": []}
40
+ if show_versions:
41
+ node_id: str = f"{artifact_id}:{version}"
42
+ else:
43
+ node_id: str = artifact_id
44
+
45
+ node: Dict[str, Any] = {"id": node_id, "children": []}
35
46
 
36
47
  # Go up the stack to find the correct parent
37
48
  while node_stack and node_stack[-1][1] >= depth:
38
49
  node_stack.pop()
39
50
 
40
51
  if node_stack:
52
+ parent_node: Dict[str, Any]
41
53
  parent_node, _ = node_stack[-1]
42
54
  parent_node["children"].append(node)
43
55
 
@@ -0,0 +1,86 @@
1
+ """Validation utilities for mvn-tree-visualizer."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from .exceptions import DependencyFileNotFoundError, OutputGenerationError
7
+
8
+
9
+ def find_dependency_files(directory: str, filename: str) -> list[str]:
10
+ """Find all dependency files in the directory tree."""
11
+ found_files = []
12
+ for dirpath, _, filenames in os.walk(directory):
13
+ if filename in filenames:
14
+ found_files.append(os.path.join(dirpath, filename))
15
+ return found_files
16
+
17
+
18
+ def print_maven_help() -> None:
19
+ """Print helpful Maven commands for generating dependency files."""
20
+ print("\n💡 To generate a Maven dependency file, try one of these commands:")
21
+ print(" mvn dependency:tree -DoutputFile=maven_dependency_file")
22
+ print(" mvn dependency:tree > maven_dependency_file")
23
+ print(" mvn dependency:tree -DoutputFile=maven_dependency_file -DoutputType=text")
24
+ print("\n📍 Make sure you're in a directory with a pom.xml file.")
25
+
26
+
27
+ def validate_directory(directory: str) -> None:
28
+ """Validate that the directory exists and is accessible."""
29
+ if not os.path.exists(directory):
30
+ raise DependencyFileNotFoundError(f"Directory '{directory}' does not exist.\nPlease check the path and try again.")
31
+
32
+ if not os.path.isdir(directory):
33
+ raise DependencyFileNotFoundError(f"'{directory}' is not a directory.\nPlease provide a valid directory path.")
34
+
35
+ if not os.access(directory, os.R_OK):
36
+ raise DependencyFileNotFoundError(f"Cannot read from directory '{directory}'.\nPlease check your permissions and try again.")
37
+
38
+
39
+ def validate_dependency_files(directory: str, filename: str) -> list[str]:
40
+ """Validate that dependency files exist and are readable."""
41
+ validate_directory(directory)
42
+
43
+ dependency_files = find_dependency_files(directory, filename)
44
+
45
+ if not dependency_files:
46
+ abs_directory = os.path.abspath(directory)
47
+ raise DependencyFileNotFoundError(
48
+ f"No '{filename}' files found in '{abs_directory}' or its subdirectories.\n\n"
49
+ f"📂 Searched in: {abs_directory}\n"
50
+ f"🔍 Looking for: {filename}\n"
51
+ f"\n💡 To generate a Maven dependency file, run:"
52
+ f"\n mvn dependency:tree -DoutputFile={filename}"
53
+ f"\n\n📍 Make sure you're in a directory with a pom.xml file."
54
+ )
55
+
56
+ # Validate that files are readable
57
+ unreadable_files = []
58
+ for file_path in dependency_files:
59
+ if not os.access(file_path, os.R_OK):
60
+ unreadable_files.append(file_path)
61
+
62
+ if unreadable_files:
63
+ files_list = "\n".join(f" - {f}" for f in unreadable_files)
64
+ raise DependencyFileNotFoundError(
65
+ f"Found {len(dependency_files)} dependency file(s), but cannot read {len(unreadable_files)} of them:\n"
66
+ f"{files_list}\n\n"
67
+ f"Please check file permissions and try again."
68
+ )
69
+
70
+ return dependency_files
71
+
72
+
73
+ def validate_output_directory(output_file: str) -> None:
74
+ """Validate that the output directory exists and is writable."""
75
+ output_dir = Path(output_file).parent
76
+
77
+ if not output_dir.exists():
78
+ try:
79
+ output_dir.mkdir(parents=True, exist_ok=True)
80
+ except PermissionError:
81
+ raise OutputGenerationError(f"Cannot create output directory '{output_dir}'.\nPlease check your permissions and try again.")
82
+ except Exception as e:
83
+ raise OutputGenerationError(f"Failed to create output directory '{output_dir}': {e}")
84
+
85
+ if not os.access(output_dir, os.W_OK):
86
+ raise OutputGenerationError(f"Cannot write to output directory '{output_dir}'.\nPlease check your permissions and try again.")
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: mvn-tree-visualizer
3
+ Version: 1.3.0
4
+ Summary: A simple command line tool to visualize the dependency tree of a Maven project in a graphical format.
5
+ Project-URL: source, https://github.com/dyka3773/mvn-tree-visualizer
6
+ Author-email: Iraklis Konsoulas <dyka3773@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: cli,command-line,dependency,graph,maven,mermaid,tool,tree,visualization
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Build Tools
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.13
17
+ Requires-Dist: jinja2>=3.1.6
18
+ Requires-Dist: watchdog>=6.0.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Maven Dependency Tree Visualizer
22
+
23
+ [![PyPI version](https://badge.fury.io/py/mvn-tree-visualizer.svg)](https://badge.fury.io/py/mvn-tree-visualizer)
24
+ ![Python](https://img.shields.io/badge/python-3.13+-blue.svg)
25
+ ![License](https://img.shields.io/badge/license-MIT-green.svg)
26
+ [![Downloads](https://pepy.tech/badge/mvn-tree-visualizer)](https://pepy.tech/project/mvn-tree-visualizer)
27
+ [![CI](https://github.com/dyka3773/mvn-tree-visualizer/workflows/CI/badge.svg)](https://github.com/dyka3773/mvn-tree-visualizer/actions)
28
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
29
+
30
+ A simple command-line tool to visualize the dependency tree of a Maven project in a graphical and interactive format.
31
+
32
+ This tool was born out of the frustration of not being able to easily visualize the dependency tree of a Maven project. The `mvn dependency:tree` command is great, but the output can be hard to read, especially for large projects. This tool aims to solve that problem by providing a simple way to generate an interactive diagram or a structured JSON output of the dependency tree.
33
+
34
+ ## Table of Contents
35
+ - [Features](#features)
36
+ - [Installation](#installation)
37
+ - [How to Use](#how-to-use)
38
+ - [Examples](#examples)
39
+ - [Options](#options)
40
+ - [Performance](#performance)
41
+ - [Troubleshooting](#troubleshooting)
42
+ - [Contributing](#contributing)
43
+ - [License](#license)
44
+
45
+ ## Installation
46
+
47
+ Install the package from PyPI:
48
+
49
+ ```bash
50
+ pip install mvn-tree-visualizer
51
+ ```
52
+
53
+ ## Features
54
+
55
+ - **🌐 Multiple Output Formats:**
56
+ - **HTML:** Generates an interactive HTML diagram of your dependency tree using Mermaid.js.
57
+ - **JSON:** Creates a structured JSON representation of the dependency tree, perfect for scripting or integration with other tools.
58
+ - **🔄 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.
64
+
65
+ ## How to Use
66
+
67
+ ### Step 1: Generate the dependency file
68
+
69
+ Run the following command in your terminal at the root of your Maven project. This will generate a file named `maven_dependency_file` in each module's `target` directory.
70
+
71
+ ```bash
72
+ mvn dependency:tree -DoutputFile=maven_dependency_file -DappendOutput=true
73
+ ```
74
+
75
+ > **💡 Tip:** You can add other options like `-Dincludes="org.example"` to filter the dependencies.
76
+
77
+ ### Step 2: Visualize the dependency tree
78
+
79
+ Use the `mvn-tree-visualizer` command to generate the diagram.
80
+
81
+ #### HTML Output (Interactive Diagram)
82
+ ```bash
83
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --format html
84
+ ```
85
+
86
+ #### JSON Output (Structured Data)
87
+ ```bash
88
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "dependencies.json" --format json
89
+ ```
90
+
91
+ #### With Version Information
92
+ ```bash
93
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --show-versions
94
+ ```
95
+
96
+ #### JSON Output with Versions
97
+ ```bash
98
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "dependencies.json" --format json --show-versions
99
+ ```
100
+
101
+ #### Watch Mode (Auto-regeneration)
102
+ ```bash
103
+ mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html" --watch
104
+ ```
105
+
106
+ > **💡 Tip:** In watch mode, the tool will monitor for changes to your Maven dependency files and automatically regenerate the diagram. Perfect for development workflows! Press `Ctrl+C` to stop watching.
107
+
108
+ ### Step 3: View the output
109
+
110
+ - **HTML:** Open the generated `diagram.html` file in your web browser to view the interactive dependency tree.
111
+ - **JSON:** Use the `dependencies.json` file in your scripts or other tools.
112
+
113
+ ## Examples
114
+
115
+ Check out the [`examples/`](examples/) directory for sample Maven dependency files and their outputs:
116
+
117
+ - **Simple Project**: Basic Spring Boot application with common dependencies
118
+ - **Complex Project**: Realistic microservice with comprehensive dependencies
119
+
120
+ Each example includes:
121
+ - Sample Maven dependency tree file
122
+ - Generated HTML and JSON outputs
123
+ - Usage instructions
124
+
125
+ ## Options
126
+
127
+ | Option | Description | Default |
128
+ |--------|-------------|---------|
129
+ | `--filename` | The name of the file containing the Maven dependency tree | `maven_dependency_file` |
130
+ | `--output` | The name of the output file | `diagram.html` |
131
+ | `--format` | The output format (`html` or `json`) | `html` |
132
+ | `--show-versions` | Show dependency versions in the diagram | `False` |
133
+ | `--watch` | Watch for file changes and auto-regenerate diagram | `False` |
134
+ | `--directory` | The directory to scan for the Maven dependency file(s) | current directory |
135
+ | `--keep-tree` | Keep the intermediate `dependency_tree.txt` file | `False` |
136
+ | `--help` | Show the help message and exit | - |
137
+
138
+ ## Performance
139
+
140
+ **For Large Projects:**
141
+ - Consider filtering dependencies at the Maven level using `-Dincludes` or `-Dexcludes` parameters
142
+ - The tool can handle projects with hundreds of dependencies efficiently
143
+
144
+ **Memory Usage:**
145
+ - Memory usage scales with the number of dependencies
146
+ - Typical projects (50-200 dependencies) use minimal memory
147
+ - Very large projects (1000+ dependencies) may require more memory
148
+
149
+ ## Troubleshooting
150
+
151
+ ### Common Issues
152
+
153
+ **"No dependency files found"**
154
+ - The tool now provides detailed guidance including:
155
+ - Exact directory searched and filename expected
156
+ - Maven commands to generate dependency files
157
+ - Instructions to ensure you're in a directory with pom.xml
158
+
159
+ **"Empty or invalid output"**
160
+ - Enhanced error messages now include:
161
+ - Specific error details (encoding, permissions, empty files)
162
+ - Validation of file content and format
163
+ - Suggestions for fixing common parsing issues
164
+
165
+ **"Browser doesn't display the diagram"**
166
+ - Ensure you're opening the HTML file in a modern browser
167
+ - Check browser console for JavaScript errors
168
+ - Try a different browser (Chrome, Firefox, Safari)
169
+
170
+ **"Permission denied errors"**
171
+ - Improved diagnostics for:
172
+ - Directory read/write permissions
173
+ - File access issues
174
+ - Output directory creation problems
175
+
176
+ ### Getting Help
177
+
178
+ - Check the [examples](examples/) directory for working samples
179
+ - Review the [issues](https://github.com/dyka3773/mvn-tree-visualizer/issues) page
180
+ - Create a new issue with your Maven dependency file sample
181
+
182
+ ## Contributing
183
+
184
+ Contributions are welcome! If you have any ideas, suggestions, or bug reports, please open an issue or submit a pull request.
185
+
186
+ Please read our [CONTRIBUTING.md](CONTRIBUTING.md) file for more details.
187
+
188
+ ## License
189
+
190
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,16 @@
1
+ mvn_tree_visualizer/TEMPLATE.py,sha256=WIQfSNBygUZVkBrERq7QzqouGURA0NYVqUUm-11wMvo,2499
2
+ mvn_tree_visualizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ mvn_tree_visualizer/__main__.py,sha256=yIQFAdWjthKAFbSzzRuz5_YGlK0c6BnR2ypjNRDq180,82
4
+ mvn_tree_visualizer/cli.py,sha256=33K5ujgB85xejcSUKMNAwirpLfnGMGfrnhqyLFOy69k,8219
5
+ mvn_tree_visualizer/diagram.py,sha256=UfvP_J4Im4JQLe3EWlY3TsP4tua3oYk5NiCGbZNQwoA,933
6
+ mvn_tree_visualizer/exceptions.py,sha256=R4nnJ0xrOpd84GfPD9rFSDk40etDLoph7iZpj1CCR0c,543
7
+ mvn_tree_visualizer/file_watcher.py,sha256=JtmV1KW08_Az-XqpKhcd342WpxV1vUW-Dge9lodjjJY,2284
8
+ mvn_tree_visualizer/get_dependencies_in_one_file.py,sha256=nXhEhU-dI7tXa3TpoW1pv2t86t1K0yppSw8FYDtmTlQ,1973
9
+ 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/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,,
@@ -1,71 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mvn-tree-visualizer
3
- Version: 1.1.0
4
- Summary: A simple command line tool to visualize the dependency tree of a Maven project in a graphical format.
5
- Project-URL: source, https://github.com/dyka3773/mvn-tree-visualizer
6
- Author-email: Iraklis Konsoulas <dyka3773@gmail.com>
7
- License-Expression: MIT
8
- License-File: LICENSE
9
- Keywords: cli,command-line,dependency,graph,maven,mermaid,tool,tree,visualization
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.13
14
- Classifier: Topic :: Software Development :: Build Tools
15
- Requires-Python: >=3.13
16
- Requires-Dist: jinja2>=3.1.6
17
- Description-Content-Type: text/markdown
18
-
19
- # Maven Dependency Tree Visualizer
20
-
21
- [![PyPI version](https://badge.fury.io/py/mvn-tree-visualizer.svg)](https://badge.fury.io/py/mvn-tree-visualizer)
22
-
23
- A simple command-line tool to visualize the dependency tree of a Maven project in a graphical and interactive format.
24
-
25
- This tool was born out of the frustration of not being able to easily visualize the dependency tree of a Maven project. The `mvn dependency:tree` command is great, but the output can be hard to read, especially for large projects. This tool aims to solve that problem by providing a simple way to generate an interactive diagram of the dependency tree.
26
-
27
- ## Features
28
-
29
- * **Interactive Diagrams:** Generates an interactive HTML diagram of your dependency tree using Mermaid.js.
30
- * **Easy to Use:** A simple command-line interface that gets the job done with minimal configuration.
31
- * **File Merging:** Automatically finds and merges multiple `maven_dependency_file` files from different subdirectories.
32
- * **Customizable Output:** Specify the output file name and location.
33
- * **SVG Export:** Download the generated diagram as an SVG file directly from the HTML page.
34
-
35
- ## How to Use
36
-
37
- 1. **Generate the dependency file:**
38
- Run the following command in your terminal at the root of your Maven project. This will generate a file named `maven_dependency_file` in each module's `target` directory.
39
-
40
- ```bash
41
- mvn dependency:tree -DoutputFile=maven_dependency_file -DappendOutput=true
42
- ```
43
- > You can add other options like `-Dincludes="org.example"` to filter the dependencies.
44
-
45
- 2. **Visualize the dependency tree:**
46
- Use the `mvn-tree-visualizer` command to generate the diagram.
47
-
48
- ```bash
49
- mvn_tree_visualizer --filename "maven_dependency_file" --output "diagram.html"
50
- ```
51
-
52
- 3. **View the diagram:**
53
- Open the generated `diagram.html` file in your web browser to view the interactive dependency tree.
54
-
55
- ## Options
56
-
57
- * `--filename`: The name of the file containing the Maven dependency tree. Defaults to `maven_dependency_file`.
58
- * `--output`: The name of the output HTML file. Defaults to `diagram.html`.
59
- * `--directory`: The directory to scan for the Maven dependency file(s). Defaults to the current directory.
60
- * `--keep-tree`: Keep the intermediate `dependency_tree.txt` file after generating the diagram. Defaults to `False`.
61
- * `--help`: Show the help message and exit.
62
-
63
- ## Contributing
64
-
65
- Contributions are welcome! If you have any ideas, suggestions, or bug reports, please open an issue or submit a pull request.
66
-
67
- Please read our [CONTRIBUTING.md](CONTRIBUTING.md) file for more details.
68
-
69
- ## License
70
-
71
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -1,13 +0,0 @@
1
- mvn_tree_visualizer/TEMPLATE.py,sha256=WIQfSNBygUZVkBrERq7QzqouGURA0NYVqUUm-11wMvo,2499
2
- mvn_tree_visualizer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- mvn_tree_visualizer/__main__.py,sha256=yIQFAdWjthKAFbSzzRuz5_YGlK0c6BnR2ypjNRDq180,82
4
- mvn_tree_visualizer/cli.py,sha256=P3v27Axod-aYkhtnoAh06RnScUTR2k9QZ3vVGkolHg8,2451
5
- mvn_tree_visualizer/diagram.py,sha256=elUQHCFu9DPeCpcLkjlRg-xl-fAEn8TvMWpIho1vo0E,312
6
- mvn_tree_visualizer/get_dependencies_in_one_file.py,sha256=sX669Lo3gsv-dTvaf4PNSQMTp3Gx3CKwqBmJQFiQF04,504
7
- mvn_tree_visualizer/outputs/html_output.py,sha256=rCD55g8aUdYiPs2Mkd9VB0dBKvMZu0Va3uYGZkJxO20,2295
8
- mvn_tree_visualizer/outputs/json_output.py,sha256=v9aRPaCDMG-zyVdg82YPhPRyFCNLaWXPcdRmnpzuajg,1554
9
- mvn_tree_visualizer-1.1.0.dist-info/METADATA,sha256=VmIy1sZ1XHWlqnXJwzFKuG1Z3yxqZ2YV7OlEPaNvIuM,3470
10
- mvn_tree_visualizer-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- mvn_tree_visualizer-1.1.0.dist-info/entry_points.txt,sha256=Mu3QZhrlvbYuCxqmluVGi2efgKjkQY6T8Opf-vdb7hU,68
12
- mvn_tree_visualizer-1.1.0.dist-info/licenses/LICENSE,sha256=4zi6unpe17RUDMBu7ebh14jdbyvyeT-UA3n8Zl7aW74,1075
13
- mvn_tree_visualizer-1.1.0.dist-info/RECORD,,