compare.sh
· 718 B · Bash
Eredeti
#!/usr/bin/env bash
#
# compare_packages.sh
#
# Compare package lists from two systems:
# anduinos-packages.txt
# ubuntu-24-packages.txt
# Ensure both files exist
if [[ ! -f "anduinos-packages.txt" || ! -f "ubuntu-24-packages.txt" ]]; then
echo "Error: One or both package list files are missing."
echo "Please make sure anduinos-packages.txt and ubuntu-24-packages.txt are present."
exit 1
fi
echo "===== Packages installed on anduinos but NOT on ubuntu ====="
comm -23 <(sort anduinos-packages.txt) <(sort ubuntu-24-packages.txt)
echo
echo "===== Packages installed on ubuntu but NOT on anduinos ====="
comm -13 <(sort anduinos-packages.txt) <(sort ubuntu-24-packages.txt)
echo
echo "Comparison done."
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." |
25 |
1 | dpkg-query -f '${binary:Package}\n' -W > anduinos-packages.txt |
2 | dpkg-query -f '${binary:Package}\n' -W > ubuntu-24-packages.txt |
visualize.py
· 8.8 KiB · Python
Eredeti
#!/usr/bin/env python3
import subprocess
from collections import defaultdict, deque
from concurrent.futures import ThreadPoolExecutor
import threading
import os
import sys
import webbrowser
import urllib.request
MERMAID_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="mermaid.min.js"></script>
<script src="panzoom.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {{
mermaid.initialize({{ startOnLoad: true }});
mermaid.contentLoaded().then(() => {{
const svg = document.querySelector(".mermaid > svg");
if (svg) {{
const panzoom = Panzoom(svg, {{ maxScale: 5, minScale: 0.5 }});
svg.parentElement.addEventListener('wheel', panzoom.zoomWithWheel);
}} else {{
console.error("Mermaid SVG not found for Panzoom initialization.");
}}
}});
}});
</script>
<style>
body {{
margin: 0;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f4f4f4;
}}
#diagram-container {{
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}}
.mermaid {{
width: 100%;
height: 100%;
}}
</style>
</head>
<body>
<div id="diagram-container">
<div class="mermaid">
{graph_content}
</div>
</div>
</body>
</html>
"""
def ensure_local_files():
"""Ensure Mermaid.js and Panzoom.js are available locally."""
js_files = {
"mermaid.min.js": "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js",
"panzoom.min.js": "https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js",
}
tmp_dir = "/tmp"
for filename, url in js_files.items():
file_path = os.path.join(tmp_dir, filename)
if not os.path.exists(file_path):
print(f"Downloading {filename}...")
urllib.request.urlretrieve(url, file_path)
def preview_mermaid_graph(file_path):
"""Preview Mermaid graph in a browser."""
ensure_local_files()
if not os.path.exists(file_path):
print(f"File {file_path} does not exist.")
return
# Load the Mermaid graph content
with open(file_path, "r") as f:
graph_content = f.read()
# Prepare the HTML file content
html_content = MERMAID_TEMPLATE.format(graph_content=graph_content)
# Write the HTML to a temporary file
html_file_path = f"{file_path}.html"
with open(html_file_path, "w") as html_file:
html_file.write(html_content)
# Copy JS files to the same directory
for js_file in ["mermaid.min.js", "panzoom.min.js"]:
src = os.path.join("/tmp", js_file)
dst = os.path.join(os.path.dirname(html_file_path), js_file)
if not os.path.exists(dst):
subprocess.run(["cp", src, dst])
# Open the HTML file in the default web browser
print(f"Opening {html_file_path} in the browser...")
webbrowser.open(f"file://{os.path.abspath(html_file_path)}", new=2)
def list_installed_packages():
"""Retrieve a list of all installed packages."""
result = subprocess.run(
["dpkg-query", "-f", "${binary:Package}\n", "-W"],
stdout=subprocess.PIPE,
text=True
)
return result.stdout.strip().split("\n")
def get_package_dependencies(package):
"""Query the direct dependencies of a single package."""
result = subprocess.run(
["apt-cache", "depends", package],
stdout=subprocess.PIPE,
text=True
)
dependencies = []
for line in result.stdout.strip().split("\n"):
if line.strip().startswith("Depends:"):
dep = line.split(":", 1)[1].strip()
dep = dep.split(":")[0] # Remove parts after colon
dependencies.append(dep)
return dependencies
def build_dependency_graph(packages):
"""Build a dependency graph for the packages and save it to a file."""
graph = defaultdict(list)
lock = threading.Lock()
def process_package(package):
dependencies = get_package_dependencies(package)
with lock:
graph[package].extend(dependencies)
total_packages = len(packages)
with ThreadPoolExecutor(max_workers=20) as executor:
for i, _ in enumerate(executor.map(process_package, packages), start=1):
progress = (i / total_packages) * 100
print(f"Building dependency graph... {progress:.2f}% completed", end="\r")
output_path = "/tmp/pkg.txt"
with open(output_path, "w") as f:
for package, dependencies in graph.items():
for dep in dependencies:
f.write(f"{package}-->{dep}\n")
print(f"\nDependency graph built and saved to {output_path}")
def load_dependency_graph(file_path="/tmp/pkg.txt"):
"""Load the dependency graph from a file."""
if not os.path.exists(file_path):
raise FileNotFoundError(f"File {file_path} does not exist. Please run the build mode first.")
graph = defaultdict(list)
reverse_graph = defaultdict(list)
with open(file_path, "r") as f:
for line in f:
line = line.strip()
if "-->" in line:
source, target = line.split("-->")
graph[source].append(target)
reverse_graph[target].append(source)
return graph, reverse_graph
def trim_package_name(package):
"""Trim package name to conform to Mermaid syntax."""
return package.replace("-", "_").replace(".", "_").replace("+", "_").replace(":", "_").replace("<", "_").replace(">", "_")
def generate_mermaid_graph(graph, root_package, exclude_leaves=False):
"""Generate a Mermaid diagram syntax for the graph."""
lines = ["stateDiagram-v2"]
visited = set()
queue = deque([root_package])
is_leaf = lambda pkg: len(graph.get(pkg, [])) == 0 # Determine if it is a leaf node
while queue:
package = queue.popleft()
if package in visited:
continue
visited.add(package)
dependencies = graph.get(package, [])
for dep in dependencies:
if exclude_leaves and is_leaf(dep):
continue # Skip leaf nodes
lines.append(f" {trim_package_name(package)} --> {trim_package_name(dep)}")
if dep not in visited:
queue.append(dep)
return "\n".join(lines)
def build_mode():
print("Retrieving installed packages...")
packages = list_installed_packages()
print("Building dependency graph...")
build_dependency_graph(packages)
def depends_mode(package, exclude_leaves):
graph, _ = load_dependency_graph()
if package not in graph:
print(f"Package {package} is not in the dependency graph.")
return
print("Generating dependency graph...")
mermaid_graph = generate_mermaid_graph(graph, package, exclude_leaves)
output_file = f"{package}_depends.mmd"
with open(output_file, "w") as f:
f.write("---\n")
f.write(f"title: {package} Dependency Graph\n")
f.write("---\n\n")
f.write(mermaid_graph)
print(f"Dependency graph generated and saved as {output_file}")
preview_mermaid_graph(output_file)
def rdepends_mode(package, exclude_leaves):
_, reverse_graph = load_dependency_graph()
if package not in reverse_graph:
print(f"Package {package} is not in the reverse dependency graph.")
return
print("Generating reverse dependency graph...")
mermaid_graph = generate_mermaid_graph(reverse_graph, package, exclude_leaves)
output_file = f"{package}_rdepends.mmd"
with open(output_file, "w") as f:
f.write("---\n")
f.write(f"title: {package} Reverse Dependency Graph\n")
f.write("---\n\n")
f.write(mermaid_graph)
print(f"Reverse dependency graph generated and saved as {output_file}")
preview_mermaid_graph(output_file)
def main():
if len(sys.argv) < 2:
print("Usage: ./vispkg.py [build|depends|rdepends] [package] [--no-leaves]")
sys.exit(1)
mode = sys.argv[1]
exclude_leaves = "--no-leaves" in sys.argv
if mode == "build":
build_mode()
elif mode == "depends":
if len(sys.argv) < 3:
print("Usage: ./vispkg.py depends <package> [--no-leaves]")
sys.exit(1)
depends_mode(sys.argv[2], exclude_leaves)
elif mode == "rdepends":
if len(sys.argv) < 3:
print("Usage: ./vispkg.py rdepends <package> [--no-leaves]")
sys.exit(1)
rdepends_mode(sys.argv[2], exclude_leaves)
else:
print("Unknown mode. Please use: build, depends, or rdepends.")
sys.exit(1)
if __name__ == "__main__":
main()
1 | #!/usr/bin/env python3 |
2 | import subprocess |
3 | from collections import defaultdict, deque |
4 | from concurrent.futures import ThreadPoolExecutor |
5 | import threading |
6 | import os |
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) |
110 | |
111 | def list_installed_packages(): |
112 | """Retrieve a list of all installed packages.""" |
113 | result = subprocess.run( |
114 | ["dpkg-query", "-f", "${binary:Package}\n", "-W"], |
115 | stdout=subprocess.PIPE, |
116 | text=True |
117 | ) |
118 | return result.stdout.strip().split("\n") |
119 | |
120 | def get_package_dependencies(package): |
121 | """Query the direct dependencies of a single package.""" |
122 | result = subprocess.run( |
123 | ["apt-cache", "depends", package], |
124 | stdout=subprocess.PIPE, |
125 | text=True |
126 | ) |
127 | dependencies = [] |
128 | for line in result.stdout.strip().split("\n"): |
129 | if line.strip().startswith("Depends:"): |
130 | dep = line.split(":", 1)[1].strip() |
131 | dep = dep.split(":")[0] # Remove parts after colon |
132 | dependencies.append(dep) |
133 | return dependencies |
134 | |
135 | def build_dependency_graph(packages): |
136 | """Build a dependency graph for the packages and save it to a file.""" |
137 | graph = defaultdict(list) |
138 | lock = threading.Lock() |
139 | |
140 | def process_package(package): |
141 | dependencies = get_package_dependencies(package) |
142 | with lock: |
143 | graph[package].extend(dependencies) |
144 | |
145 | total_packages = len(packages) |
146 | with ThreadPoolExecutor(max_workers=20) as executor: |
147 | for i, _ in enumerate(executor.map(process_package, packages), start=1): |
148 | progress = (i / total_packages) * 100 |
149 | print(f"Building dependency graph... {progress:.2f}% completed", end="\r") |
150 | |
151 | output_path = "/tmp/pkg.txt" |
152 | with open(output_path, "w") as f: |
153 | for package, dependencies in graph.items(): |
154 | for dep in dependencies: |
155 | f.write(f"{package}-->{dep}\n") |
156 | |
157 | print(f"\nDependency graph built and saved to {output_path}") |
158 | |
159 | def load_dependency_graph(file_path="/tmp/pkg.txt"): |
160 | """Load the dependency graph from a file.""" |
161 | if not os.path.exists(file_path): |
162 | raise FileNotFoundError(f"File {file_path} does not exist. Please run the build mode first.") |
163 | |
164 | graph = defaultdict(list) |
165 | reverse_graph = defaultdict(list) |
166 | |
167 | with open(file_path, "r") as f: |
168 | for line in f: |
169 | line = line.strip() |
170 | if "-->" in line: |
171 | source, target = line.split("-->") |
172 | graph[source].append(target) |
173 | reverse_graph[target].append(source) |
174 | |
175 | return graph, reverse_graph |
176 | |
177 | def trim_package_name(package): |
178 | """Trim package name to conform to Mermaid syntax.""" |
179 | return package.replace("-", "_").replace(".", "_").replace("+", "_").replace(":", "_").replace("<", "_").replace(">", "_") |
180 | |
181 | def generate_mermaid_graph(graph, root_package, exclude_leaves=False): |
182 | """Generate a Mermaid diagram syntax for the graph.""" |
183 | lines = ["stateDiagram-v2"] |
184 | visited = set() |
185 | queue = deque([root_package]) |
186 | is_leaf = lambda pkg: len(graph.get(pkg, [])) == 0 # Determine if it is a leaf node |
187 | |
188 | while queue: |
189 | package = queue.popleft() |
190 | if package in visited: |
191 | continue |
192 | visited.add(package) |
193 | |
194 | dependencies = graph.get(package, []) |
195 | for dep in dependencies: |
196 | if exclude_leaves and is_leaf(dep): |
197 | continue # Skip leaf nodes |
198 | |
199 | lines.append(f" {trim_package_name(package)} --> {trim_package_name(dep)}") |
200 | if dep not in visited: |
201 | queue.append(dep) |
202 | |
203 | return "\n".join(lines) |
204 | |
205 | def build_mode(): |
206 | print("Retrieving installed packages...") |
207 | packages = list_installed_packages() |
208 | print("Building dependency graph...") |
209 | build_dependency_graph(packages) |
210 | |
211 | def depends_mode(package, exclude_leaves): |
212 | graph, _ = load_dependency_graph() |
213 | if package not in graph: |
214 | print(f"Package {package} is not in the dependency graph.") |
215 | return |
216 | |
217 | print("Generating dependency graph...") |
218 | mermaid_graph = generate_mermaid_graph(graph, package, exclude_leaves) |
219 | |
220 | output_file = f"{package}_depends.mmd" |
221 | with open(output_file, "w") as f: |
222 | f.write("---\n") |
223 | f.write(f"title: {package} Dependency Graph\n") |
224 | f.write("---\n\n") |
225 | f.write(mermaid_graph) |
226 | |
227 | print(f"Dependency graph generated and saved as {output_file}") |
228 | preview_mermaid_graph(output_file) |
229 | |
230 | def rdepends_mode(package, exclude_leaves): |
231 | _, reverse_graph = load_dependency_graph() |
232 | if package not in reverse_graph: |
233 | print(f"Package {package} is not in the reverse dependency graph.") |
234 | return |
235 | |
236 | print("Generating reverse dependency graph...") |
237 | mermaid_graph = generate_mermaid_graph(reverse_graph, package, exclude_leaves) |
238 | |
239 | output_file = f"{package}_rdepends.mmd" |
240 | with open(output_file, "w") as f: |
241 | f.write("---\n") |
242 | f.write(f"title: {package} Reverse Dependency Graph\n") |
243 | f.write("---\n\n") |
244 | f.write(mermaid_graph) |
245 | |
246 | print(f"Reverse dependency graph generated and saved as {output_file}") |
247 | preview_mermaid_graph(output_file) |
248 | |
249 | def main(): |
250 | if len(sys.argv) < 2: |
251 | print("Usage: ./vispkg.py [build|depends|rdepends] [package] [--no-leaves]") |
252 | sys.exit(1) |
253 | |
254 | mode = sys.argv[1] |
255 | exclude_leaves = "--no-leaves" in sys.argv |
256 | |
257 | if mode == "build": |
258 | build_mode() |
259 | elif mode == "depends": |
260 | if len(sys.argv) < 3: |
261 | print("Usage: ./vispkg.py depends <package> [--no-leaves]") |
262 | sys.exit(1) |
263 | depends_mode(sys.argv[2], exclude_leaves) |
264 | elif mode == "rdepends": |
265 | if len(sys.argv) < 3: |
266 | print("Usage: ./vispkg.py rdepends <package> [--no-leaves]") |
267 | sys.exit(1) |
268 | rdepends_mode(sys.argv[2], exclude_leaves) |
269 | else: |
270 | print("Unknown mode. Please use: build, depends, or rdepends.") |
271 | sys.exit(1) |
272 | |
273 | if __name__ == "__main__": |
274 | main() |
275 |