Skip to content

Commit 85c3859

Browse files
committed
feat: script to find minimum edge cut between two files in the include graph
1 parent 18f5ecc commit 85c3859

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

minimum_edge_cut.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import csv
5+
import logging
6+
import os
7+
import sys
8+
9+
import networkx as nx
10+
11+
from include_analysis import IncludeAnalysisOutput, ParseError, parse_raw_include_analysis_output
12+
from typing import Iterator, Tuple
13+
from utils import (
14+
create_graph_from_include_analysis,
15+
get_latest_include_analysis,
16+
)
17+
18+
19+
def minimum_edge_cut(
20+
include_analysis: IncludeAnalysisOutput,
21+
source: str,
22+
target: str,
23+
) -> Iterator[Tuple[str, str]]:
24+
files = include_analysis["files"]
25+
DG: nx.DiGraph = create_graph_from_include_analysis(include_analysis)
26+
27+
edge_cut = nx.minimum_edge_cut(DG, files.index(source), files.index(target))
28+
29+
for includer_idx, include_idx in edge_cut:
30+
includer = files[includer_idx]
31+
included = files[include_idx]
32+
33+
yield includer, included
34+
35+
36+
def main():
37+
parser = argparse.ArgumentParser(description="Find the minimum edge cut between two files")
38+
parser.add_argument(
39+
"include_analysis_output",
40+
type=argparse.FileType("r"),
41+
nargs="?",
42+
help="The include analysis output to use.",
43+
)
44+
parser.add_argument("source", help="Source file.")
45+
parser.add_argument("target", help="Target file.")
46+
parser.add_argument("--verbose", action="store_true", default=False, help="Enable verbose logging.")
47+
args = parser.parse_args()
48+
49+
# If the user specified an include analysis output file, use that instead of fetching it
50+
if args.include_analysis_output:
51+
raw_include_analysis = args.include_analysis_output.read()
52+
else:
53+
raw_include_analysis = get_latest_include_analysis()
54+
55+
try:
56+
include_analysis = parse_raw_include_analysis_output(raw_include_analysis)
57+
except ParseError as e:
58+
message = str(e)
59+
print("error: Could not parse include analysis output file")
60+
if message:
61+
print(message)
62+
return 2
63+
64+
if args.verbose:
65+
logging.basicConfig(level=logging.DEBUG)
66+
67+
csv_writer = csv.writer(sys.stdout)
68+
69+
if args.source not in include_analysis["files"]:
70+
print(f"error: {args.source} is not a known file")
71+
return 1
72+
73+
if args.target not in include_analysis["files"]:
74+
print(f"error: {args.target} is not a known file")
75+
return 1
76+
77+
try:
78+
for row in minimum_edge_cut(
79+
include_analysis,
80+
args.source,
81+
args.target,
82+
):
83+
csv_writer.writerow(row)
84+
85+
sys.stdout.flush()
86+
except nx.exception.NetworkXNoPath:
87+
print(f"error: no transitive include path from {args.filename} to {args.header}")
88+
return 3
89+
except BrokenPipeError:
90+
devnull = os.open(os.devnull, os.O_WRONLY)
91+
os.dup2(devnull, sys.stdout.fileno())
92+
sys.exit(1)
93+
94+
return 0
95+
96+
97+
if __name__ == "__main__":
98+
try:
99+
sys.exit(main())
100+
except KeyboardInterrupt:
101+
pass # Don't show the user anything

0 commit comments

Comments
 (0)