Skip to content

Instantly share code, notes, and snippets.

@Pinacolada64
Last active December 24, 2024 20:33
Show Gist options
  • Save Pinacolada64/d837151e1aec80d859872b297437e9ca to your computer and use it in GitHub Desktop.
Save Pinacolada64/d837151e1aec80d859872b297437e9ca to your computer and use it in GitHub Desktop.
A better attempt at terminal settings
import logging
import textwrap
from dataclasses import asdict, dataclass, is_dataclass, field
from enum import Enum
"""
Goal: to display a settings menu. The way I think it should work:
- Enumerate through Player atributes, get a key (LINE_ENDING)
- Get a key name common to both SettingString.LINE_ENDING (to display in the menu item) and SettingValue.LINE_ENDING
- Use that as a lookup in the Enum list?
"""
class KeyboardKeyName(str, Enum):
RETURN = "Return"
ENTER = "Enter"
def __str__(self):
return self.value
class TerminalSettingString(str, Enum):
# these are terminal settings strings to be displayed in menu
NAME = "Name"
RETURN_KEY = "Return or Enter key?"
TRANSLATION = "Character translation"
LINE_ENDING = "Line ending(s)"
SCREEN_ROWS = "Screen rows"
SCREEN_COLS = "Screen columns"
def __str__(self):
# return e.g., "Line ending(s)"
return self.value
def __repr__(self):
# return e.g., "LINE_ENDING"
return self.name
@dataclass
class Translation:
ASCII = "ASCII"
ANSI = "ANSI"
PETSCII = "PetSCII"
def __str__(self):
return self.value
@dataclass(frozen=True)
class LineEnding:
name: str
ending: str
def __repr__(self):
return self.name
def __str__(self):
return self.ending
@dataclass(frozen=True)
class ReturnKey:
name: str
def __str__(self):
return KeyboardKeyName.value
CRLineEnding = LineEnding(name="Carriage return", ending="\r")
LFLineEnding = LineEnding(name="Line feed", ending="\n")
CRLFLineEnding = LineEnding(name="Carriage return + line feed", ending="\r\n")
@dataclass
class UserSetting:
NAME: str | None = None
RETURN_KEY: KeyboardKeyName = field(default_factory=lambda: KeyboardKeyName.ENTER)
TRANSLATION: Translation = field(default_factory=lambda: Translation.ANSI)
LINE_ENDING: LineEnding = field(default_factory=lambda: CRLFLineEnding)
SCREEN_COLS: int = 40
SCREEN_ROWS: int = 25
@dataclass
class Player(object):
name: str
# copy UserSetting class here:
settings: UserSetting = field(default_factory=lambda: UserSetting())
def show_terminal_settings(self):
"""
e.g.,
TerminalSettingString....: Player.UserSetting
1. Name.....................: Generic Commodore client
2. Return or Enter key?.....: Return
3. Character translation....: PetSCII
4. Line ending(s)...........: Carriage return + line feed
5. Screen columns...........: 40
6. Screen rows..............: 25
"""
for k, v in enumerate(asdict(self.settings)):
value = getattr(self.settings, v)
value_str = repr(value) if is_dataclass(value) else value
print(
f"{k + 1}. {TerminalSettingString[v].value.ljust(25, '.')}: "
f"{value_str}"
)
def edit_settings(self):
max_options = len(TerminalSettingString)
log.debug("max_options: %i" % max_options)
self.show_terminal_settings()
while True:
choice = input(f"Edit which [1-{max_options}, Q)uit]: ")
if choice.lower()[0] == "q":
break
try:
option_number = int(choice)
# Get the TerminalSettingString enum member
setting_enum = list(TerminalSettingString)[option_number - 1]
# Determine the type of setting and get the corresponding Enum
setting_enums_dict = {
TerminalSettingString.TRANSLATION: (lambda: t for t in Translation.),
TerminalSettingString.LINE_ENDING: lambda: l for l in LineEnding,
# FIXME: finish this
}
allowed_values = list(setting_enums_dict.get(setting_enum, None))
logging.debug(allowed_values)
if allowed_values:
# Present the user with a list of valid choices for the Enum members:
print(f"Choose a value for {setting_enum.value}:")
for i, value in enumerate(allowed_values, start=1):
print(f"{i}. {value.value}")
# Get user input for the chosen value
while True:
try:
choice = int(input("Enter your choice: "))
if 1 <= choice <= len(allowed_values):
selected_value = allowed_values(choice) # Convert choice to Enum member
break
else:
print(f"Invalid choice. Please enter a number between 1 and {len(allowed_values)}.")
except ValueError:
print("Please enter a number.")
# Update the player's setting
setattr(self.settings, setting_enum.name, selected_value)
else:
# Handle settings without a predefined Enum (e.g., Name)
print(f"Setting '{setting_enum.value}' is not editable.")
except ValueError:
print("FIXME: Bad value.")
def output(self, s: str) -> None:
# print(s, end=str(self.settings.LINE_ENDING))
print(textwrap.fill(text=s, width=self.settings.SCREEN_COLS), end=self.settings.LINE_ENDING.ending)
if __name__ == "__main__":
log = logging.getLogger(__name__)
print(log)
logging.basicConfig(level=logging.DEBUG,
# format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
# https://stackoverflow.com/questions/10973362/python-logging-function-name-file-name-line-number-using-a-single-file
format = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s",
datefmt='%m-%d %H:%M')
log.setLevel(logging.DEBUG)
log.info("Running") # displays filename, line number, function name
# much better code by volca!
player = Player(name="Ryan", settings=UserSetting(NAME="Generic Commodore client",
TRANSLATION=Translation.ASCII,
LINE_ENDING=CRLFLineEnding,
RETURN_KEY=KeyboardKeyName.RETURN))
# player.show_terminal_settings()
player.edit_settings()
player.output("Demonstrating word-wrap:")
player.output("You probably want to figure out how to print with the user's CR/LF preferences without needing "
"access to the player object.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment