script: add generate-builtin-terminfo.py

This script parses a terminfo source file and generates a C header
file with a static struct containing terminfo names and their values.

The table is sorted on the capability names
This commit is contained in:
Daniel Eklöf 2022-01-02 11:06:09 +01:00
parent 467e4f2ef8
commit b8fc56ecb4
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
2 changed files with 213 additions and 1 deletions

View file

@ -136,6 +136,16 @@ version = custom_target(
output: 'version.h', output: 'version.h',
command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@']) command: [env, 'LC_ALL=C', generate_version_sh, meson.project_version(), '@CURRENT_SOURCE_DIR@', '@OUTPUT@'])
python = find_program('python3', native: true)
generate_builtin_terminfo_py = files('scripts/generate-builtin-terminfo.py')
foot_terminfo = files('foot.info')
builtin_terminfo = custom_target(
'generate_builtin_terminfo',
output: 'foot-terminfo.h',
command: [python, generate_builtin_terminfo_py,
'@default_terminfo@', foot_terminfo, 'foot', '@OUTPUT@']
)
common = static_library( common = static_library(
'common', 'common',
'log.c', 'log.h', 'log.c', 'log.h',
@ -213,7 +223,7 @@ executable(
'url-mode.c', 'url-mode.h', 'url-mode.c', 'url-mode.h',
'user-notification.c', 'user-notification.h', 'user-notification.c', 'user-notification.h',
'wayland.c', 'wayland.h', 'wayland.c', 'wayland.h',
wl_proto_src + wl_proto_headers, version, builtin_terminfo, wl_proto_src + wl_proto_headers, version,
dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc, dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc,
tllist, fcft], tllist, fcft],
link_with: pgolib, link_with: pgolib,

View file

@ -0,0 +1,202 @@
#!/usr/bin/env python3
import argparse
import re
import sys
from typing import Dict, Union
class Capability:
def __init__(self, name: str, value: Union[bool, int, str]):
self._name = name
self._value = value
@property
def name(self) -> str:
return self._name
@property
def value(self) -> Union[bool, int, str]:
return self._value
def __lt__(self, other):
return self._name < other._name
def __le__(self, other):
return self._name <= other._name
def __eq__(self, other):
return self._name == other._name
def __ne__(self, other):
return self._name != other._name
def __gt__(self, other):
return self._name > other._name
def __ge__(self, other):
return self._name >= other._name
class BoolCapability(Capability):
def __init__(self, name: str):
super().__init__(name, True)
class IntCapability(Capability):
pass
class StringCapability(Capability):
pass
class Fragment:
def __init__(self, name: str, description: str):
self._name = name
self._description = description
self._caps = {}
@property
def name(self) -> str:
return self._name
@property
def description(self) -> str:
return self._description
@property
def caps(self) -> Dict[str, Capability]:
return self._caps
def add_capability(self, cap: Capability):
assert cap.name not in self._caps
self._caps[cap.name] = cap
def del_capability(self, name: str):
del self._caps[name]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('source_entry_name')
parser.add_argument('source', type=argparse.FileType('r'))
parser.add_argument('target_entry_name')
parser.add_argument('target', type=argparse.FileType('w'))
opts = parser.parse_args()
source_entry_name = opts.source_entry_name
target_entry_name = opts.target_entry_name
source = opts.source
target = opts.target
lines = []
for l in source.readlines():
l = l.strip()
if l.startswith('#'):
continue
lines.append(l)
fragments = {}
cur_fragment = None
for m in re.finditer(
r'(?P<name>(?P<entry_name>[-+\w@]+)\|(?P<entry_desc>.+?),)|'
r'(?P<bool_cap>(?P<bool_name>\w+),)|'
r'(?P<int_cap>(?P<int_name>\w+)#(?P<int_val>(0x)?[0-9a-fA-F]+),)|'
r'(?P<str_cap>(?P<str_name>\w+)=(?P<str_val>(.+?)),)',
''.join(lines)):
if m.group('name') is not None:
name = m.group('entry_name')
description = m.group('entry_desc')
assert name not in fragments
fragments[name] = Fragment(name, description)
cur_fragment = fragments[name]
elif m.group('bool_cap') is not None:
name = m.group('bool_name')
cur_fragment.add_capability(BoolCapability(name))
elif m.group('int_cap') is not None:
name = m.group('int_name')
value = int(m.group('int_val'), 0)
cur_fragment.add_capability(IntCapability(name, value))
elif m.group('str_cap') is not None:
name = m.group('str_name')
value = m.group('str_val')
cur_fragment.add_capability(StringCapability(name, value))
else:
assert False
# Expand use capabilities
for frag in fragments.values():
for cap in frag.caps.values():
if cap.name == 'use':
use_frag = fragments[cap.value]
for use_cap in use_frag.caps.values():
frag.add_capability(use_cap)
frag.del_capability(cap.name)
break
entry = fragments[source_entry_name]
try:
entry.del_capability('RGB')
except KeyError:
pass
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
entry.add_capability(StringCapability('TN', target_entry_name))
target.write('#pragma once\n')
target.write('\n')
# target.write('enum terminfo_capability_type {\n')
# target.write(' TERMINFO_CAP_BOOL,\n')
# target.write(' TERMINFO_CAP_INT,\n')
# target.write(' TERMINFO_CAP_STRING,\n')
# target.write('};\n')
# target.write('\n')
target.write('struct foot_terminfo_entry {\n')
target.write(' const char *name;\n')
# target.write(' enum terminfo_capability_type type;\n')
target.write(' const char *value;\n')
target.write('};\n')
target.write('\n')
target.write(
'static const struct foot_terminfo_entry terminfo_capabilities[] = {\n')
for cap in sorted(entry.caps.values()):
if isinstance(cap, BoolCapability):
continue
name = cap.name
value = str(cap.value)
# Expand \E to literal ESC in non-parameterized capabilities
if '%' not in value:
value = re.sub(r'\\E([0-7])', r'\\033" "\1', value)
value = re.sub(r'\\E', r'\\033', value)
else:
# Need to double-escape \E in C string literals
value = value.replace('\\E', '\\\\E')
# Dont escape :
value = value.replace('\\:', ':')
# Do escape ‘“‘
name = name.replace('"', '\"')
value = value.replace('"', '\"')
target.write(f' {{"{name}", "{value}"}},\n')
target.write('};\n')
if __name__ == '__main__':
sys.exit(main())