mirror of
https://github.com/kiviktnm/decman.git
synced 2026-09-19 12:08:28 +00:00
Add feature to print pacman output highlights
This commit is contained in:
@@ -132,6 +132,18 @@ decman.config.suppress_command_output = True
|
|||||||
# Make output less verbose. Summaries are still printed.
|
# Make output less verbose. Summaries are still printed.
|
||||||
decman.config.quiet_output = False
|
decman.config.quiet_output = False
|
||||||
|
|
||||||
|
# Decman captures pacman command output, and any line (and adjacent lines) that contains any of
|
||||||
|
# the following keywords (case-insensetive) will be printed after the pacman command finishes.
|
||||||
|
decman.config.pacman_output_keywords = [
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"note",
|
||||||
|
"pacsave",
|
||||||
|
"pacnew",
|
||||||
|
]
|
||||||
|
# If you don't want to print lines that contain keywords, set this to False
|
||||||
|
decman.config.print_pacman_output_highlights = True
|
||||||
|
|
||||||
# The user which builds aur and user packages.
|
# The user which builds aur and user packages.
|
||||||
# decman.config.makepkg_user = "nobody" # This was set in a previous example. Let's not override it.
|
# decman.config.makepkg_user = "nobody" # This was set in a previous example. Let's not override it.
|
||||||
|
|
||||||
|
|||||||
@@ -199,6 +199,15 @@ valid_pkgexts: list[str] = [
|
|||||||
".pkg.tar.Z",
|
".pkg.tar.Z",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
pacman_output_keywords: list[str] = [
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"note",
|
||||||
|
"pacsave",
|
||||||
|
"pacnew",
|
||||||
|
]
|
||||||
|
print_pacman_output_highlights: bool = True
|
||||||
|
|
||||||
makepkg_user: str = "nobody"
|
makepkg_user: str = "nobody"
|
||||||
build_dir: str = "/tmp/decman/build"
|
build_dir: str = "/tmp/decman/build"
|
||||||
pkg_cache_dir: str = "/var/cache/decman"
|
pkg_cache_dir: str = "/var/cache/decman"
|
||||||
|
|||||||
+125
-19
@@ -2,7 +2,8 @@
|
|||||||
Library module for decman.
|
Library module for decman.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pwd
|
import threading
|
||||||
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
@@ -755,14 +756,23 @@ class Pacman:
|
|||||||
if not packages:
|
if not packages:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
returncode, output = echo_and_capture_command(
|
||||||
|
conf.commands.install_pkgs(packages))
|
||||||
|
if returncode != 0:
|
||||||
|
raise err.UserFacingError(
|
||||||
|
f"Failed to install packages using pacman. Process exited with code {returncode}."
|
||||||
|
)
|
||||||
|
if conf.print_pacman_output_highlights:
|
||||||
|
print_highlighted_pacman_messages(output)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(conf.commands.install_pkgs(packages), check=True)
|
|
||||||
subprocess.run(conf.commands.set_as_explicitly_installed(packages),
|
subprocess.run(conf.commands.set_as_explicitly_installed(packages),
|
||||||
check=True,
|
check=True,
|
||||||
capture_output=conf.suppress_command_output)
|
capture_output=conf.suppress_command_output)
|
||||||
except subprocess.CalledProcessError as error:
|
except subprocess.CalledProcessError as error:
|
||||||
raise err.UserFacingError(
|
raise err.UserFacingError(
|
||||||
"Failed to install packages using pacman.") from error
|
"Failed to set packages as explicitly installed using pacman."
|
||||||
|
) from error
|
||||||
|
|
||||||
def install_dependencies(self, deps: list[str]):
|
def install_dependencies(self, deps: list[str]):
|
||||||
"""
|
"""
|
||||||
@@ -771,12 +781,14 @@ class Pacman:
|
|||||||
if not deps:
|
if not deps:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
returncode, output = echo_and_capture_command(
|
||||||
subprocess.run(conf.commands.install_deps(deps), check=True)
|
conf.commands.install_deps(deps))
|
||||||
except subprocess.CalledProcessError as error:
|
if returncode != 0:
|
||||||
raise err.UserFacingError(
|
raise err.UserFacingError(
|
||||||
"Failed to install packages as dependencies using pacman."
|
f"Failed to install packages as dependencies using pacman. Process exited with code {returncode}."
|
||||||
) from error
|
)
|
||||||
|
if conf.print_pacman_output_highlights:
|
||||||
|
print_highlighted_pacman_messages(output)
|
||||||
|
|
||||||
def install_files(self, files: list[str], as_explicit: list[str]):
|
def install_files(self, files: list[str], as_explicit: list[str]):
|
||||||
"""
|
"""
|
||||||
@@ -786,9 +798,16 @@ class Pacman:
|
|||||||
if not files:
|
if not files:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
returncode, output = echo_and_capture_command(
|
||||||
subprocess.run(conf.commands.install_files(files), check=True)
|
conf.commands.install_files(files))
|
||||||
|
if returncode != 0:
|
||||||
|
raise err.UserFacingError(
|
||||||
|
f"Failed to install package files using pacman. Process exited with code {returncode}."
|
||||||
|
)
|
||||||
|
if conf.print_pacman_output_highlights:
|
||||||
|
print_highlighted_pacman_messages(output)
|
||||||
|
|
||||||
|
try:
|
||||||
if as_explicit:
|
if as_explicit:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
conf.commands.set_as_explicitly_installed(as_explicit),
|
conf.commands.set_as_explicitly_installed(as_explicit),
|
||||||
@@ -799,17 +818,20 @@ class Pacman:
|
|||||||
print_error("Output:")
|
print_error("Output:")
|
||||||
print_continuation(error.output)
|
print_continuation(error.output)
|
||||||
raise err.UserFacingError(
|
raise err.UserFacingError(
|
||||||
"Failed to install package files using pacman.") from error
|
"Failed to set packages as explicitly installed using pacman."
|
||||||
|
) from error
|
||||||
|
|
||||||
def upgrade(self):
|
def upgrade(self):
|
||||||
"""
|
"""
|
||||||
Upgrades all packages.
|
Upgrades all packages.
|
||||||
"""
|
"""
|
||||||
try:
|
returncode, output = echo_and_capture_command(conf.commands.upgrade())
|
||||||
subprocess.run(conf.commands.upgrade(), check=True)
|
if returncode != 0:
|
||||||
except subprocess.CalledProcessError as error:
|
|
||||||
raise err.UserFacingError(
|
raise err.UserFacingError(
|
||||||
"Failed to upgrade packages using pacman.") from error
|
f"Failed to upgrade packages using pacman. Process exited with code {returncode}."
|
||||||
|
)
|
||||||
|
if conf.print_pacman_output_highlights:
|
||||||
|
print_highlighted_pacman_messages(output)
|
||||||
|
|
||||||
def remove(self, packages: list[str]):
|
def remove(self, packages: list[str]):
|
||||||
"""
|
"""
|
||||||
@@ -817,11 +839,95 @@ class Pacman:
|
|||||||
"""
|
"""
|
||||||
if not packages:
|
if not packages:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
subprocess.run(conf.commands.remove(packages), check=True)
|
returncode, output = echo_and_capture_command(
|
||||||
except subprocess.CalledProcessError as error:
|
conf.commands.remove(packages))
|
||||||
|
if returncode != 0:
|
||||||
raise err.UserFacingError(
|
raise err.UserFacingError(
|
||||||
"Failed to remove packages using pacman.") from error
|
f"Failed to remove packages using pacman. Process exited with code {returncode}."
|
||||||
|
)
|
||||||
|
if conf.print_pacman_output_highlights:
|
||||||
|
print_highlighted_pacman_messages(output)
|
||||||
|
|
||||||
|
|
||||||
|
def print_highlighted_pacman_messages(output: str):
|
||||||
|
"""
|
||||||
|
Prints lines that contain pacman output keywords.
|
||||||
|
"""
|
||||||
|
print_summary("Pacman output highlights:")
|
||||||
|
lines = output.split("\n")
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
for keyword in conf.pacman_output_keywords:
|
||||||
|
if keyword.lower() in line.lower():
|
||||||
|
print_summary(f"lines: {index}-{index+2}")
|
||||||
|
if index >= 1:
|
||||||
|
print_continuation(lines[index - 1])
|
||||||
|
print_continuation(line)
|
||||||
|
if index + 1 < len(lines):
|
||||||
|
print_continuation(lines[index + 1])
|
||||||
|
print_continuation("")
|
||||||
|
|
||||||
|
# Break, as to not print the same line again if it contains multiple keywords
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def echo_and_capture_command(program: list[str]) -> tuple[int, str]:
|
||||||
|
"""
|
||||||
|
Runs the given CLI program and arguments.
|
||||||
|
|
||||||
|
Returns a tuple containing the return code of the program as well as all output of the program.
|
||||||
|
"""
|
||||||
|
with subprocess.Popen(program,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT) as process:
|
||||||
|
os.set_blocking(process.stdout.fileno(), False)
|
||||||
|
|
||||||
|
output_thread = _OutputCapturingThread(process.stdout)
|
||||||
|
output_thread.start()
|
||||||
|
|
||||||
|
os.set_blocking(sys.stdin.fileno(), False)
|
||||||
|
|
||||||
|
# Capture stdin and forward it to the process in a non-blocking manner
|
||||||
|
while process.poll() is None:
|
||||||
|
inp = sys.stdin.readline()
|
||||||
|
|
||||||
|
if inp:
|
||||||
|
process.stdin.write(inp.encode())
|
||||||
|
process.stdin.flush()
|
||||||
|
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
os.set_blocking(sys.stdin.fileno(), True)
|
||||||
|
|
||||||
|
output_thread.done = True
|
||||||
|
output_thread.join()
|
||||||
|
|
||||||
|
# Capture any output that may not have been yet captured
|
||||||
|
output = output_thread.output
|
||||||
|
missing_output = process.stdout.read()
|
||||||
|
if missing_output:
|
||||||
|
output += missing_output.decode()
|
||||||
|
|
||||||
|
return (process.returncode, output)
|
||||||
|
|
||||||
|
|
||||||
|
class _OutputCapturingThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, stream):
|
||||||
|
super().__init__()
|
||||||
|
self._stream = stream
|
||||||
|
self.output = ""
|
||||||
|
self.done = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while not self.done and not self._stream.closed:
|
||||||
|
output = self._stream.read()
|
||||||
|
if output:
|
||||||
|
output = output.decode()
|
||||||
|
self.output += output
|
||||||
|
print(output, end="")
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
class Systemd:
|
class Systemd:
|
||||||
|
|||||||
Reference in New Issue
Block a user