from pathlib import Path
from dataclasses import dataclass
import argparse
import enum
import os
import re
import sys
import functools
import json
import jinja2
import jinja2.environment
def sanitize(s):
s = re.sub(r"[-‐\t: \\|/.\n,()]", "", s)
s = re.sub(r"[+]", "Plus", s)
s = re.sub(r"[-]", "Minus", s)
numbers = {
"1": "One",
"2": "Two",
"3": "Three",
"4": "Four",
"5": "Five",
"6": "Six",
"7": "Seven",
"8": "Eight",
"9": "Nine",
"0": "Zero",
}
s = functools.reduce(
lambda s, kv: re.sub(rf"^({kv[0]})", kv[1], s), numbers.items(), s
)
return s
def sanitize_keyboard(s):
map = {
"!": "ExclamationMark",
"@": "At",
"/": "Slash",
"#": "Hash",
"$": "Dollar",
"%": "Percent",
"^": "Caret",
"&": "Ampersand",
"*": "Star",
"[": "OpenBracket",
"]": "CloseBracket",
"{": "OpenBrace",
"}": "CloseBrace",
"(": "OpenParenthesis",
")": "CloseParenthesis",
"'": "SingleQuote",
'"': "DoubleQuote",
"<": "LessThan",
">": "GreaterThan",
" and ": "And",
" ": "",
",": "Comma",
"-": "Minus",
"+": "Plus",
"|": "Pipe",
"?": "QuestionMark",
"=": "Equals",
"\\": "Backslash",
"~": "Tilde",
";": "Semicolon",
":": "Colon",
".": "Period",
}
s = functools.reduce(lambda s, kv: s.replace(kv[0], kv[1]), map.items(), s)
numbers = {
"1": "One",
"2": "Two",
"3": "Three",
"4": "Four",
"5": "Five",
"6": "Six",
"7": "Seven",
"8": "Eight",
"9": "Nine",
"0": "Zero",
}
s = functools.reduce(
lambda s, kv: re.sub(rf"^({kv[0]})", kv[1], s), numbers.items(), s
)
return s
class UsageType(enum.StrEnum):
LINEAR_CONTROL = "LC"
ON_OFF_CONTROL = "OOC"
MOMENTARY_CONTROL = "MC"
ONE_SHOT_CONTROL = "OSC"
RETRIGGER_CONTROL = "RTC"
SELECTOR = "Sel"
STATIC_VALUE = "SV"
STATIC_FLAG = "SF"
DYNAMIC_VALUE = "DV"
DYNAMIC_FLAG = "DF"
NAMED_ARRAY = "NAry"
APPLICATION_COLLECTION = "CA"
LOGICAL_COLLECTION = "CL"
PHYSICAL_COLLECTION = "CP"
USAGE_SWITCH = "US"
USAGE_MODIFIER = "UM"
BUFFERED_BYTES = "BufferedBytes"
class UsagePageType(enum.StrEnum):
DEFINED = "Defined"
GENERATED = "Generated"
@dataclass
class UsageId:
printable: str name: str value: int
usage_types: list[UsageType]
@dataclass
class UsagePage:
printable: str
name: str
value: int
usages: list[UsageId]
usage_page_type: UsagePageType
name_prefix: str | None
def parse_usage_id(u) -> UsageId:
printable = u["Name"].replace("\\", "")
name = sanitize(printable)
value = int(u["Id"])
types = [UsageType(k) for k in u["Kinds"]]
usage = UsageId(printable=printable, name=name, value=value, usage_types=types)
return usage
def parse_usage_page(up) -> UsagePage:
printable = up["Name"]
name = sanitize(printable)
value = int(up["Id"])
usages = sorted([parse_usage_id(u) for u in up["UsageIds"]], key=lambda u: u.value)
uptype = UsagePageType(up["Kind"])
name_prefix = None
if uptype == UsagePageType.GENERATED:
name_prefix = up["UsageIdGenerator"]["NamePrefix"]
if name_prefix.lower() == "enum":
name_prefix += "erate"
usage_page = UsagePage(
printable=printable,
name=name,
value=value,
usages=usages,
usage_page_type=uptype,
name_prefix=name_prefix,
)
return usage_page
def parse_data_files(datadir: Path):
usage_pages = []
for datafile in datadir.glob("*.json"):
js = json.load(open(datafile, "r"))
ups = [parse_usage_page(up) for up in js["UsagePages"]]
usage_pages.extend(ups)
unicode_page = UsagePage(
printable="Unicode",
name="Unicode",
value=0x10,
usages=[],
usage_page_type=UsagePageType.GENERATED,
name_prefix="codepoint",
)
usage_pages.append(unicode_page)
return sorted(usage_pages, key=lambda up: up.value)
def generate_source(
usage_pages: list[UsagePage], template: str
) -> jinja2.environment.TemplateStream:
data = {}
data["usage_pages"] = usage_pages
loader: jinja2.BaseLoader
if template == "-":
loader = jinja2.FunctionLoader(lambda _: sys.stdin.read())
filename = "<stdin>"
else:
path = Path(template)
assert path.exists(), f"Failed to find template {path}"
filename = path.name
loader = jinja2.FileSystemLoader(os.fspath(path.parent))
env = jinja2.Environment(
loader=loader,
trim_blocks=True,
lstrip_blocks=True,
)
jtemplate = env.get_template(filename)
return jtemplate.stream(data)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--output", default="-")
parser.add_argument(
"--datadir",
type=Path,
default=Path("data"),
help="Path to the directory containing the HUT json files",
)
parser.add_argument("template", type=Path, help="The jinja template file")
args = parser.parse_args()
assert args.datadir.exists()
assert args.template.exists()
hut = parse_data_files(args.datadir)
stream = generate_source(usage_pages=hut, template=args.template)
file = sys.stdout if args.output == "-" else open(args.output, "w")
stream.dump(file)