compare.sh
                        
                             · 718 B · Bash
                        
                    
                    
                      
                        Surowy
                      
                    
                      
                    
                        
                          
                        
                    
                    
                
                
                
            #!/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
                        
                    
                    
                      
                        Surowy
                      
                    
                      
                    
                        
                          
                        
                    
                    
                
                
                
            #!/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 |