Last active 1735293001

anduin's Avatar anduin revised this gist 1735293001. 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's Avatar anduin revised this gist 1735291219. 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's Avatar anduin revised this gist 1735287333. 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's Avatar anduin revised this gist 1735043896. 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
Newer Older