-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] Main program entry, logging and prompting
Add an entry point with basic checks for odev. Enable logging and prompting to the console with limited helper methods. [FIX] Pre-Commit PyLint hook version The PyLint hook was using an older version not compatible with Python 3.11. Ref: GrahamDumpleton/wrapt#196
- Loading branch information
Showing
7 changed files
with
239 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import odev.__main__ as main | ||
|
||
|
||
if __name__ == "__main__": | ||
main.main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""Entry point for the odev executable.""" | ||
|
||
|
||
import os | ||
import signal | ||
from types import FrameType | ||
from typing import Optional | ||
|
||
from odev._version import __version__ | ||
from odev.common import logging, prompt | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
# --- Handle signals and interrupts -------------------------------------------- | ||
|
||
|
||
def signal_handler(signal_number: int, frame: Optional[FrameType]): | ||
prompt.clear_line(0) # Hide control characters | ||
logger.warning(f"Received signal ({signal_number}), aborting execution") | ||
exit(signal_number) | ||
|
||
|
||
signal.signal(signal.SIGINT, signal_handler) | ||
signal.signal(signal.SIGTERM, signal_handler) | ||
|
||
|
||
# --- Main entry method -------------------------------------------------------- | ||
|
||
|
||
def main(): | ||
"""Setup wizard for odev.""" | ||
|
||
logger.debug(f"Starting odev version {__version__}") | ||
|
||
try: | ||
logger.debug("Checking runtime permissions") | ||
if os.geteuid() == 0: | ||
raise Exception("Odev should not be run as root") | ||
|
||
# TODO: Implement framework logic | ||
|
||
except Exception: | ||
logger.exception("Execution failed due to an unhandled exception") | ||
exit(1) | ||
|
||
logger.debug("Execution completed successfully") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
"""Version information for odev.""" | ||
|
||
__version__ = "0.0.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"""Common utilities for odev.""" | ||
|
||
from .logging import logging | ||
from .prompt import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
"""Logging configuration for odev. | ||
To be used with: | ||
```py | ||
from odev.logging import logging | ||
logger = logging.getLogger(__name__) | ||
``` | ||
""" | ||
|
||
import logging | ||
from logging import LogRecord | ||
from typing import Dict, Literal, Union | ||
|
||
from rich.console import Console | ||
from rich.highlighter import ReprHighlighter, _combine_regex | ||
from rich.logging import RichHandler | ||
from rich.text import Text | ||
from rich.theme import Theme | ||
|
||
|
||
__all__ = ["logging"] | ||
|
||
|
||
class OdevRichHandler(RichHandler): | ||
"""Custom `RichHandler` to show the log level as a single character. | ||
See `rich.logging.RichHandler`. | ||
""" | ||
|
||
symbols: Dict[Union[int, Literal["default"]], str] = { | ||
logging.CRITICAL: "~", | ||
logging.ERROR: "-", | ||
logging.WARNING: "!", | ||
logging.INFO: "i", | ||
logging.DEBUG: "#", | ||
"default": "?", | ||
} | ||
|
||
def get_level_text(self, record: LogRecord) -> Text: | ||
"""Get the level name from the record. | ||
:param LogRecord record: LogRecord instance. | ||
:return: A tuple of the style and level name. | ||
:rtype: Text | ||
""" | ||
|
||
symbol = self.symbols.get(record.levelno, self.symbols["default"]) | ||
level_text = super().get_level_text(record) | ||
level_text.plain = f"[{symbol}]".ljust(3) | ||
return level_text | ||
|
||
|
||
class OdevReprHighlighter(ReprHighlighter): | ||
"""Extension of `ReprHighlighter` to highlight odev version numbers.""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.highlights[-1] = _combine_regex( | ||
r"(?P<odev>odev)", | ||
r"(?P<version>([0-9]+\.)+[0-9]+)", | ||
self.highlights[-1], | ||
) | ||
|
||
|
||
console = Console( | ||
highlighter=OdevReprHighlighter(), | ||
theme=Theme( | ||
{ | ||
"repr.odev": "bold cyan", | ||
"repr.version": "bold cyan", | ||
"logging.level.critical": "bold red", | ||
"logging.level.error": "bold red", | ||
"logging.level.warning": "bold yellow", | ||
"logging.level.info": "bold cyan", | ||
"logging.level.debug": "bold bright_black", | ||
} | ||
), | ||
) | ||
|
||
logging.basicConfig( | ||
level="NOTSET", | ||
format="%(message)s", | ||
handlers=[ | ||
OdevRichHandler( | ||
rich_tracebacks=True, | ||
tracebacks_show_locals=True, | ||
show_time=False, | ||
console=console, | ||
markup=True, | ||
) | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"""Prompt for user input.""" | ||
|
||
from pathlib import Path | ||
from typing import Optional | ||
|
||
from InquirerPy import get_style, inquirer | ||
from InquirerPy.validator import PathValidator | ||
from prompt_toolkit.validation import ValidationError | ||
|
||
|
||
__all__ = ["directory", "clear_line"] | ||
|
||
|
||
MARK = "[?]" | ||
STYLE = get_style( | ||
style_override=False, | ||
style={ | ||
"questionmark": "fg:#af5faf bold", | ||
"answermark": "fg:#af5faf bold", | ||
"answer": "#af5faf", | ||
"input": "#00afaf", | ||
"validator": "fg:red bg: bold", | ||
}, | ||
) | ||
|
||
|
||
class PurportedPathValidator(PathValidator): | ||
"""Path validator that doesn't check if the path exists.""" | ||
|
||
def validate(self, document) -> None: | ||
"""Check if user input is a valid path.""" | ||
|
||
path = Path(document.text).expanduser() | ||
|
||
if self._is_file and path.is_dir(): | ||
raise ValidationError( | ||
message=self._message, | ||
cursor_position=document.cursor_position, | ||
) | ||
|
||
if self._is_dir and path.is_file(): | ||
raise ValidationError( | ||
message=self._message, | ||
cursor_position=document.cursor_position, | ||
) | ||
|
||
|
||
def clear_line(count: int = 1) -> None: | ||
"""Clear up a number of lines from the terminal. | ||
If a count is provided, the same amount of lines will be cleared. | ||
:param int count: Number of lines to clear | ||
""" | ||
|
||
print("\x1b[F".join(["\x1b[2K\x1b[0G" for _ in range(count + 1)]), end="") | ||
|
||
|
||
def directory(message: str, default: str = None) -> Optional[str]: | ||
"""Prompt for a directory path. | ||
:param message: Question to ask the user | ||
:param default: Set the default text value of the prompt | ||
:return: Path to the directory to use | ||
:rtype: str or None | ||
""" | ||
|
||
try: | ||
path = inquirer.filepath( | ||
message=message, | ||
default=default, | ||
only_directories=True, | ||
validate=PurportedPathValidator( | ||
is_dir=True, | ||
message="Path must be a directory", | ||
), | ||
raise_keyboard_interrupt=True, | ||
style=STYLE, | ||
amark=MARK, | ||
qmark=MARK, | ||
).execute() | ||
except KeyboardInterrupt: | ||
return None | ||
|
||
return path |