107 lines
3.3 KiB
Python
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])
|