Skip to content

Instantly share code, notes, and snippets.

@weixinfree
Last active May 12, 2022 02:54
Show Gist options
  • Save weixinfree/ac3cf44b3b96fb95fa76c330fe3dc983 to your computer and use it in GitHub Desktop.
Save weixinfree/ac3cf44b3b96fb95fa76c330fe3dc983 to your computer and use it in GitHub Desktop.
快速的分析一个复杂模块的依赖关系 并且 可视化
#! /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