mirror of
https://github.com/kiviktnm/decman.git
synced 2026-09-19 12:08:28 +00:00
Merge pull request #3 from kiviktnm/highlight-pacman-output
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.
|
||||
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.
|
||||
# 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",
|
||||
]
|
||||
|
||||
pacman_output_keywords: list[str] = [
|
||||
"warning",
|
||||
"error",
|
||||
"note",
|
||||
"pacsave",
|
||||
"pacnew",
|
||||
]
|
||||
print_pacman_output_highlights: bool = True
|
||||
|
||||
makepkg_user: str = "nobody"
|
||||
build_dir: str = "/tmp/decman/build"
|
||||
pkg_cache_dir: str = "/var/cache/decman"
|
||||
|
||||
+125
-19
@@ -2,7 +2,8 @@
|
||||
Library module for decman.
|
||||
"""
|
||||
|
||||
import pwd
|
||||
import threading
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import json
|
||||
@@ -755,14 +756,23 @@ class Pacman:
|
||||
if not packages:
|
||||
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:
|
||||
subprocess.run(conf.commands.install_pkgs(packages), check=True)
|
||||
subprocess.run(conf.commands.set_as_explicitly_installed(packages),
|
||||
check=True,
|
||||
capture_output=conf.suppress_command_output)
|
||||
except subprocess.CalledProcessError as error:
|
||||
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]):
|
||||
"""
|
||||
@@ -771,12 +781,14 @@ class Pacman:
|
||||
if not deps:
|
||||
return
|
||||
|
||||
try:
|
||||
subprocess.run(conf.commands.install_deps(deps), check=True)
|
||||
except subprocess.CalledProcessError as error:
|
||||
returncode, output = echo_and_capture_command(
|
||||
conf.commands.install_deps(deps))
|
||||
if returncode != 0:
|
||||
raise err.UserFacingError(
|
||||
"Failed to install packages as dependencies using pacman."
|
||||
) from error
|
||||
f"Failed to install packages as dependencies using pacman. Process exited with code {returncode}."
|
||||
)
|
||||
if conf.print_pacman_output_highlights:
|
||||
print_highlighted_pacman_messages(output)
|
||||
|
||||
def install_files(self, files: list[str], as_explicit: list[str]):
|
||||
"""
|
||||
@@ -786,9 +798,16 @@ class Pacman:
|
||||
if not files:
|
||||
return
|
||||
|
||||
try:
|
||||
subprocess.run(conf.commands.install_files(files), check=True)
|
||||
returncode, output = echo_and_capture_command(
|
||||
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:
|
||||
subprocess.run(
|
||||
conf.commands.set_as_explicitly_installed(as_explicit),
|
||||
@@ -799,17 +818,20 @@ class Pacman:
|
||||
print_error("Output:")
|
||||
print_continuation(error.output)
|
||||
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):
|
||||
"""
|
||||
Upgrades all packages.
|
||||
"""
|
||||
try:
|
||||
subprocess.run(conf.commands.upgrade(), check=True)
|
||||
except subprocess.CalledProcessError as error:
|
||||
returncode, output = echo_and_capture_command(conf.commands.upgrade())
|
||||
if returncode != 0:
|
||||
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]):
|
||||
"""
|
||||
@@ -817,11 +839,95 @@ class Pacman:
|
||||
"""
|
||||
if not packages:
|
||||
return
|
||||
try:
|
||||
subprocess.run(conf.commands.remove(packages), check=True)
|
||||
except subprocess.CalledProcessError as error:
|
||||
|
||||
returncode, output = echo_and_capture_command(
|
||||
conf.commands.remove(packages))
|
||||
if returncode != 0:
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user