2022-01-02 11:06:09 +01:00
|
|
|
|
#!/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):
|
2022-01-13 13:37:44 +01:00
|
|
|
|
def __init__(self, name: str, value: str):
|
2024-04-27 09:38:55 +02:00
|
|
|
|
# see terminfo(5) for valid escape sequences
|
2022-10-04 21:42:13 +02:00
|
|
|
|
|
2024-04-27 09:38:55 +02:00
|
|
|
|
# Control characters
|
|
|
|
|
|
def translate_ctrl_chr(m):
|
|
|
|
|
|
ctrl = m.group(1)
|
|
|
|
|
|
if ctrl == '?':
|
|
|
|
|
|
return chr(0x7f)
|
|
|
|
|
|
return chr(ord(ctrl) - ord('@'))
|
|
|
|
|
|
value = re.sub('\^([@A-Z[\\\\\]^_?])', translate_ctrl_chr, value)
|
2022-10-04 21:42:13 +02:00
|
|
|
|
|
2024-04-27 09:38:55 +02:00
|
|
|
|
# Ensure e.g. \E7 (or \e7) doesn’t get translated to “\0337”,
|
|
|
|
|
|
# which would be interpreted as octal 337 by the C compiler
|
|
|
|
|
|
value = re.sub(r'(\\E|\\e)([0-7])', r'\\033" "\2', value)
|
2022-10-04 21:42:13 +02:00
|
|
|
|
|
2024-04-27 09:38:55 +02:00
|
|
|
|
# Replace \E and \e with ESC
|
|
|
|
|
|
value = re.sub(r'\\E|\\e', r'\\033', value)
|
2022-09-27 19:05:56 +02:00
|
|
|
|
|
2024-04-27 09:38:55 +02:00
|
|
|
|
# Unescape ,:^
|
|
|
|
|
|
value = re.sub(r'\\(,|:|\^)', r'\1', value)
|
2022-01-13 13:37:44 +01:00
|
|
|
|
|
2024-04-27 09:38:55 +02:00
|
|
|
|
# Replace \s with space
|
|
|
|
|
|
value = value.replace('\\s', ' ')
|
|
|
|
|
|
|
|
|
|
|
|
# Let \\, \n, \r, \t, \b and \f "fall through", to the C string literal
|
|
|
|
|
|
|
|
|
|
|
|
if re.search(r'\\l', value):
|
|
|
|
|
|
raise NotImplementedError('\\l escape sequence')
|
2022-01-13 13:37:44 +01:00
|
|
|
|
|
|
|
|
|
|
super().__init__(name, value)
|
2022-01-02 11:06:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2022-01-02 20:22:01 +01:00
|
|
|
|
entry.add_capability(IntCapability('Co', 256))
|
2022-01-02 11:06:09 +01:00
|
|
|
|
entry.add_capability(StringCapability('TN', target_entry_name))
|
2022-09-28 21:09:35 +02:00
|
|
|
|
entry.add_capability(StringCapability('name', target_entry_name))
|
2022-01-02 20:22:01 +01:00
|
|
|
|
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
|
2022-01-02 11:06:09 +01:00
|
|
|
|
|
2022-01-13 13:37:44 +01:00
|
|
|
|
terminfo_parts = []
|
2022-01-02 11:06:09 +01:00
|
|
|
|
for cap in sorted(entry.caps.values()):
|
|
|
|
|
|
name = cap.name
|
|
|
|
|
|
value = str(cap.value)
|
|
|
|
|
|
|
2022-01-13 13:37:44 +01:00
|
|
|
|
# Escape ‘“‘
|
2022-01-02 11:06:09 +01:00
|
|
|
|
name = name.replace('"', '\"')
|
|
|
|
|
|
value = value.replace('"', '\"')
|
2022-01-04 22:01:36 +01:00
|
|
|
|
|
2022-01-13 13:37:44 +01:00
|
|
|
|
terminfo_parts.append(name)
|
2022-01-04 22:01:36 +01:00
|
|
|
|
if isinstance(cap, BoolCapability):
|
2022-01-13 13:37:44 +01:00
|
|
|
|
terminfo_parts.append('')
|
2022-01-04 22:01:36 +01:00
|
|
|
|
else:
|
2022-01-13 13:37:44 +01:00
|
|
|
|
terminfo_parts.append(value)
|
|
|
|
|
|
|
|
|
|
|
|
terminfo = '\\0" "'.join(terminfo_parts)
|
2022-01-02 11:06:09 +01:00
|
|
|
|
|
2022-01-13 13:37:44 +01:00
|
|
|
|
target.write('#pragma once\n')
|
|
|
|
|
|
target.write('\n')
|
|
|
|
|
|
target.write(f'static const char terminfo_capabilities[] = "{terminfo}";')
|
|
|
|
|
|
target.write('\n')
|
2022-01-02 11:06:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
sys.exit(main())
|