Requirements: Python 3.9+
Running: python3 -m vpm < input.txt
DEPEND TELNET TCPIP NETCARD | |
DEPEND TCPIP NETCARD | |
DEPEND DNS TCPIP NETCARD | |
DEPEND BROWSER TCPIP HTML | |
INSTALL NETCARD | |
INSTALL TELNET | |
INSTALL foo | |
REMOVE NETCARD | |
INSTALL BROWSER | |
INSTALL DNS | |
LIST | |
REMOVE TELNET | |
REMOVE NETCARD | |
REMOVE DNS | |
REMOVE NETCARD | |
INSTALL NETCARD | |
REMOVE TCPIP | |
REMOVE BROWSER | |
REMOVE TCPIP | |
LIST | |
END |
# Vintra package manager | |
import sys | |
from graphlib import TopologicalSorter # Python 3.9+ | |
def collect_dependencies(dependencies, *pkgs): | |
""" | |
Returns iterator over dependencies in sorted order. | |
Uses graphlib.TopologicalSorter from Python 3.9+ | |
""" | |
ts = TopologicalSorter() | |
candidates = set(pkgs) | |
while candidates: | |
pkg = candidates.pop() | |
if pkg in dependencies: | |
deps = dependencies[pkg] | |
ts.add(pkg, *deps) | |
candidates.update(deps) | |
return ts.static_order() | |
class PackageManager: | |
def __init__(self): | |
self.dependencies = {} | |
self.installed = set() | |
self.auto_installed = set() | |
def depend(self, pkg, *items): | |
""" | |
Package item1 depends on package item2 (and item3 or any additional packages). | |
""" | |
self.dependencies[pkg] = items | |
def install(self, item): | |
""" | |
Installs item1 and any other packages required by item1 | |
""" | |
if item in self.installed: | |
print(f'\t{item} is already installed') | |
return | |
deps = list(collect_dependencies(self.dependencies, item))[:-1] | |
for pkg in deps: | |
if pkg not in self.auto_installed and pkg not in self.installed: | |
# perform actual install | |
self.auto_installed.add(pkg) | |
print(f'\t{pkg} successfully installed') | |
print(f'\t{item} successfully installed') | |
self.installed.add(item) | |
def remove(self, pkg): | |
""" | |
Removes item1 and, if possible, packages required by item1. | |
""" | |
if not (pkg in self.installed or pkg in self.auto_installed): | |
print(f'\t{pkg} is not installed') | |
return | |
deps = set(collect_dependencies(self.dependencies, *(self.installed - {pkg}))) | |
if pkg in deps: | |
print(f'\t{pkg} is still needed') | |
return | |
self.installed.remove(pkg) | |
print(f'\t{pkg} successfully removed') | |
pkg_deps = list(collect_dependencies(self.dependencies, pkg)) | |
for p in pkg_deps[:-1]: | |
if p not in deps and p not in self.installed: | |
print(f'\t{p} is no longer needed') | |
self.auto_installed.remove(p) | |
print(f'\t{p} successfully removed') | |
def list(self): | |
""" | |
Lists the names of all currently installed packages. | |
""" | |
for pkg in self.installed.union(self.auto_installed): | |
print(f'\t{pkg}') | |
def end(self): | |
""" | |
Marks the end of input, when used in a line by itself. | |
""" | |
pass | |
def process_input(package_manager=None, stream=sys.stdin): | |
pm = package_manager or PackageManager() | |
for line in stream: | |
if words := line.split(): | |
cmd, *args = words | |
try: | |
print(line, end='') | |
getattr(pm, cmd.swapcase())(*args) | |
except AttributeError: | |
print(f'ERROR: Unknown command {cmd}') | |
if __name__ == '__main__': | |
process_input() |