Last active
May 12, 2022 02:54
-
-
Save weixinfree/ac3cf44b3b96fb95fa76c330fe3dc983 to your computer and use it in GitHub Desktop.
快速的分析一个复杂模块的依赖关系 并且 可视化
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python3 | |
""" | |
Prerequirements: | |
1. brew install graphviz | |
2. pip3 install fire | |
3. pip3 install simpletemplate | |
Usage: | |
deps [path to code] | |
""" | |
import fire | |
from pathlib import Path | |
import re | |
import os | |
from pprint import pprint | |
from typing import Dict, Iterable, List | |
from simpletemplate import Template | |
from subprocess import check_call | |
from concurrent.futures import ThreadPoolExecutor | |
from time import time | |
DEFAULT_EXLUDE = r"(accessids|logtag|widget|listener|response|layout|view|model|utils|util|helper|logger|pagelist|constant|constants|api|config|type|wrapper|source|item|reason|dialog|object|info|name|status|data|apiservice|drawable|callback|viewV\d+|delegate)$" | |
ENGINE = ["dot", "neato", "twopi", "circo", "osage", "fdp", "sfdp"] | |
TEMPLATE = """\ | |
digraph Deps { | |
node [color=lightblue2, style=filled]; | |
%{for node in deps.keys():}% | |
{{node}}; | |
%{end}% | |
%{for node, values in deps.items():}% | |
%{for v in values:}% | |
{{node}} -> {{v}}; | |
%{end}% | |
%{end}% | |
}\ | |
""" | |
def _parse_file(file: Path): | |
content = file.read_text() | |
return file.stem, set(re.findall(r"\b[A-Z$][\w]+\b", content)) | |
def _files(root: Path) -> Iterable[Path]: | |
for d, _, files in os.walk(root): | |
yield from (Path(os.path.join(d, f)) for f in files) | |
def _should_exlude(file: Path, exclude: str): | |
r = re.search(exclude, file.stem, re.I) | |
if r: | |
print(f"exlude: {file.stem} {r}") | |
return r | |
def main(path: str, exclude: str = DEFAULT_EXLUDE, engine: str = "dot"): | |
dot = Path(path).stem | |
t1 = time() | |
files = _files(Path(path)) | |
if exclude: | |
files = (f for f in files if not _should_exlude(f, exclude=exclude)) | |
t2 = time() | |
print(f">>> 1. scan files finishd, cost: {t2 - t1:.2f}s") | |
with ThreadPoolExecutor() as pool: | |
_deps = dict(pool.map(_parse_file, list(files))) | |
# _deps = dict(_parse_file(f) for f in files) | |
valid_keys = set(_deps.keys()) | |
deps = { | |
k: [v for v in values if v in valid_keys and v != k] | |
for k, values in _deps.items() | |
} | |
t3 = time() | |
print(f">>> 2. parse files finishd, cost: {t3 - t2:.2f}s") | |
code = Template(TEMPLATE).render({"deps": deps}) | |
with open(f"{dot}.dot", "w") as f: | |
f.write(code) | |
check_call(f"{engine} -Tpng {dot}.dot -o {dot}.png && open {dot}.png", shell=True) | |
t4 = time() | |
print(f">>> 3. render finishd, cost: {t4 - t3:.2f}s, total: {t4 - t1:.2f}s") | |
if __name__ == "__main__": | |
fire.Fire(main) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment