dotfiles/talon/user/community/core/user_settings.py

107 lines
3.3 KiB
Python

import csv
import os
from pathlib import Path
from typing import IO, Callable
from talon import resource
# NOTE: This method requires this module to be one folder below the top-level
# community/knausj folder.
SETTINGS_DIR = Path(__file__).parents[1] / "settings"
SETTINGS_DIR.mkdir(exist_ok=True)
CallbackT = Callable[[dict[str, str]], None]
DecoratorT = Callable[[CallbackT], CallbackT]
def read_csv_list(
f: IO, headers: tuple[str, str], is_spoken_form_first: bool = False
) -> dict[str, str]:
rows = list(csv.reader(f))
# print(str(rows))
mapping = {}
if len(rows) >= 2:
actual_headers = rows[0]
if not actual_headers == list(headers):
print(
f'"{f.name}": Malformed headers - {actual_headers}.'
+ f" Should be {list(headers)}. Ignoring row."
)
for row in rows[1:]:
if len(row) == 0:
# Windows newlines are sometimes read as empty rows. :champagne:
continue
if len(row) == 1:
output = spoken_form = row[0]
else:
if is_spoken_form_first:
spoken_form, output = row[:2]
else:
output, spoken_form = row[:2]
if len(row) > 2:
print(
f'"{f.name}": More than two values in row: {row}.'
+ " Ignoring the extras."
)
# Leading/trailing whitespace in spoken form can prevent recognition.
spoken_form = spoken_form.strip()
mapping[spoken_form] = output
return mapping
def write_csv_defaults(
path: Path,
headers: tuple[str, str],
default: dict[str, str] = None,
is_spoken_form_first: bool = False,
) -> None:
if not path.is_file() and default is not None:
with open(path, "w", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(headers)
for key, value in default.items():
if key == value:
writer.writerow([key])
elif is_spoken_form_first:
writer.writerow([key, value])
else:
writer.writerow([value, key])
def track_csv_list(
filename: str,
headers: tuple[str, str],
default: dict[str, str] = None,
is_spoken_form_first: bool = False,
) -> DecoratorT:
assert filename.endswith(".csv")
path = SETTINGS_DIR / filename
write_csv_defaults(path, headers, default, is_spoken_form_first)
def decorator(fn: CallbackT) -> CallbackT:
@resource.watch(str(path))
def on_update(f):
data = read_csv_list(f, headers, is_spoken_form_first)
fn(data)
return decorator
def append_to_csv(filename: str, rows: dict[str, str]):
path = SETTINGS_DIR / filename
assert filename.endswith(".csv")
with open(str(path)) as file:
line = None
for line in file:
pass
needs_newline = line is not None and not line.endswith("\n")
with open(path, "a", encoding="utf-8", newline="") as file:
writer = csv.writer(file)
if needs_newline:
writer.writerow([])
for key, value in rows.items():
writer.writerow([key] if key == value else [value, key])