src/eric7/PipInterface/pipdeptree.py

branch
eric7
changeset 9849
99782ca569ed
parent 9653
e67609152c5e
child 10026
617290a049f0
equal deleted inserted replaced
9848:3d750b2e012c 9849:99782ca569ed
44 import tempfile 44 import tempfile
45 from collections import defaultdict, deque 45 from collections import defaultdict, deque
46 from collections.abc import Mapping 46 from collections.abc import Mapping
47 from importlib import import_module 47 from importlib import import_module
48 from itertools import chain 48 from itertools import chain
49 from textwrap import dedent
49 50
50 from pip._vendor import pkg_resources 51 from pip._vendor import pkg_resources
51 52
52 try: 53 try:
53 from pip._internal.operations.freeze import FrozenRequirement 54 from pip._internal.operations.freeze import FrozenRequirement
54 except ImportError: 55 except ImportError:
55 from pip import FrozenRequirement 56 from pip import FrozenRequirement
56 57
57 58
58 __version__ = '2.3.3' # eric-ide modification: from version.py 59 __version__ = '2.5.2' # eric-ide modification: from version.py
59 60
60 61
61 flatten = chain.from_iterable 62 flatten = chain.from_iterable
62 63
63 64
588 return d 589 return d
589 590
590 return json.dumps([aux(p) for p in nodes], indent=indent) 591 return json.dumps([aux(p) for p in nodes], indent=indent)
591 592
592 593
594 def render_mermaid(tree) -> str:
595 """Produce a Mermaid flowchart from the dependency graph.
596
597 :param dict tree: dependency graph
598 """
599 # List of reserved keywords in Mermaid that cannot be used as node names.
600 # See: https://github.com/mermaid-js/mermaid/issues/4182#issuecomment-1454787806
601 reserved_ids: set[str] = {
602 "C4Component",
603 "C4Container",
604 "C4Deployment",
605 "C4Dynamic",
606 "_blank",
607 "_parent",
608 "_self",
609 "_top",
610 "call",
611 "class",
612 "classDef",
613 "click",
614 "end",
615 "flowchart",
616 "flowchart-v2",
617 "graph",
618 "interpolate",
619 "linkStyle",
620 "style",
621 "subgraph",
622 }
623 node_ids_map: dict[str:str] = {}
624
625 def mermaid_id(key: str) -> str:
626 """Returns a valid Mermaid node ID from a string."""
627 # If we have already seen this key, return the canonical ID.
628 canonical_id = node_ids_map.get(key)
629 if canonical_id is not None:
630 return canonical_id
631 # If the key is not a reserved keyword, return it as is, and update the map.
632 if key not in reserved_ids:
633 node_ids_map[key] = key
634 return key
635 # If the key is a reserved keyword, append a number to it.
636 number = 0
637 while True:
638 new_id = f"{key}_{number}"
639 if new_id not in node_ids_map:
640 node_ids_map[key] = new_id
641 return new_id
642 number += 1
643
644 # Use a sets to avoid duplicate entries.
645 nodes: set[str] = set()
646 edges: set[str] = set()
647
648 for pkg, deps in tree.items():
649 pkg_label = f"{pkg.project_name}\\n{pkg.version}"
650 pkg_key = mermaid_id(pkg.key)
651 nodes.add(f'{pkg_key}["{pkg_label}"]')
652 for dep in deps:
653 edge_label = dep.version_spec or "any"
654 dep_key = mermaid_id(dep.key)
655 if dep.is_missing:
656 dep_label = f"{dep.project_name}\\n(missing)"
657 nodes.add(f'{dep_key}["{dep_label}"]:::missing')
658 edges.add(f"{pkg_key} -.-> {dep_key}")
659 else:
660 edges.add(f'{pkg_key} -- "{edge_label}" --> {dep_key}')
661
662 # Produce the Mermaid Markdown.
663 indent = " " * 4
664 output = dedent(
665 f"""\
666 flowchart TD
667 {indent}classDef missing stroke-dasharray: 5
668 """
669 )
670 # Sort the nodes and edges to make the output deterministic.
671 output += indent
672 output += f"\n{indent}".join(node for node in sorted(nodes))
673 output += "\n" + indent
674 output += f"\n{indent}".join(edge for edge in sorted(edges))
675 output += "\n"
676 return output
677
678
593 def dump_graphviz(tree, output_format="dot", is_reverse=False): 679 def dump_graphviz(tree, output_format="dot", is_reverse=False):
594 """Output dependency graph as one of the supported GraphViz output formats. 680 """Output dependency graph as one of the supported GraphViz output formats.
595 681
596 :param dict tree: dependency graph 682 :param dict tree: dependency graph
597 :param string output_format: output format 683 :param string output_format: output format
650 edge_label = req_ref.version_spec or "any" 736 edge_label = req_ref.version_spec or "any"
651 graph.edge(dep.key, parent.key, label=edge_label) 737 graph.edge(dep.key, parent.key, label=edge_label)
652 738
653 # Allow output of dot format, even if GraphViz isn't installed. 739 # Allow output of dot format, even if GraphViz isn't installed.
654 if output_format == "dot": 740 if output_format == "dot":
655 return graph.source 741 # Emulates graphviz.dot.Dot.__iter__() to force the sorting of graph.body.
742 # Fixes https://github.com/tox-dev/pipdeptree/issues/188
743 # That way we can guarantee the output of the dot format is deterministic
744 # and stable.
745 return "".join([tuple(graph)[0]] + sorted(graph.body) + [graph._tail])
656 746
657 # As it's unknown if the selected output format is binary or not, try to 747 # As it's unknown if the selected output format is binary or not, try to
658 # decode it as UTF8 and only print it out in binary if that's not possible. 748 # decode it as UTF8 and only print it out in binary if that's not possible.
659 try: 749 try:
660 return graph.pipe().decode("utf-8") 750 return graph.pipe().decode("utf-8")
810 "the same way as the plain text output printed by default. " 900 "the same way as the plain text output printed by default. "
811 "This option overrides all other options (except --json)." 901 "This option overrides all other options (except --json)."
812 ), 902 ),
813 ) 903 )
814 parser.add_argument( 904 parser.add_argument(
905 "--mermaid",
906 action="store_true",
907 default=False,
908 help=("Display dependency tree as a Maermaid graph. " "This option overrides all other options."),
909 )
910 parser.add_argument(
815 "--graph-output", 911 "--graph-output",
816 dest="output_format", 912 dest="output_format",
817 help=( 913 help=(
818 "Print a dependency graph in the specified output " 914 "Print a dependency graph in the specified output "
819 "format. Available are all formats supported by " 915 "format. Available are all formats supported by "
918 1014
919 if args.json: 1015 if args.json:
920 print(render_json(tree, indent=4)) 1016 print(render_json(tree, indent=4))
921 elif args.json_tree: 1017 elif args.json_tree:
922 print(render_json_tree(tree, indent=4)) 1018 print(render_json_tree(tree, indent=4))
1019 elif args.mermaid:
1020 print(render_mermaid(tree))
923 elif args.output_format: 1021 elif args.output_format:
924 output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse) 1022 output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse)
925 print_graphviz(output) 1023 print_graphviz(output)
926 else: 1024 else:
927 render_text(tree, args.all, args.freeze) 1025 render_text(tree, args.all, args.freeze)

eric ide

mercurial