|
10 | 10 | from collections.abc import Mapping
|
11 | 11 | from importlib import import_module
|
12 | 12 | from itertools import chain
|
| 13 | +from textwrap import dedent |
13 | 14 |
|
14 | 15 | from pip._vendor import pkg_resources
|
15 | 16 |
|
@@ -550,6 +551,44 @@ def aux(node, parent=None, cur_chain=None):
|
550 | 551 | return json.dumps([aux(p) for p in nodes], indent=indent)
|
551 | 552 |
|
552 | 553 |
|
| 554 | +def render_mermaid(tree) -> str: |
| 555 | + """Produce a Mermaid flowchart from the dependency graph. |
| 556 | +
|
| 557 | + :param dict tree: dependency graph |
| 558 | + """ |
| 559 | + # Use a sets to avoid duplicate entries. |
| 560 | + nodes: set[str] = set() |
| 561 | + edges: set[str] = set() |
| 562 | + |
| 563 | + for pkg, deps in tree.items(): |
| 564 | + pkg_label = f"{pkg.project_name}\\n{pkg.version}" |
| 565 | + nodes.add(f"{pkg.key}[{pkg_label}]") |
| 566 | + for dep in deps: |
| 567 | + edge_label = dep.version_spec or "any" |
| 568 | + if dep.is_missing: |
| 569 | + dep_label = f"{dep.project_name}\\n(missing)" |
| 570 | + nodes.add(f"{dep.key}[{dep_label}]:::missing") |
| 571 | + edges.add(f"{pkg.key} -.-> {dep.key}") |
| 572 | + else: |
| 573 | + edges.add(f"{pkg.key} -- {edge_label} --> {dep.key}") |
| 574 | + |
| 575 | + # Produce the Mermaid Markdown. |
| 576 | + indent = " " * 4 |
| 577 | + output = dedent( |
| 578 | + f"""\ |
| 579 | + flowchart TD |
| 580 | + {indent}classDef missing stroke-dasharray: 5 |
| 581 | + """ |
| 582 | + ) |
| 583 | + # Sort the nodes and edges to make the output deterministic. |
| 584 | + output += indent |
| 585 | + output += f"\n{indent}".join(node for node in sorted(nodes)) |
| 586 | + output += "\n" + indent |
| 587 | + output += f"\n{indent}".join(edge for edge in sorted(edges)) |
| 588 | + output += "\n" |
| 589 | + return output |
| 590 | + |
| 591 | + |
553 | 592 | def dump_graphviz(tree, output_format="dot", is_reverse=False):
|
554 | 593 | """Output dependency graph as one of the supported GraphViz output formats.
|
555 | 594 |
|
@@ -775,6 +814,12 @@ def get_parser():
|
775 | 814 | "This option overrides all other options (except --json)."
|
776 | 815 | ),
|
777 | 816 | )
|
| 817 | + parser.add_argument( |
| 818 | + "--mermaid", |
| 819 | + action="store_true", |
| 820 | + default=False, |
| 821 | + help=("Display dependency tree as a Maermaid graph. " "This option overrides all other options."), |
| 822 | + ) |
778 | 823 | parser.add_argument(
|
779 | 824 | "--graph-output",
|
780 | 825 | dest="output_format",
|
@@ -884,6 +929,8 @@ def main():
|
884 | 929 | print(render_json(tree, indent=4))
|
885 | 930 | elif args.json_tree:
|
886 | 931 | print(render_json_tree(tree, indent=4))
|
| 932 | + elif args.output_format: |
| 933 | + print(render_mermaid(tree)) |
887 | 934 | elif args.output_format:
|
888 | 935 | output = dump_graphviz(tree, output_format=args.output_format, is_reverse=args.reverse)
|
889 | 936 | print_graphviz(output)
|
|
0 commit comments