Last active
February 11, 2025 06:51
-
-
Save Pinacolada64/c9adae5bdb9404224daf2b5be259cb6d to your computer and use it in GitHub Desktop.
A color string parser meant to handle ANSI and Commodore terminal colors
This file contains hidden or 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
import enum | |
import logging | |
from dataclasses import dataclass | |
from enum import Enum | |
import codecs | |
import cbmcodecs2 | |
from colorama import init, Fore | |
def replace_with_color_code(exception): | |
""" | |
Handles UnicodeEncodeError by replacing the unmapped character | |
with the corresponding CBM color code. | |
""" | |
logging.debug("got exception: %s" % exception) | |
logging.debug("got exception.start: %s" % exception.start) | |
try: | |
# Get the color code character | |
color_code = chr(int(CBMColors[exception.object[exception.start]].value)) | |
# Return the color code character and the position to continue encoding | |
return color_code, exception.start + 1 # Increment start to skip the color code character | |
except IndexError: | |
# If the character is not a recognized color, return a placeholder | |
return '?', exception.start + 1 # Increment start to skip the unmapped character | |
class Translation(str, Enum): | |
PETSCII = "PETSCII" | |
ANSI = "ANSI" | |
def process_color_code(color_name: str, translation: Translation) -> str: | |
""" | |
Process a color code (e.g., RED) and return the appropriate replacement | |
based on the player's translation (PETSCII or ANSI). | |
""" | |
try: | |
if translation == Translation.PETSCII: | |
return chr(getattr(CBMColors, color_name).value) # PETSCII single-byte color codes | |
elif translation == Translation.ANSI: | |
color_code = getattr(ColoramaColors, color_name).value | |
return color_code if color_code != 'None' else '' | |
except AttributeError: | |
logging.warning(f"Unknown color: {color_name}") | |
return '' | |
@dataclass | |
class Player: | |
translation: Translation = Translation.PETSCII | |
def output(self, string: str): | |
""" | |
Outputs the given string, replacing color codes within braces {} | |
with the corresponding CBM or ANSI color codes, depending on the translation. | |
""" | |
output_text = [] | |
i = 0 | |
while i < len(string): | |
if string[i] == '{': | |
end_brace = string.find('}', i) | |
if end_brace != -1: | |
color_name = string[i + 1:end_brace].strip().upper() | |
output_text.append(process_color_code(color_name, self.translation)) | |
i = end_brace # Skip to after the closing brace | |
else: | |
output_text.append(string[i]) # Append unmatched '{' | |
else: | |
output_text.append(string[i]) | |
i += 1 | |
final_output = ''.join(output_text) | |
print(final_output) | |
return final_output | |
@dataclass | |
class Player: | |
translation: Translation = Translation.PETSCII | |
def output(self, string: str): | |
""" | |
Outputs the given string, replacing color codes within braces {} | |
with the corresponding CBM or ANSI color codes, depending on the translation. | |
""" | |
result = "" # Initialize result as a string for simplicity | |
for i in range(len(string)): | |
if string[i] == '{': | |
end_brace = string.find('}', i) | |
if end_brace != -1: | |
color_name = string[i + 1:end_brace].upper() | |
try: | |
if self.translation == Translation.PETSCII: | |
color_code = chr(getattr(CBMColors, color_name).value) | |
result += color_code # PETSCII uses single-byte color codes | |
elif self.translation == Translation.ANSI: | |
color_code = getattr(ColoramaColors, color_name).value | |
if color_code != 'None': | |
result += color_code # ANSI uses sequences from Colorama | |
except AttributeError: | |
logging.warning(f"Unknown color: {color_name}") | |
i = end_brace # Skip to the closing brace | |
else: | |
result += string[i] | |
else: | |
result += string[i] | |
print(result) # Output the colorized string directly to the terminal | |
return result | |
class CBMColors(int, Enum): | |
BLACK = 144 | |
WHITE = 5 | |
RED = 28 | |
CYAN = 159 | |
PURPLE = 156 | |
DARK_GREEN = 30 | |
DARK_BLUE = 31 | |
YELLOW = 158 | |
ORANGE = 129 | |
BROWN = 149 | |
LIGHT_RED = 150 | |
DARK_GRAY = 151 | |
MEDIUM_GRAY = 152 | |
LIGHT_GREEN = 153 | |
LIGHT_BLUE = 154 | |
LIGHT_GRAY = 155 | |
RESET = MEDIUM_GRAY | |
class ColoramaColors(str, Enum): | |
BLACK = Fore.BLACK | |
WHITE = Fore.WHITE | |
RED = Fore.RED | |
CYAN = Fore.CYAN | |
PURPLE = None | |
DARK_GREEN = Fore.GREEN | |
DARK_BLUE = Fore.BLUE | |
YELLOW = Fore.YELLOW | |
ORANGE = None | |
BROWN = None | |
LIGHT_RED = Fore.LIGHTRED_EX | |
DARK_GRAY = Fore.LIGHTBLACK_EX | |
MEDIUM_GRAY = Fore.LIGHTBLACK_EX | |
LIGHT_GREEN = Fore.LIGHTGREEN_EX | |
LIGHT_BLUE = Fore.LIGHTBLUE_EX | |
LIGHT_GRAY = Fore.LIGHTBLACK_EX | |
RESET = Fore.RESET | |
if __name__ == '__main__': | |
# Set up logging | |
logging.basicConfig(format='%(levelname)10s | %(funcName)15s() | %(message)s', level=logging.DEBUG) | |
# Register the codec error handler | |
# codecs.register_error('custom_replace', replace_with_color_code) | |
# Modify the decoding table to add PetSCII color codes directly | |
# This is a simplified example and may need further refinement | |
modified_decoding_table = list(cbmcodecs2.petscii_c64en_lc.decoding_table) | |
# Add CBMColors Enum values as individual elements into the decoding table | |
for color in CBMColors: | |
modified_decoding_table[color.value] = chr(color.value) | |
modified_decoding_table = tuple(modified_decoding_table) | |
# Create a new codec instance with the modified table | |
class MyPetsciiCodec(cbmcodecs2.petscii_c64en_lc.Codec): | |
is_text_encoding = None | |
incrementaldecoders = None | |
incrementalencoders = None | |
encoding = 'my_petscii_c64en_lc' | |
decode_table = modified_decoding_table | |
def __init__(self, encoding_name): | |
self.encoding_name = encoding_name | |
def encoders(self, input, errors='strict'): | |
return super().encode(input, errors) | |
def decoders(self, input, errors='strict'): | |
return super().decode(input, errors) | |
def streamreaders(self, stream, errors='strict'): | |
return super().streamreader(stream, errors) | |
def streamwriters(self, stream, errors='strict'): | |
return super().streamwriter(stream, errors) | |
# Register the new codec | |
# Constants for readability and reusability | |
MY_PETSCII_ENCODING = 'my_petscii_c64en_lc' | |
""" | |
class MyPetsciiCodec(cbmcodecs2.petscii_c64en_lc.Codec): | |
is_text_encoding = None | |
incremental_decoders = None # Renamed for clarity | |
incremental_encoders = None # Renamed for consistency | |
encoding = MY_PETSCII_ENCODING # Use shared constant for encoding | |
decode_table = modified_decoding_table | |
def __init__(self, encoding_name): | |
pass # Assuming the body remains unchanged | |
# Renamed methods for better understanding | |
def encode(self, input, errors='strict'): | |
pass | |
def decode(self, input, errors='strict'): | |
pass | |
def create_stream_reader(self, stream, errors='strict'): | |
pass | |
def create_stream_writer(self, stream, errors='strict'): | |
pass | |
""" | |
# Using a dictionary to improve clarity during registration | |
codecs.register( | |
name=MY_PETSCII_ENCODING, | |
encoder=MyPetsciiCodec.encode, | |
decoder=MyPetsciiCodec.decode, | |
incrementalencoder=MyPetsciiCodec.incremental_encoders, | |
incrementaldecoder=MyPetsciiCodec.incremental_decoders, | |
streamreader=MyPetsciiCodec.create_stream_reader, | |
streamwriter=MyPetsciiCodec.create_stream_writer, | |
_is_text_encoding=MyPetsciiCodec.is_text_encoding | |
) | |
# Test the modified codec | |
try: | |
print("useless pause") | |
black_test = chr(144).encode(encoding="my_petscii_c64en_lc") | |
print(black_test.__repr__()) | |
except LookupError as e: | |
print(f"Character encoding {e} was not found in the modified table.") | |
except UnicodeEncodeError as e: | |
logging.warning(f"Error encoding black character: {e}") | |
# Initialize colorama | |
init() | |
player = Player(translation=Translation.PETSCII) | |
# Example usage | |
color_string = ("{red}Red {orange}Orange {yellow}Yellow {dark_green}Green {dark_blue}Blue {purple}Purple " | |
"{cyan}Cyan {light_green}-{dark_green}-{reset}! {plum}") | |
player.output(color_string) |
Most of this was suggested by JetBrains AI.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A bunch of stuff that the codecs library needs, apparently. When I try to run the PetSCII string through .encode():
This version takes a different tactic, subclasses the codec decoding table. I couldn't get the error_handler to work in the previous version.