anduin revised this gist 10 months ago. Go to revision
1 file changed, 111 insertions, 5 deletions
visualize.py
| @@ -5,6 +5,108 @@ from concurrent.futures import ThreadPoolExecutor | |||
| 5 | 5 | import threading | |
| 6 | 6 | import os | |
| 7 | 7 | import sys | |
| 8 | + | import webbrowser | |
| 9 | + | import urllib.request | |
| 10 | + | ||
| 11 | + | MERMAID_TEMPLATE = """ | |
| 12 | + | <!DOCTYPE html> | |
| 13 | + | <html> | |
| 14 | + | <head> | |
| 15 | + | <meta charset="UTF-8"> | |
| 16 | + | <script src="mermaid.min.js"></script> | |
| 17 | + | <script src="panzoom.min.js"></script> | |
| 18 | + | <script> | |
| 19 | + | document.addEventListener("DOMContentLoaded", () => {{ | |
| 20 | + | mermaid.initialize({{ startOnLoad: true }}); | |
| 21 | + | ||
| 22 | + | mermaid.contentLoaded().then(() => {{ | |
| 23 | + | const svg = document.querySelector(".mermaid > svg"); | |
| 24 | + | if (svg) {{ | |
| 25 | + | const panzoom = Panzoom(svg, {{ maxScale: 5, minScale: 0.5 }}); | |
| 26 | + | svg.parentElement.addEventListener('wheel', panzoom.zoomWithWheel); | |
| 27 | + | }} else {{ | |
| 28 | + | console.error("Mermaid SVG not found for Panzoom initialization."); | |
| 29 | + | }} | |
| 30 | + | }}); | |
| 31 | + | }}); | |
| 32 | + | </script> | |
| 33 | + | <style> | |
| 34 | + | body {{ | |
| 35 | + | margin: 0; | |
| 36 | + | overflow: hidden; | |
| 37 | + | display: flex; | |
| 38 | + | justify-content: center; | |
| 39 | + | align-items: center; | |
| 40 | + | height: 100vh; | |
| 41 | + | background-color: #f4f4f4; | |
| 42 | + | }} | |
| 43 | + | #diagram-container {{ | |
| 44 | + | width: 100%; | |
| 45 | + | height: 100%; | |
| 46 | + | overflow: hidden; | |
| 47 | + | position: relative; | |
| 48 | + | }} | |
| 49 | + | .mermaid {{ | |
| 50 | + | width: 100%; | |
| 51 | + | height: 100%; | |
| 52 | + | }} | |
| 53 | + | </style> | |
| 54 | + | </head> | |
| 55 | + | <body> | |
| 56 | + | <div id="diagram-container"> | |
| 57 | + | <div class="mermaid"> | |
| 58 | + | {graph_content} | |
| 59 | + | </div> | |
| 60 | + | </div> | |
| 61 | + | </body> | |
| 62 | + | </html> | |
| 63 | + | """ | |
| 64 | + | ||
| 65 | + | ||
| 66 | + | def ensure_local_files(): | |
| 67 | + | """Ensure Mermaid.js and Panzoom.js are available locally.""" | |
| 68 | + | js_files = { | |
| 69 | + | "mermaid.min.js": "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js", | |
| 70 | + | "panzoom.min.js": "https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js", | |
| 71 | + | } | |
| 72 | + | tmp_dir = "/tmp" | |
| 73 | + | ||
| 74 | + | for filename, url in js_files.items(): | |
| 75 | + | file_path = os.path.join(tmp_dir, filename) | |
| 76 | + | if not os.path.exists(file_path): | |
| 77 | + | print(f"Downloading {filename}...") | |
| 78 | + | urllib.request.urlretrieve(url, file_path) | |
| 79 | + | ||
| 80 | + | def preview_mermaid_graph(file_path): | |
| 81 | + | """Preview Mermaid graph in a browser.""" | |
| 82 | + | ensure_local_files() | |
| 83 | + | ||
| 84 | + | if not os.path.exists(file_path): | |
| 85 | + | print(f"File {file_path} does not exist.") | |
| 86 | + | return | |
| 87 | + | ||
| 88 | + | # Load the Mermaid graph content | |
| 89 | + | with open(file_path, "r") as f: | |
| 90 | + | graph_content = f.read() | |
| 91 | + | ||
| 92 | + | # Prepare the HTML file content | |
| 93 | + | html_content = MERMAID_TEMPLATE.format(graph_content=graph_content) | |
| 94 | + | ||
| 95 | + | # Write the HTML to a temporary file | |
| 96 | + | html_file_path = f"{file_path}.html" | |
| 97 | + | with open(html_file_path, "w") as html_file: | |
| 98 | + | html_file.write(html_content) | |
| 99 | + | ||
| 100 | + | # Copy JS files to the same directory | |
| 101 | + | for js_file in ["mermaid.min.js", "panzoom.min.js"]: | |
| 102 | + | src = os.path.join("/tmp", js_file) | |
| 103 | + | dst = os.path.join(os.path.dirname(html_file_path), js_file) | |
| 104 | + | if not os.path.exists(dst): | |
| 105 | + | subprocess.run(["cp", src, dst]) | |
| 106 | + | ||
| 107 | + | # Open the HTML file in the default web browser | |
| 108 | + | print(f"Opening {html_file_path} in the browser...") | |
| 109 | + | webbrowser.open(f"file://{os.path.abspath(html_file_path)}", new=2) | |
| 8 | 110 | ||
| 9 | 111 | def list_installed_packages(): | |
| 10 | 112 | """Retrieve a list of all installed packages.""" | |
| @@ -34,7 +136,7 @@ def build_dependency_graph(packages): | |||
| 34 | 136 | """Build a dependency graph for the packages and save it to a file.""" | |
| 35 | 137 | graph = defaultdict(list) | |
| 36 | 138 | lock = threading.Lock() | |
| 37 | - | ||
| 139 | + | ||
| 38 | 140 | def process_package(package): | |
| 39 | 141 | dependencies = get_package_dependencies(package) | |
| 40 | 142 | with lock: | |
| @@ -115,13 +217,15 @@ def depends_mode(package, exclude_leaves): | |||
| 115 | 217 | print("Generating dependency graph...") | |
| 116 | 218 | mermaid_graph = generate_mermaid_graph(graph, package, exclude_leaves) | |
| 117 | 219 | ||
| 118 | - | with open(f"{package}_depends.mmd", "w") as f: | |
| 220 | + | output_file = f"{package}_depends.mmd" | |
| 221 | + | with open(output_file, "w") as f: | |
| 119 | 222 | f.write("---\n") | |
| 120 | 223 | f.write(f"title: {package} Dependency Graph\n") | |
| 121 | 224 | f.write("---\n\n") | |
| 122 | 225 | f.write(mermaid_graph) | |
| 123 | 226 | ||
| 124 | - | print(f"Dependency graph generated and saved as {package}_depends.mmd") | |
| 227 | + | print(f"Dependency graph generated and saved as {output_file}") | |
| 228 | + | preview_mermaid_graph(output_file) | |
| 125 | 229 | ||
| 126 | 230 | def rdepends_mode(package, exclude_leaves): | |
| 127 | 231 | _, reverse_graph = load_dependency_graph() | |
| @@ -132,13 +236,15 @@ def rdepends_mode(package, exclude_leaves): | |||
| 132 | 236 | print("Generating reverse dependency graph...") | |
| 133 | 237 | mermaid_graph = generate_mermaid_graph(reverse_graph, package, exclude_leaves) | |
| 134 | 238 | ||
| 135 | - | with open(f"{package}_rdepends.mmd", "w") as f: | |
| 239 | + | output_file = f"{package}_rdepends.mmd" | |
| 240 | + | with open(output_file, "w") as f: | |
| 136 | 241 | f.write("---\n") | |
| 137 | 242 | f.write(f"title: {package} Reverse Dependency Graph\n") | |
| 138 | 243 | f.write("---\n\n") | |
| 139 | 244 | f.write(mermaid_graph) | |
| 140 | 245 | ||
| 141 | - | print(f"Reverse dependency graph generated and saved as {package}_rdepends.mmd") | |
| 246 | + | print(f"Reverse dependency graph generated and saved as {output_file}") | |
| 247 | + | preview_mermaid_graph(output_file) | |
| 142 | 248 | ||
| 143 | 249 | def main(): | |
| 144 | 250 | if len(sys.argv) < 2: | |
anduin revised this gist 10 months ago. Go to revision
1 file changed, 129 insertions, 58 deletions
visualize.py
| @@ -1,8 +1,13 @@ | |||
| 1 | + | #!/usr/bin/env python3 | |
| 1 | 2 | import subprocess | |
| 2 | - | from collections import defaultdict | |
| 3 | + | from collections import defaultdict, deque | |
| 4 | + | from concurrent.futures import ThreadPoolExecutor | |
| 5 | + | import threading | |
| 6 | + | import os | |
| 7 | + | import sys | |
| 3 | 8 | ||
| 4 | 9 | def list_installed_packages(): | |
| 5 | - | """获取所有已安装的包列表。""" | |
| 10 | + | """Retrieve a list of all installed packages.""" | |
| 6 | 11 | result = subprocess.run( | |
| 7 | 12 | ["dpkg-query", "-f", "${binary:Package}\n", "-W"], | |
| 8 | 13 | stdout=subprocess.PIPE, | |
| @@ -11,7 +16,7 @@ def list_installed_packages(): | |||
| 11 | 16 | return result.stdout.strip().split("\n") | |
| 12 | 17 | ||
| 13 | 18 | def get_package_dependencies(package): | |
| 14 | - | """查询单个包的直接依赖项。""" | |
| 19 | + | """Query the direct dependencies of a single package.""" | |
| 15 | 20 | result = subprocess.run( | |
| 16 | 21 | ["apt-cache", "depends", package], | |
| 17 | 22 | stdout=subprocess.PIPE, | |
| @@ -21,77 +26,143 @@ def get_package_dependencies(package): | |||
| 21 | 26 | for line in result.stdout.strip().split("\n"): | |
| 22 | 27 | if line.strip().startswith("Depends:"): | |
| 23 | 28 | dep = line.split(":", 1)[1].strip() | |
| 24 | - | dep = dep.split(":")[0] # 去掉冒号后的部分 | |
| 29 | + | dep = dep.split(":")[0] # Remove parts after colon | |
| 25 | 30 | dependencies.append(dep) | |
| 26 | 31 | return dependencies | |
| 27 | 32 | ||
| 28 | 33 | def build_dependency_graph(packages): | |
| 29 | - | """构建包的依赖关系图。""" | |
| 30 | - | totalPackages = len(packages) | |
| 31 | - | processedPackages = 0 | |
| 34 | + | """Build a dependency graph for the packages and save it to a file.""" | |
| 32 | 35 | graph = defaultdict(list) | |
| 33 | - | for package in packages: | |
| 36 | + | lock = threading.Lock() | |
| 37 | + | ||
| 38 | + | def process_package(package): | |
| 34 | 39 | dependencies = get_package_dependencies(package) | |
| 35 | - | for dep in dependencies: | |
| 36 | - | print(f"{package} -> {dep}") | |
| 37 | - | graph[package].append(dep) | |
| 38 | - | processedPackages += 1 | |
| 39 | - | print(f"已处理 {processedPackages}/{totalPackages} 个包") | |
| 40 | - | return graph | |
| 41 | - | ||
| 42 | - | def remove_redundant_edges(graph): | |
| 43 | - | """去除冗余的边。""" | |
| 44 | - | def dfs(node, visited): | |
| 45 | - | if node in visited: | |
| 46 | - | return visited[node] | |
| 47 | - | visited[node] = set() | |
| 48 | - | for neighbor in graph[node]: | |
| 49 | - | visited[node].update(dfs(neighbor, visited)) | |
| 50 | - | visited[node].add(node) | |
| 51 | - | return visited[node] | |
| 52 | - | ||
| 53 | - | # 创建 graph 的静态副本来避免动态修改引发问题 | |
| 54 | - | nodes = list(graph.keys()) | |
| 55 | - | reachable = {} | |
| 56 | - | for node in nodes: # 这里使用静态副本 | |
| 57 | - | dfs(node, reachable) | |
| 58 | - | ||
| 59 | - | minimal_graph = defaultdict(list) | |
| 60 | - | for node in nodes: # 再次使用静态副本 | |
| 61 | - | direct_deps = set(graph[node]) | |
| 62 | - | for dep in graph[node]: | |
| 63 | - | direct_deps -= reachable[dep] | |
| 64 | - | minimal_graph[node] = list(direct_deps) | |
| 65 | - | return minimal_graph | |
| 66 | - | ||
| 67 | - | def generate_mermaid_graph(graph): | |
| 68 | - | """生成 Mermaid 图表的语法。""" | |
| 40 | + | with lock: | |
| 41 | + | graph[package].extend(dependencies) | |
| 42 | + | ||
| 43 | + | total_packages = len(packages) | |
| 44 | + | with ThreadPoolExecutor(max_workers=20) as executor: | |
| 45 | + | for i, _ in enumerate(executor.map(process_package, packages), start=1): | |
| 46 | + | progress = (i / total_packages) * 100 | |
| 47 | + | print(f"Building dependency graph... {progress:.2f}% completed", end="\r") | |
| 48 | + | ||
| 49 | + | output_path = "/tmp/pkg.txt" | |
| 50 | + | with open(output_path, "w") as f: | |
| 51 | + | for package, dependencies in graph.items(): | |
| 52 | + | for dep in dependencies: | |
| 53 | + | f.write(f"{package}-->{dep}\n") | |
| 54 | + | ||
| 55 | + | print(f"\nDependency graph built and saved to {output_path}") | |
| 56 | + | ||
| 57 | + | def load_dependency_graph(file_path="/tmp/pkg.txt"): | |
| 58 | + | """Load the dependency graph from a file.""" | |
| 59 | + | if not os.path.exists(file_path): | |
| 60 | + | raise FileNotFoundError(f"File {file_path} does not exist. Please run the build mode first.") | |
| 61 | + | ||
| 62 | + | graph = defaultdict(list) | |
| 63 | + | reverse_graph = defaultdict(list) | |
| 64 | + | ||
| 65 | + | with open(file_path, "r") as f: | |
| 66 | + | for line in f: | |
| 67 | + | line = line.strip() | |
| 68 | + | if "-->" in line: | |
| 69 | + | source, target = line.split("-->") | |
| 70 | + | graph[source].append(target) | |
| 71 | + | reverse_graph[target].append(source) | |
| 72 | + | ||
| 73 | + | return graph, reverse_graph | |
| 74 | + | ||
| 75 | + | def trim_package_name(package): | |
| 76 | + | """Trim package name to conform to Mermaid syntax.""" | |
| 77 | + | return package.replace("-", "_").replace(".", "_").replace("+", "_").replace(":", "_").replace("<", "_").replace(">", "_") | |
| 78 | + | ||
| 79 | + | def generate_mermaid_graph(graph, root_package, exclude_leaves=False): | |
| 80 | + | """Generate a Mermaid diagram syntax for the graph.""" | |
| 69 | 81 | lines = ["stateDiagram-v2"] | |
| 70 | - | for package, dependencies in graph.items(): | |
| 82 | + | visited = set() | |
| 83 | + | queue = deque([root_package]) | |
| 84 | + | is_leaf = lambda pkg: len(graph.get(pkg, [])) == 0 # Determine if it is a leaf node | |
| 85 | + | ||
| 86 | + | while queue: | |
| 87 | + | package = queue.popleft() | |
| 88 | + | if package in visited: | |
| 89 | + | continue | |
| 90 | + | visited.add(package) | |
| 91 | + | ||
| 92 | + | dependencies = graph.get(package, []) | |
| 71 | 93 | for dep in dependencies: | |
| 72 | - | lines.append(f" {package} --> {dep}") | |
| 94 | + | if exclude_leaves and is_leaf(dep): | |
| 95 | + | continue # Skip leaf nodes | |
| 96 | + | ||
| 97 | + | lines.append(f" {trim_package_name(package)} --> {trim_package_name(dep)}") | |
| 98 | + | if dep not in visited: | |
| 99 | + | queue.append(dep) | |
| 100 | + | ||
| 73 | 101 | return "\n".join(lines) | |
| 74 | 102 | ||
| 75 | - | def main(): | |
| 76 | - | print("正在获取已安装的包...") | |
| 103 | + | def build_mode(): | |
| 104 | + | print("Retrieving installed packages...") | |
| 77 | 105 | packages = list_installed_packages() | |
| 106 | + | print("Building dependency graph...") | |
| 107 | + | build_dependency_graph(packages) | |
| 108 | + | ||
| 109 | + | def depends_mode(package, exclude_leaves): | |
| 110 | + | graph, _ = load_dependency_graph() | |
| 111 | + | if package not in graph: | |
| 112 | + | print(f"Package {package} is not in the dependency graph.") | |
| 113 | + | return | |
| 114 | + | ||
| 115 | + | print("Generating dependency graph...") | |
| 116 | + | mermaid_graph = generate_mermaid_graph(graph, package, exclude_leaves) | |
| 78 | 117 | ||
| 79 | - | print("正在构建依赖图...") | |
| 80 | - | graph = build_dependency_graph(packages) | |
| 118 | + | with open(f"{package}_depends.mmd", "w") as f: | |
| 119 | + | f.write("---\n") | |
| 120 | + | f.write(f"title: {package} Dependency Graph\n") | |
| 121 | + | f.write("---\n\n") | |
| 122 | + | f.write(mermaid_graph) | |
| 81 | 123 | ||
| 82 | - | print("正在去除冗余边...") | |
| 83 | - | minimal_graph = remove_redundant_edges(graph) | |
| 124 | + | print(f"Dependency graph generated and saved as {package}_depends.mmd") | |
| 84 | 125 | ||
| 85 | - | print("正在生成 Mermaid 图表语法...") | |
| 86 | - | mermaid_graph = generate_mermaid_graph(minimal_graph) | |
| 126 | + | def rdepends_mode(package, exclude_leaves): | |
| 127 | + | _, reverse_graph = load_dependency_graph() | |
| 128 | + | if package not in reverse_graph: | |
| 129 | + | print(f"Package {package} is not in the reverse dependency graph.") | |
| 130 | + | return | |
| 131 | + | ||
| 132 | + | print("Generating reverse dependency graph...") | |
| 133 | + | mermaid_graph = generate_mermaid_graph(reverse_graph, package, exclude_leaves) | |
| 134 | + | ||
| 135 | + | with open(f"{package}_rdepends.mmd", "w") as f: | |
| 136 | + | f.write("---\n") | |
| 137 | + | f.write(f"title: {package} Reverse Dependency Graph\n") | |
| 138 | + | f.write("---\n\n") | |
| 139 | + | f.write(mermaid_graph) | |
| 140 | + | ||
| 141 | + | print(f"Reverse dependency graph generated and saved as {package}_rdepends.mmd") | |
| 142 | + | ||
| 143 | + | def main(): | |
| 144 | + | if len(sys.argv) < 2: | |
| 145 | + | print("Usage: ./vispkg.py [build|depends|rdepends] [package] [--no-leaves]") | |
| 146 | + | sys.exit(1) | |
| 87 | 147 | ||
| 88 | - | with open("dependency_graph.mmd", "w") as file: | |
| 89 | - | file.write("---\n") | |
| 90 | - | file.write("title: APT Dependency Graph\n") | |
| 91 | - | file.write("---\n\n") | |
| 92 | - | file.write(mermaid_graph) | |
| 148 | + | mode = sys.argv[1] | |
| 149 | + | exclude_leaves = "--no-leaves" in sys.argv | |
| 93 | 150 | ||
| 94 | - | print("Mermaid 图表已生成并保存为 dependency_graph.mmd") | |
| 151 | + | if mode == "build": | |
| 152 | + | build_mode() | |
| 153 | + | elif mode == "depends": | |
| 154 | + | if len(sys.argv) < 3: | |
| 155 | + | print("Usage: ./vispkg.py depends <package> [--no-leaves]") | |
| 156 | + | sys.exit(1) | |
| 157 | + | depends_mode(sys.argv[2], exclude_leaves) | |
| 158 | + | elif mode == "rdepends": | |
| 159 | + | if len(sys.argv) < 3: | |
| 160 | + | print("Usage: ./vispkg.py rdepends <package> [--no-leaves]") | |
| 161 | + | sys.exit(1) | |
| 162 | + | rdepends_mode(sys.argv[2], exclude_leaves) | |
| 163 | + | else: | |
| 164 | + | print("Unknown mode. Please use: build, depends, or rdepends.") | |
| 165 | + | sys.exit(1) | |
| 95 | 166 | ||
| 96 | 167 | if __name__ == "__main__": | |
| 97 | 168 | main() | |
anduin revised this gist 10 months ago. Go to revision
1 file changed, 97 insertions
visualize.py(file created)
| @@ -0,0 +1,97 @@ | |||
| 1 | + | import subprocess | |
| 2 | + | from collections import defaultdict | |
| 3 | + | ||
| 4 | + | def list_installed_packages(): | |
| 5 | + | """获取所有已安装的包列表。""" | |
| 6 | + | result = subprocess.run( | |
| 7 | + | ["dpkg-query", "-f", "${binary:Package}\n", "-W"], | |
| 8 | + | stdout=subprocess.PIPE, | |
| 9 | + | text=True | |
| 10 | + | ) | |
| 11 | + | return result.stdout.strip().split("\n") | |
| 12 | + | ||
| 13 | + | def get_package_dependencies(package): | |
| 14 | + | """查询单个包的直接依赖项。""" | |
| 15 | + | result = subprocess.run( | |
| 16 | + | ["apt-cache", "depends", package], | |
| 17 | + | stdout=subprocess.PIPE, | |
| 18 | + | text=True | |
| 19 | + | ) | |
| 20 | + | dependencies = [] | |
| 21 | + | for line in result.stdout.strip().split("\n"): | |
| 22 | + | if line.strip().startswith("Depends:"): | |
| 23 | + | dep = line.split(":", 1)[1].strip() | |
| 24 | + | dep = dep.split(":")[0] # 去掉冒号后的部分 | |
| 25 | + | dependencies.append(dep) | |
| 26 | + | return dependencies | |
| 27 | + | ||
| 28 | + | def build_dependency_graph(packages): | |
| 29 | + | """构建包的依赖关系图。""" | |
| 30 | + | totalPackages = len(packages) | |
| 31 | + | processedPackages = 0 | |
| 32 | + | graph = defaultdict(list) | |
| 33 | + | for package in packages: | |
| 34 | + | dependencies = get_package_dependencies(package) | |
| 35 | + | for dep in dependencies: | |
| 36 | + | print(f"{package} -> {dep}") | |
| 37 | + | graph[package].append(dep) | |
| 38 | + | processedPackages += 1 | |
| 39 | + | print(f"已处理 {processedPackages}/{totalPackages} 个包") | |
| 40 | + | return graph | |
| 41 | + | ||
| 42 | + | def remove_redundant_edges(graph): | |
| 43 | + | """去除冗余的边。""" | |
| 44 | + | def dfs(node, visited): | |
| 45 | + | if node in visited: | |
| 46 | + | return visited[node] | |
| 47 | + | visited[node] = set() | |
| 48 | + | for neighbor in graph[node]: | |
| 49 | + | visited[node].update(dfs(neighbor, visited)) | |
| 50 | + | visited[node].add(node) | |
| 51 | + | return visited[node] | |
| 52 | + | ||
| 53 | + | # 创建 graph 的静态副本来避免动态修改引发问题 | |
| 54 | + | nodes = list(graph.keys()) | |
| 55 | + | reachable = {} | |
| 56 | + | for node in nodes: # 这里使用静态副本 | |
| 57 | + | dfs(node, reachable) | |
| 58 | + | ||
| 59 | + | minimal_graph = defaultdict(list) | |
| 60 | + | for node in nodes: # 再次使用静态副本 | |
| 61 | + | direct_deps = set(graph[node]) | |
| 62 | + | for dep in graph[node]: | |
| 63 | + | direct_deps -= reachable[dep] | |
| 64 | + | minimal_graph[node] = list(direct_deps) | |
| 65 | + | return minimal_graph | |
| 66 | + | ||
| 67 | + | def generate_mermaid_graph(graph): | |
| 68 | + | """生成 Mermaid 图表的语法。""" | |
| 69 | + | lines = ["stateDiagram-v2"] | |
| 70 | + | for package, dependencies in graph.items(): | |
| 71 | + | for dep in dependencies: | |
| 72 | + | lines.append(f" {package} --> {dep}") | |
| 73 | + | return "\n".join(lines) | |
| 74 | + | ||
| 75 | + | def main(): | |
| 76 | + | print("正在获取已安装的包...") | |
| 77 | + | packages = list_installed_packages() | |
| 78 | + | ||
| 79 | + | print("正在构建依赖图...") | |
| 80 | + | graph = build_dependency_graph(packages) | |
| 81 | + | ||
| 82 | + | print("正在去除冗余边...") | |
| 83 | + | minimal_graph = remove_redundant_edges(graph) | |
| 84 | + | ||
| 85 | + | print("正在生成 Mermaid 图表语法...") | |
| 86 | + | mermaid_graph = generate_mermaid_graph(minimal_graph) | |
| 87 | + | ||
| 88 | + | with open("dependency_graph.mmd", "w") as file: | |
| 89 | + | file.write("---\n") | |
| 90 | + | file.write("title: APT Dependency Graph\n") | |
| 91 | + | file.write("---\n\n") | |
| 92 | + | file.write(mermaid_graph) | |
| 93 | + | ||
| 94 | + | print("Mermaid 图表已生成并保存为 dependency_graph.mmd") | |
| 95 | + | ||
| 96 | + | if __name__ == "__main__": | |
| 97 | + | main() | |
anduin revised this gist 10 months ago. Go to revision
2 files changed, 26 insertions
compare.sh(file created)
| @@ -0,0 +1,24 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # | |
| 3 | + | # compare_packages.sh | |
| 4 | + | # | |
| 5 | + | # Compare package lists from two systems: | |
| 6 | + | # anduinos-packages.txt | |
| 7 | + | # ubuntu-24-packages.txt | |
| 8 | + | ||
| 9 | + | # Ensure both files exist | |
| 10 | + | if [[ ! -f "anduinos-packages.txt" || ! -f "ubuntu-24-packages.txt" ]]; then | |
| 11 | + | echo "Error: One or both package list files are missing." | |
| 12 | + | echo "Please make sure anduinos-packages.txt and ubuntu-24-packages.txt are present." | |
| 13 | + | exit 1 | |
| 14 | + | fi | |
| 15 | + | ||
| 16 | + | echo "===== Packages installed on anduinos but NOT on ubuntu =====" | |
| 17 | + | comm -23 <(sort anduinos-packages.txt) <(sort ubuntu-24-packages.txt) | |
| 18 | + | ||
| 19 | + | echo | |
| 20 | + | echo "===== Packages installed on ubuntu but NOT on anduinos =====" | |
| 21 | + | comm -13 <(sort anduinos-packages.txt) <(sort ubuntu-24-packages.txt) | |
| 22 | + | ||
| 23 | + | echo | |
| 24 | + | echo "Comparison done." | |
export.sh(file created)
| @@ -0,0 +1,2 @@ | |||
| 1 | + | dpkg-query -f '${binary:Package}\n' -W > anduinos-packages.txt | |
| 2 | + | dpkg-query -f '${binary:Package}\n' -W > ubuntu-24-packages.txt | |