mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-06-25 03:38:59 +02:00
Implemented --log option to create logfile.log
This commit is contained in:
parent
03192dd4f3
commit
c700ff3ee7
42
main.py
42
main.py
@ -7,6 +7,8 @@ import sys
|
|||||||
import textwrap
|
import textwrap
|
||||||
import threading
|
import threading
|
||||||
import signal
|
import signal
|
||||||
|
from datetime import datetime
|
||||||
|
import pty
|
||||||
|
|
||||||
from cli.sounds import Sound
|
from cli.sounds import Sound
|
||||||
|
|
||||||
@ -60,17 +62,20 @@ def failure_with_warning_loop():
|
|||||||
print("Warnings stopped by user.")
|
print("Warnings stopped by user.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Parse --no-sound early and remove from args
|
# Parse --no-sound and --log early and remove from args
|
||||||
no_sound = False
|
no_sound = False
|
||||||
|
log_enabled = False
|
||||||
if '--no-sound' in sys.argv:
|
if '--no-sound' in sys.argv:
|
||||||
no_sound = True
|
no_sound = True
|
||||||
sys.argv.remove('--no-sound')
|
sys.argv.remove('--no-sound')
|
||||||
|
if '--log' in sys.argv:
|
||||||
|
log_enabled = True
|
||||||
|
sys.argv.remove('--log')
|
||||||
|
|
||||||
# Setup segfault handler to catch crashes
|
# Setup segfault handler to catch crashes
|
||||||
def segv_handler(signum, frame):
|
def segv_handler(signum, frame):
|
||||||
if not no_sound:
|
if not no_sound:
|
||||||
Sound.play_finished_failed_sound()
|
Sound.play_finished_failed_sound()
|
||||||
# Loop warning until interrupted
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
Sound.play_warning_sound()
|
Sound.play_warning_sound()
|
||||||
@ -94,9 +99,10 @@ if __name__ == "__main__":
|
|||||||
# Handle help invocation
|
# Handle help invocation
|
||||||
if len(sys.argv) == 1 or sys.argv[1] in ('-h', '--help'):
|
if len(sys.argv) == 1 or sys.argv[1] in ('-h', '--help'):
|
||||||
print("CyMaIS CLI – proxy to tools in ./cli/")
|
print("CyMaIS CLI – proxy to tools in ./cli/")
|
||||||
print("Usage: cymais [--no-sound] <command> [options]")
|
print("Usage: cymais [--no-sound] [--log] <command> [options]")
|
||||||
print("Options:")
|
print("Options:")
|
||||||
print(" --no-sound Suppress all sounds during execution")
|
print(" --no-sound Suppress all sounds during execution")
|
||||||
|
print(" --log Log all proxied command output to logfile.log")
|
||||||
print(" -h, --help Show this help message and exit")
|
print(" -h, --help Show this help message and exit")
|
||||||
print("Available commands:")
|
print("Available commands:")
|
||||||
for cmd in available_cli_commands:
|
for cmd in available_cli_commands:
|
||||||
@ -119,10 +125,40 @@ if __name__ == "__main__":
|
|||||||
cmd_path = os.path.join(cli_dir, f"{args.cli_command}.py")
|
cmd_path = os.path.join(cli_dir, f"{args.cli_command}.py")
|
||||||
full_cmd = [sys.executable, cmd_path] + args.cli_args
|
full_cmd = [sys.executable, cmd_path] + args.cli_args
|
||||||
|
|
||||||
|
log_file = None
|
||||||
|
if log_enabled:
|
||||||
|
log_file_path = os.path.join(script_dir, 'logfile.log')
|
||||||
|
log_file = open(log_file_path, 'a', encoding='utf-8')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if log_enabled:
|
||||||
|
# Use a pseudo-terminal to preserve color formatting
|
||||||
|
master_fd, slave_fd = pty.openpty()
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
full_cmd,
|
||||||
|
stdin=slave_fd,
|
||||||
|
stdout=slave_fd,
|
||||||
|
stderr=slave_fd,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
os.close(slave_fd)
|
||||||
|
with os.fdopen(master_fd) as m:
|
||||||
|
for line in m:
|
||||||
|
ts = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
|
||||||
|
log_file.write(f"{ts} {line}")
|
||||||
|
log_file.flush()
|
||||||
|
# Print raw line (with ANSI escapes) to stdout
|
||||||
|
print(line, end='')
|
||||||
|
proc.wait()
|
||||||
|
rc = proc.returncode
|
||||||
|
else:
|
||||||
proc = subprocess.Popen(full_cmd)
|
proc = subprocess.Popen(full_cmd)
|
||||||
proc.wait()
|
proc.wait()
|
||||||
rc = proc.returncode
|
rc = proc.returncode
|
||||||
|
|
||||||
|
if log_file:
|
||||||
|
log_file.close()
|
||||||
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
print(f"Command '{args.cli_command}' failed with exit code {rc}.")
|
print(f"Command '{args.cli_command}' failed with exit code {rc}.")
|
||||||
failure_with_warning_loop()
|
failure_with_warning_loop()
|
||||||
|
77
tests/unit/test_main.py
Normal file
77
tests/unit/test_main.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# tests/unit/test_main.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import stat
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# Insert project root into import path so we can import main.py
|
||||||
|
sys.path.insert(
|
||||||
|
0,
|
||||||
|
os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
|
||||||
|
)
|
||||||
|
|
||||||
|
import main # assumes main.py lives at the project root
|
||||||
|
|
||||||
|
|
||||||
|
class TestMainHelpers(unittest.TestCase):
|
||||||
|
def test_format_command_help_basic(self):
|
||||||
|
name = "cmd"
|
||||||
|
description = "A basic description"
|
||||||
|
output = main.format_command_help(
|
||||||
|
name, description,
|
||||||
|
indent=2, col_width=20, width=40
|
||||||
|
)
|
||||||
|
# Should start with two spaces and the command name
|
||||||
|
self.assertTrue(output.startswith(" cmd"))
|
||||||
|
# Description should appear somewhere in the wrapped text
|
||||||
|
self.assertIn("A basic description", output)
|
||||||
|
|
||||||
|
def test_list_cli_commands_filters_and_sorts(self):
|
||||||
|
# Create a temporary directory with sample files
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
open(os.path.join(tmpdir, "one.py"), "w").close()
|
||||||
|
open(os.path.join(tmpdir, "__init__.py"), "w").close()
|
||||||
|
open(os.path.join(tmpdir, "ignore.txt"), "w").close()
|
||||||
|
open(os.path.join(tmpdir, "two.py"), "w").close()
|
||||||
|
|
||||||
|
# Only 'one' and 'two' should be returned, in sorted order
|
||||||
|
commands = main.list_cli_commands(tmpdir)
|
||||||
|
self.assertEqual(commands, ["one", "two"])
|
||||||
|
|
||||||
|
def test_extract_description_via_help_with_description(self):
|
||||||
|
# Create a dummy script that prints a help description
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
script_path = os.path.join(tmpdir, "dummy.py")
|
||||||
|
with open(script_path, "w") as f:
|
||||||
|
f.write(
|
||||||
|
"#!/usr/bin/env python3\n"
|
||||||
|
"import sys\n"
|
||||||
|
"if '--help' in sys.argv:\n"
|
||||||
|
" print('usage: dummy.py [options]')\n"
|
||||||
|
" print()\n"
|
||||||
|
" print('This is a help description.')\n"
|
||||||
|
)
|
||||||
|
# Make it executable
|
||||||
|
mode = os.stat(script_path).st_mode
|
||||||
|
os.chmod(script_path, mode | stat.S_IXUSR)
|
||||||
|
|
||||||
|
description = main.extract_description_via_help(script_path)
|
||||||
|
self.assertEqual(description, "This is a help description.")
|
||||||
|
|
||||||
|
def test_extract_description_via_help_without_description(self):
|
||||||
|
# Script that has no help description
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
script_path = os.path.join(tmpdir, "empty.py")
|
||||||
|
with open(script_path, "w") as f:
|
||||||
|
f.write(
|
||||||
|
"#!/usr/bin/env python3\n"
|
||||||
|
"print('no help here')\n"
|
||||||
|
)
|
||||||
|
description = main.extract_description_via_help(script_path)
|
||||||
|
self.assertEqual(description, "-")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user