Python: make ruff & black happy
parent
c6716461d1
commit
1a4a89a749
|
@ -140,12 +140,12 @@ def update_registry(registry_path: Path, doc_dir: Path, updates: Sequence[str]):
|
|||
print(f"[ERROR] “{old}” not found and has no update.")
|
||||
exit_code |= ExitCode.MISSING_UPDATES
|
||||
if exit_code:
|
||||
print(f"[ERROR] Processing interrupted: please fix the errors above.")
|
||||
print("[ERROR] Processing interrupted: please fix the errors above.")
|
||||
exit(exit_code.value)
|
||||
# Write changes
|
||||
with registry_path.open("wt", encoding="utf-8") as fd:
|
||||
fd.write(f"# WARNING: This file is autogenerated by: {RELATIVE_SCRIPT_PATH}\n")
|
||||
fd.write(f"# Do not edit manually.\n")
|
||||
fd.write("# Do not edit manually.\n")
|
||||
yaml.dump(
|
||||
registry,
|
||||
fd,
|
||||
|
|
|
@ -1,47 +1,55 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import re, sys, itertools
|
||||
import re
|
||||
import sys
|
||||
import itertools
|
||||
|
||||
import perfect_hash
|
||||
|
||||
pattern = re.compile(r'^#define\s+XKB_KEY_(?P<name>\w+)\s+(?P<value>0x[0-9a-fA-F]+)\s')
|
||||
pattern = re.compile(r"^#define\s+XKB_KEY_(?P<name>\w+)\s+(?P<value>0x[0-9a-fA-F]+)\s")
|
||||
matches = [pattern.match(line) for line in open(sys.argv[1])]
|
||||
entries = [(m.group("name"), int(m.group("value"), 16)) for m in matches if m]
|
||||
|
||||
entries_isorted = sorted(entries, key=lambda e: e[0].lower())
|
||||
entries_kssorted = sorted(entries, key=lambda e: e[1])
|
||||
|
||||
print('''
|
||||
print(
|
||||
"""
|
||||
/**
|
||||
* This file comes from libxkbcommon and was generated by makekeys.py
|
||||
* You can always fetch the latest version from:
|
||||
* https://raw.github.com/xkbcommon/libxkbcommon/master/src/ks_tables.h
|
||||
*/
|
||||
''')
|
||||
"""
|
||||
)
|
||||
|
||||
entry_offsets = {}
|
||||
|
||||
print('''
|
||||
print(
|
||||
"""
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Woverlength-strings"
|
||||
#endif
|
||||
static const char *keysym_names =
|
||||
'''.strip())
|
||||
""".strip()
|
||||
)
|
||||
offs = 0
|
||||
for (name, _) in entries_isorted:
|
||||
for name, _ in entries_isorted:
|
||||
entry_offsets[name] = offs
|
||||
print(' "{name}\\0"'.format(name=name))
|
||||
offs += len(name) + 1
|
||||
print('''
|
||||
print(
|
||||
"""
|
||||
;
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
'''.strip())
|
||||
""".strip()
|
||||
)
|
||||
|
||||
|
||||
template = r'''
|
||||
template = r"""
|
||||
static const uint16_t keysym_name_G[] = {
|
||||
$G
|
||||
};
|
||||
|
@ -63,27 +71,39 @@ keysym_name_perfect_hash(const char *key)
|
|||
keysym_name_G[keysym_name_hash_f(key, "$S2")]
|
||||
) % $NG;
|
||||
}
|
||||
'''
|
||||
print(perfect_hash.generate_code(
|
||||
"""
|
||||
print(
|
||||
perfect_hash.generate_code(
|
||||
keys=[name for name, value in entries_isorted],
|
||||
template=template,
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
print('''
|
||||
print(
|
||||
"""
|
||||
struct name_keysym {
|
||||
xkb_keysym_t keysym;
|
||||
uint32_t offset;
|
||||
};\n''')
|
||||
};\n"""
|
||||
)
|
||||
|
||||
|
||||
def print_entries(x):
|
||||
for (name, value) in x:
|
||||
print(' {{ 0x{value:08x}, {offs} }}, /* {name} */'.format(offs=entry_offsets[name], value=value, name=name))
|
||||
for name, value in x:
|
||||
print(
|
||||
" {{ 0x{value:08x}, {offs} }}, /* {name} */".format(
|
||||
offs=entry_offsets[name], value=value, name=name
|
||||
)
|
||||
)
|
||||
|
||||
print('static const struct name_keysym name_to_keysym[] = {')
|
||||
|
||||
print("static const struct name_keysym name_to_keysym[] = {")
|
||||
print_entries(entries_isorted)
|
||||
print('};\n')
|
||||
print("};\n")
|
||||
|
||||
# *.sort() is stable so we always get the first keysym for duplicate
|
||||
print('static const struct name_keysym keysym_to_name[] = {')
|
||||
print_entries(next(g[1]) for g in itertools.groupby(entries_kssorted, key=lambda e: e[1]))
|
||||
print('};')
|
||||
print("static const struct name_keysym keysym_to_name[] = {")
|
||||
print_entries(
|
||||
next(g[1]) for g in itertools.groupby(entries_kssorted, key=lambda e: e[1])
|
||||
)
|
||||
print("};")
|
||||
|
|
|
@ -9,7 +9,7 @@ import pathlib
|
|||
|
||||
|
||||
def symbols_from_map(path):
|
||||
return re.findall(r'^\s+(r?xkb_.*);', path.read_text('utf-8'), re.MULTILINE)
|
||||
return re.findall(r"^\s+(r?xkb_.*);", path.read_text("utf-8"), re.MULTILINE)
|
||||
|
||||
|
||||
if 2 > len(sys.argv) > 3:
|
||||
|
|
|
@ -92,7 +92,7 @@ else:
|
|||
from io import StringIO
|
||||
|
||||
|
||||
__version__ = '0.4.2'
|
||||
__version__ = "0.4.2"
|
||||
|
||||
|
||||
verbose = False
|
||||
|
@ -107,6 +107,7 @@ class Graph(object):
|
|||
are assigned such that the two values corresponding to an edge add up to
|
||||
the desired edge value (mod N).
|
||||
"""
|
||||
|
||||
def __init__(self, N):
|
||||
self.N = N # number of vertices
|
||||
|
||||
|
@ -170,7 +171,8 @@ class Graph(object):
|
|||
# Set new vertex's value to the desired edge value,
|
||||
# minus the value of the vertex we came here from.
|
||||
self.vertex_values[neighbor] = (
|
||||
edge_value - self.vertex_values[vertex]) % self.N
|
||||
edge_value - self.vertex_values[vertex]
|
||||
) % self.N
|
||||
|
||||
# check if all vertices have a valid value
|
||||
for vertex in range(self.N):
|
||||
|
@ -188,11 +190,12 @@ class StrSaltHash(object):
|
|||
a random string of characters, summed up, and finally modulo NG is
|
||||
taken.
|
||||
"""
|
||||
|
||||
chars = string.ascii_letters + string.digits
|
||||
|
||||
def __init__(self, N):
|
||||
self.N = N
|
||||
self.salt = ''
|
||||
self.salt = ""
|
||||
|
||||
def __call__(self, key):
|
||||
# XXX: xkbcommon modification: make the salt length a power of 2
|
||||
|
@ -200,8 +203,7 @@ class StrSaltHash(object):
|
|||
while len(self.salt) < max(len(key), 32): # add more salt as necessary
|
||||
self.salt += random.choice(self.chars)
|
||||
|
||||
return sum(ord(self.salt[i]) * ord(c)
|
||||
for i, c in enumerate(key)) % self.N
|
||||
return sum(ord(self.salt[i]) * ord(c) for i, c in enumerate(key)) % self.N
|
||||
|
||||
template = """
|
||||
def hash_f(key, T):
|
||||
|
@ -212,12 +214,14 @@ def perfect_hash(key):
|
|||
G[hash_f(key, "$S2")]) % $NG
|
||||
"""
|
||||
|
||||
|
||||
class IntSaltHash(object):
|
||||
"""
|
||||
Random hash function generator.
|
||||
Simple byte level hashing, each byte is multiplied in sequence to a table
|
||||
containing random numbers, summed tp, and finally modulo NG is taken.
|
||||
"""
|
||||
|
||||
def __init__(self, N):
|
||||
self.N = N
|
||||
self.salt = []
|
||||
|
@ -226,8 +230,7 @@ class IntSaltHash(object):
|
|||
while len(self.salt) < len(key): # add more salt as necessary
|
||||
self.salt.append(random.randint(1, self.N - 1))
|
||||
|
||||
return sum(self.salt[i] * ord(c)
|
||||
for i, c in enumerate(key)) % self.N
|
||||
return sum(self.salt[i] * ord(c) for i, c in enumerate(key)) % self.N
|
||||
|
||||
template = """
|
||||
S1 = [$S1]
|
||||
|
@ -241,14 +244,18 @@ def perfect_hash(key):
|
|||
return (G[hash_f(key, S1)] + G[hash_f(key, S2)]) % $NG
|
||||
"""
|
||||
|
||||
|
||||
def builtin_template(Hash):
|
||||
return """\
|
||||
return (
|
||||
"""\
|
||||
# =======================================================================
|
||||
# ================= Python code for perfect hash function ===============
|
||||
# =======================================================================
|
||||
|
||||
G = [$G]
|
||||
""" + Hash.template + """
|
||||
"""
|
||||
+ Hash.template
|
||||
+ """
|
||||
# ============================ Sanity check =============================
|
||||
|
||||
K = [$K]
|
||||
|
@ -257,6 +264,7 @@ assert len(K) == $NK
|
|||
for h, k in enumerate(K):
|
||||
assert perfect_hash(k) == h
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class TooManyInterationsError(Exception):
|
||||
|
@ -279,16 +287,19 @@ def generate_hash(keys, Hash=StrSaltHash):
|
|||
if not isinstance(key, str):
|
||||
raise TypeError("key a not string: %r" % key)
|
||||
if NK > 10000 and Hash == StrSaltHash:
|
||||
print("""\
|
||||
print(
|
||||
"""\
|
||||
WARNING: You have %d keys.
|
||||
Using --hft=1 is likely to fail for so many keys.
|
||||
Please use --hft=2 instead.
|
||||
""" % NK)
|
||||
"""
|
||||
% NK
|
||||
)
|
||||
|
||||
# the number of vertices in the graph G
|
||||
NG = NK + 1
|
||||
if verbose:
|
||||
print('NG = %d' % NG)
|
||||
print("NG = %d" % NG)
|
||||
|
||||
trial = 0 # Number of trial graphs so far
|
||||
while True:
|
||||
|
@ -296,14 +307,14 @@ WARNING: You have %d keys.
|
|||
if trial > 0:
|
||||
NG = max(NG + 1, int(1.05 * NG))
|
||||
if verbose:
|
||||
sys.stdout.write('\nGenerating graphs NG = %d ' % NG)
|
||||
sys.stdout.write("\nGenerating graphs NG = %d " % NG)
|
||||
trial += 1
|
||||
|
||||
if NG > 100 * (NK + 1):
|
||||
raise TooManyInterationsError("%d keys" % NK)
|
||||
|
||||
if verbose:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.write(".")
|
||||
sys.stdout.flush()
|
||||
|
||||
G = Graph(NG) # Create graph with NG vertices
|
||||
|
@ -322,33 +333,30 @@ WARNING: You have %d keys.
|
|||
break
|
||||
|
||||
if verbose:
|
||||
print('\nAcyclic graph found after %d trials.' % trial)
|
||||
print('NG = %d' % NG)
|
||||
print("\nAcyclic graph found after %d trials." % trial)
|
||||
print("NG = %d" % NG)
|
||||
|
||||
# Sanity check the result by actually verifying that all the keys
|
||||
# hash to the right value.
|
||||
for hashval, key in enumerate(keys):
|
||||
assert hashval == (
|
||||
G.vertex_values[f1(key)] + G.vertex_values[f2(key)]
|
||||
) % NG
|
||||
assert hashval == (G.vertex_values[f1(key)] + G.vertex_values[f2(key)]) % NG
|
||||
|
||||
if verbose:
|
||||
print('OK')
|
||||
print("OK")
|
||||
|
||||
return f1, f2, G.vertex_values
|
||||
|
||||
|
||||
class Format(object):
|
||||
|
||||
def __init__(self, width=76, indent=4, delimiter=', '):
|
||||
def __init__(self, width=76, indent=4, delimiter=", "):
|
||||
self.width = width
|
||||
self.indent = indent
|
||||
self.delimiter = delimiter
|
||||
|
||||
def print_format(self):
|
||||
print("Format options:")
|
||||
for name in 'width', 'indent', 'delimiter':
|
||||
print(' %s: %r' % (name, getattr(self, name)))
|
||||
for name in "width", "indent", "delimiter":
|
||||
print(" %s: %r" % (name, getattr(self, name)))
|
||||
|
||||
def __call__(self, data, quote=False):
|
||||
if not isinstance(data, (list, tuple)):
|
||||
|
@ -360,10 +368,10 @@ class Format(object):
|
|||
for i, elt in enumerate(data):
|
||||
last = bool(i == len(data) - 1)
|
||||
|
||||
s = ('"%s"' if quote else '%s') % elt
|
||||
s = ('"%s"' if quote else "%s") % elt
|
||||
|
||||
if pos + len(s) + lendel > self.width:
|
||||
aux.write('\n' + (self.indent * ' '))
|
||||
aux.write("\n" + (self.indent * " "))
|
||||
pos = self.indent
|
||||
|
||||
aux.write(s)
|
||||
|
@ -372,7 +380,7 @@ class Format(object):
|
|||
aux.write(self.delimiter)
|
||||
pos += lendel
|
||||
|
||||
return '\n'.join(l.rstrip() for l in aux.getvalue().split('\n'))
|
||||
return "\n".join(l.rstrip() for l in aux.getvalue().split("\n"))
|
||||
|
||||
|
||||
def generate_code(keys, Hash=StrSaltHash, template=None, options=None):
|
||||
|
@ -397,20 +405,22 @@ def generate_code(keys, Hash=StrSaltHash, template=None, options=None):
|
|||
if options is None:
|
||||
fmt = Format()
|
||||
else:
|
||||
fmt = Format(width=options.width, indent=options.indent,
|
||||
delimiter=options.delimiter)
|
||||
fmt = Format(
|
||||
width=options.width, indent=options.indent, delimiter=options.delimiter
|
||||
)
|
||||
|
||||
if verbose:
|
||||
fmt.print_format()
|
||||
|
||||
return string.Template(template).substitute(
|
||||
NS = salt_len,
|
||||
S1 = fmt(f1.salt),
|
||||
S2 = fmt(f2.salt),
|
||||
NG = len(G),
|
||||
G = fmt(G),
|
||||
NK = len(keys),
|
||||
K = fmt(list(keys), quote=True))
|
||||
NS=salt_len,
|
||||
S1=fmt(f1.salt),
|
||||
S2=fmt(f2.salt),
|
||||
NG=len(G),
|
||||
G=fmt(G),
|
||||
NK=len(keys),
|
||||
K=fmt(list(keys), quote=True),
|
||||
)
|
||||
|
||||
|
||||
def read_table(filename, options):
|
||||
|
@ -430,8 +440,8 @@ def read_table(filename, options):
|
|||
|
||||
if verbose:
|
||||
print("Reader options:")
|
||||
for name in 'comment', 'splitby', 'keycol':
|
||||
print(' %s: %r' % (name, getattr(options, name)))
|
||||
for name in "comment", "splitby", "keycol":
|
||||
print(" %s: %r" % (name, getattr(options, name)))
|
||||
|
||||
for n, line in enumerate(fi):
|
||||
line = line.strip()
|
||||
|
@ -446,8 +456,9 @@ def read_table(filename, options):
|
|||
try:
|
||||
key = row[options.keycol - 1]
|
||||
except IndexError:
|
||||
sys.exit("%s:%d: Error: Cannot read key, not enough columns." %
|
||||
(filename, n + 1))
|
||||
sys.exit(
|
||||
"%s:%d: Error: Cannot read key, not enough columns." % (filename, n + 1)
|
||||
)
|
||||
|
||||
keys.append(key)
|
||||
|
||||
|
@ -463,7 +474,7 @@ def read_template(filename):
|
|||
if verbose:
|
||||
print("Reading template from file `%s'" % filename)
|
||||
try:
|
||||
with open(filename, 'r') as fi:
|
||||
with open(filename, "r") as fi:
|
||||
return fi.read()
|
||||
except IOError:
|
||||
sys.exit("Error: Could not open `%s' for reading." % filename)
|
||||
|
@ -471,8 +482,8 @@ def read_template(filename):
|
|||
|
||||
def run_code(code):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
path = join(tmpdir, 't.py')
|
||||
with open(path, 'w') as fo:
|
||||
path = join(tmpdir, "t.py")
|
||||
with open(path, "w") as fo:
|
||||
fo.write(code)
|
||||
try:
|
||||
subprocess.check_call([sys.executable, path])
|
||||
|
@ -494,102 +505,123 @@ If no template file is provided, a small built-in Python template
|
|||
is processed and the output code is written to stdout.
|
||||
"""
|
||||
|
||||
parser = OptionParser(usage = usage,
|
||||
description = description,
|
||||
prog = sys.argv[0],
|
||||
version = "%prog: " + __version__)
|
||||
parser = OptionParser(
|
||||
usage=usage,
|
||||
description=description,
|
||||
prog=sys.argv[0],
|
||||
version="%prog: " + __version__,
|
||||
)
|
||||
|
||||
parser.add_option("--delimiter",
|
||||
action = "store",
|
||||
default = ", ",
|
||||
help = "Delimiter for list items used in output, "
|
||||
parser.add_option(
|
||||
"--delimiter",
|
||||
action="store",
|
||||
default=", ",
|
||||
help="Delimiter for list items used in output, "
|
||||
"the default delimiter is '%default'",
|
||||
metavar = "STR")
|
||||
metavar="STR",
|
||||
)
|
||||
|
||||
parser.add_option("--indent",
|
||||
action = "store",
|
||||
default = 4,
|
||||
type = "int",
|
||||
help = "Make INT spaces at the beginning of a "
|
||||
parser.add_option(
|
||||
"--indent",
|
||||
action="store",
|
||||
default=4,
|
||||
type="int",
|
||||
help="Make INT spaces at the beginning of a "
|
||||
"new line when generated list is wrapped. "
|
||||
"Default is %default",
|
||||
metavar = "INT")
|
||||
metavar="INT",
|
||||
)
|
||||
|
||||
parser.add_option("--width",
|
||||
action = "store",
|
||||
default = 76,
|
||||
type = "int",
|
||||
help = "Maximal width of generated list when "
|
||||
parser.add_option(
|
||||
"--width",
|
||||
action="store",
|
||||
default=76,
|
||||
type="int",
|
||||
help="Maximal width of generated list when "
|
||||
"wrapped. Default width is %default",
|
||||
metavar = "INT")
|
||||
metavar="INT",
|
||||
)
|
||||
|
||||
parser.add_option("--comment",
|
||||
action = "store",
|
||||
default = "#",
|
||||
help = "STR is the character, or sequence of "
|
||||
parser.add_option(
|
||||
"--comment",
|
||||
action="store",
|
||||
default="#",
|
||||
help="STR is the character, or sequence of "
|
||||
"characters, which marks the beginning "
|
||||
"of a comment (which runs till "
|
||||
"the end of the line), in the input "
|
||||
"KEYS_FILE. "
|
||||
"Default is '%default'",
|
||||
metavar = "STR")
|
||||
metavar="STR",
|
||||
)
|
||||
|
||||
parser.add_option("--splitby",
|
||||
action = "store",
|
||||
default = ",",
|
||||
help = "STR is the character by which the columns "
|
||||
parser.add_option(
|
||||
"--splitby",
|
||||
action="store",
|
||||
default=",",
|
||||
help="STR is the character by which the columns "
|
||||
"in the input KEYS_FILE are split. "
|
||||
"Default is '%default'",
|
||||
metavar = "STR")
|
||||
metavar="STR",
|
||||
)
|
||||
|
||||
parser.add_option("--keycol",
|
||||
action = "store",
|
||||
default = 1,
|
||||
type = "int",
|
||||
help = "Specifies the column INT in the input "
|
||||
parser.add_option(
|
||||
"--keycol",
|
||||
action="store",
|
||||
default=1,
|
||||
type="int",
|
||||
help="Specifies the column INT in the input "
|
||||
"KEYS_FILE which contains the keys. "
|
||||
"Default is %default, i.e. the first column.",
|
||||
metavar = "INT")
|
||||
metavar="INT",
|
||||
)
|
||||
|
||||
parser.add_option("--trials",
|
||||
action = "store",
|
||||
default = 5,
|
||||
type = "int",
|
||||
help = "Specifies the number of trials before "
|
||||
parser.add_option(
|
||||
"--trials",
|
||||
action="store",
|
||||
default=5,
|
||||
type="int",
|
||||
help="Specifies the number of trials before "
|
||||
"NG is increased. A small INT will give "
|
||||
"compute faster, but the array G will be "
|
||||
"large. A large INT will take longer to "
|
||||
"compute but G will be smaller. "
|
||||
"Default is %default",
|
||||
metavar = "INT")
|
||||
metavar="INT",
|
||||
)
|
||||
|
||||
parser.add_option("--hft",
|
||||
action = "store",
|
||||
default = 1,
|
||||
type = "int",
|
||||
help = "Hash function type INT. Possible values "
|
||||
parser.add_option(
|
||||
"--hft",
|
||||
action="store",
|
||||
default=1,
|
||||
type="int",
|
||||
help="Hash function type INT. Possible values "
|
||||
"are 1 (StrSaltHash) and 2 (IntSaltHash). "
|
||||
"The default is %default",
|
||||
metavar = "INT")
|
||||
metavar="INT",
|
||||
)
|
||||
|
||||
parser.add_option("-e", "--execute",
|
||||
action = "store_true",
|
||||
help = "Execute the generated code within "
|
||||
"the Python interpreter.")
|
||||
parser.add_option(
|
||||
"-e",
|
||||
"--execute",
|
||||
action="store_true",
|
||||
help="Execute the generated code within " "the Python interpreter.",
|
||||
)
|
||||
|
||||
parser.add_option("-o", "--output",
|
||||
action = "store",
|
||||
help = "Specify output FILE explicitly. "
|
||||
parser.add_option(
|
||||
"-o",
|
||||
"--output",
|
||||
action="store",
|
||||
help="Specify output FILE explicitly. "
|
||||
"`-o std' means standard output. "
|
||||
"`-o no' means no output. "
|
||||
"By default, the file name is obtained "
|
||||
"from the name of the template file by "
|
||||
"substituting `tmpl' to `code'.",
|
||||
metavar = "FILE")
|
||||
metavar="FILE",
|
||||
)
|
||||
|
||||
parser.add_option("-v", "--verbose",
|
||||
action = "store_true",
|
||||
help = "verbosity")
|
||||
parser.add_option("-v", "--verbose", action="store_true", help="verbosity")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
|
||||
|
@ -605,7 +637,7 @@ is processed and the output code is written to stdout.
|
|||
if len(args) not in (1, 2):
|
||||
parser.error("incorrect number of arguments")
|
||||
|
||||
if len(args) == 2 and not args[1].count('tmpl'):
|
||||
if len(args) == 2 and not args[1].count("tmpl"):
|
||||
parser.error("template filename does not contain 'tmpl'")
|
||||
|
||||
if options.hft == 1:
|
||||
|
@ -638,22 +670,22 @@ is processed and the output code is written to stdout.
|
|||
outname = options.output
|
||||
else:
|
||||
if tmpl_file:
|
||||
if 'tmpl' not in tmpl_file:
|
||||
if "tmpl" not in tmpl_file:
|
||||
sys.exit("Hmm, template filename does not contain 'tmpl'")
|
||||
outname = tmpl_file.replace('tmpl', 'code')
|
||||
outname = tmpl_file.replace("tmpl", "code")
|
||||
else:
|
||||
outname = 'std'
|
||||
outname = "std"
|
||||
|
||||
if verbose:
|
||||
print("outname = %r\n" % outname)
|
||||
|
||||
if outname == 'std':
|
||||
if outname == "std":
|
||||
outstream = sys.stdout
|
||||
elif outname == 'no':
|
||||
elif outname == "no":
|
||||
outstream = None
|
||||
else:
|
||||
try:
|
||||
outstream = open(outname, 'w')
|
||||
outstream = open(outname, "w")
|
||||
except IOError:
|
||||
sys.exit("Error: Could not open `%s' for writing." % outname)
|
||||
|
||||
|
@ -661,14 +693,14 @@ is processed and the output code is written to stdout.
|
|||
|
||||
if options.execute or template == builtin_template(Hash):
|
||||
if verbose:
|
||||
print('Executing code...\n')
|
||||
print("Executing code...\n")
|
||||
run_code(code)
|
||||
|
||||
if outstream:
|
||||
outstream.write(code)
|
||||
if not outname == 'std':
|
||||
if not outname == "std":
|
||||
outstream.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -10,15 +10,15 @@ import re
|
|||
import sys
|
||||
|
||||
|
||||
top_srcdir = pathlib.Path(os.environ['top_srcdir'])
|
||||
top_srcdir = pathlib.Path(os.environ["top_srcdir"])
|
||||
|
||||
|
||||
def symbols_from_map(path):
|
||||
return re.findall(r'^\s+(xkb_.*);', path.read_text('utf-8'), re.MULTILINE)
|
||||
return re.findall(r"^\s+(xkb_.*);", path.read_text("utf-8"), re.MULTILINE)
|
||||
|
||||
|
||||
def symbols_from_src(path):
|
||||
return re.findall(r'XKB_EXPORT.*\n(xkb_.*)\(', path.read_text('utf-8'))
|
||||
return re.findall(r"XKB_EXPORT.*\n(xkb_.*)\(", path.read_text("utf-8"))
|
||||
|
||||
|
||||
def diff(map_path, src_paths):
|
||||
|
@ -31,32 +31,32 @@ exit = 0
|
|||
|
||||
# xkbcommon symbols
|
||||
left, right = diff(
|
||||
top_srcdir/'xkbcommon.map',
|
||||
top_srcdir / "xkbcommon.map",
|
||||
[
|
||||
*(top_srcdir/'src').glob('*.c'),
|
||||
*(top_srcdir/'src'/'xkbcomp').glob('*.c'),
|
||||
*(top_srcdir/'src'/'compose').glob('*.c'),
|
||||
*(top_srcdir / "src").glob("*.c"),
|
||||
*(top_srcdir / "src" / "xkbcomp").glob("*.c"),
|
||||
*(top_srcdir / "src" / "compose").glob("*.c"),
|
||||
],
|
||||
)
|
||||
if left:
|
||||
print('xkbcommon map has extra symbols:', ' '.join(left))
|
||||
print("xkbcommon map has extra symbols:", " ".join(left))
|
||||
exit = 1
|
||||
if right:
|
||||
print('xkbcommon src has extra symbols:', ' '.join(right))
|
||||
print("xkbcommon src has extra symbols:", " ".join(right))
|
||||
exit = 1
|
||||
|
||||
# xkbcommon-x11 symbols
|
||||
left, right = diff(
|
||||
top_srcdir/'xkbcommon-x11.map',
|
||||
top_srcdir / "xkbcommon-x11.map",
|
||||
[
|
||||
*(top_srcdir/'src'/'x11').glob('*.c'),
|
||||
*(top_srcdir / "src" / "x11").glob("*.c"),
|
||||
],
|
||||
)
|
||||
if left:
|
||||
print('xkbcommon-x11 map has extra symbols:', ' '.join(left))
|
||||
print("xkbcommon-x11 map has extra symbols:", " ".join(left))
|
||||
exit = 1
|
||||
if right:
|
||||
print('xkbcommon-x11 src has extra symbols:', ' '.join(right))
|
||||
print("xkbcommon-x11 src has extra symbols:", " ".join(right))
|
||||
exit = 1
|
||||
|
||||
sys.exit(exit)
|
||||
|
|
|
@ -21,51 +21,57 @@ xkb_symbols "basic" {{
|
|||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Tool to verify whether a keysym is resolved'
|
||||
description="Tool to verify whether a keysym is resolved"
|
||||
)
|
||||
parser.add_argument("keysym", type=str, help="XKB keysym")
|
||||
parser.add_argument(
|
||||
"--tool",
|
||||
type=str,
|
||||
nargs=1,
|
||||
default=["xkbcli", "compile-keymap"],
|
||||
help="Full path to the xkbcli-compile-keymap tool",
|
||||
)
|
||||
parser.add_argument('keysym', type=str, help='XKB keysym')
|
||||
parser.add_argument('--tool', type=str, nargs=1,
|
||||
default=['xkbcli', 'compile-keymap'],
|
||||
help='Full path to the xkbcli-compile-keymap tool')
|
||||
args = parser.parse_args()
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
symfile = Path(tmpdir) / "symbols" / "keytest"
|
||||
symfile.parent.mkdir()
|
||||
with symfile.open(mode='w') as f:
|
||||
with symfile.open(mode="w") as f:
|
||||
f.write(template.format(args.keysym))
|
||||
|
||||
try:
|
||||
cmd = [
|
||||
*args.tool,
|
||||
'--layout', 'keytest',
|
||||
"--layout",
|
||||
"keytest",
|
||||
]
|
||||
|
||||
env = os.environ.copy()
|
||||
env['XKB_CONFIG_EXTRA_PATH'] = tmpdir
|
||||
env["XKB_CONFIG_EXTRA_PATH"] = tmpdir
|
||||
|
||||
result = subprocess.run(cmd, env=env, capture_output=True,
|
||||
universal_newlines=True)
|
||||
result = subprocess.run(
|
||||
cmd, env=env, capture_output=True, universal_newlines=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print('ERROR: Failed to compile:')
|
||||
print("ERROR: Failed to compile:")
|
||||
print(result.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# grep for TLDE actually being remapped
|
||||
for l in result.stdout.split('\n'):
|
||||
match = re.match(r'\s+key \<TLDE\>\s+{\s+\[\s+(?P<keysym>\w+)\s+\]\s+}', l)
|
||||
for l in result.stdout.split("\n"):
|
||||
match = re.match(r"\s+key \<TLDE\>\s+{\s+\[\s+(?P<keysym>\w+)\s+\]\s+}", l)
|
||||
if match:
|
||||
if args.keysym == match.group('keysym'):
|
||||
if args.keysym == match.group("keysym"):
|
||||
sys.exit(0)
|
||||
elif match.group('keysym') == 'NoSymbol':
|
||||
print('ERROR: key {} not resolved:'.format(args.keysym), l)
|
||||
elif match.group("keysym") == "NoSymbol":
|
||||
print("ERROR: key {} not resolved:".format(args.keysym), l)
|
||||
else:
|
||||
print('ERROR: key {} mapped to wrong key:'.format(args.keysym), l)
|
||||
print("ERROR: key {} mapped to wrong key:".format(args.keysym), l)
|
||||
sys.exit(1)
|
||||
|
||||
print(result.stdout)
|
||||
print('ERROR: above keymap is missing key mapping for {}'.format(args.keysym))
|
||||
print("ERROR: above keymap is missing key mapping for {}".format(args.keysym))
|
||||
sys.exit(1)
|
||||
except FileNotFoundError as err:
|
||||
print('ERROR: invalid or missing tool: {}'.format(err))
|
||||
print("ERROR: invalid or missing tool: {}".format(err))
|
||||
sys.exit(1)
|
||||
|
|
|
@ -32,28 +32,37 @@ import unittest
|
|||
|
||||
|
||||
try:
|
||||
top_builddir = os.environ['top_builddir']
|
||||
top_srcdir = os.environ['top_srcdir']
|
||||
top_builddir = os.environ["top_builddir"]
|
||||
top_srcdir = os.environ["top_srcdir"]
|
||||
except KeyError:
|
||||
print('Required environment variables not found: top_srcdir/top_builddir', file=sys.stderr)
|
||||
print(
|
||||
"Required environment variables not found: top_srcdir/top_builddir",
|
||||
file=sys.stderr,
|
||||
)
|
||||
from pathlib import Path
|
||||
top_srcdir = '.'
|
||||
|
||||
top_srcdir = "."
|
||||
try:
|
||||
top_builddir = next(Path('.').glob('**/meson-logs/')).parent
|
||||
top_builddir = next(Path(".").glob("**/meson-logs/")).parent
|
||||
except StopIteration:
|
||||
sys.exit(1)
|
||||
print('Using srcdir "{}", builddir "{}"'.format(top_srcdir, top_builddir), file=sys.stderr)
|
||||
print(
|
||||
'Using srcdir "{}", builddir "{}"'.format(top_srcdir, top_builddir),
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger('test')
|
||||
logger = logging.getLogger("test")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# Permutation of RMLVO that we use in multiple tests
|
||||
rmlvos = [list(x) for x in itertools.permutations(
|
||||
['--rules=evdev', '--model=pc104',
|
||||
'--layout=ch', '--options=eurosign:5']
|
||||
)]
|
||||
rmlvos = [
|
||||
list(x)
|
||||
for x in itertools.permutations(
|
||||
["--rules=evdev", "--model=pc104", "--layout=ch", "--options=eurosign:5"]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def _disable_coredump():
|
||||
|
@ -61,19 +70,23 @@ def _disable_coredump():
|
|||
|
||||
|
||||
def run_command(args):
|
||||
logger.debug('run command: {}'.format(' '.join(args)))
|
||||
logger.debug("run command: {}".format(" ".join(args)))
|
||||
|
||||
try:
|
||||
p = subprocess.run(args, preexec_fn=_disable_coredump,
|
||||
capture_output=True, text=True,
|
||||
timeout=0.7)
|
||||
p = subprocess.run(
|
||||
args,
|
||||
preexec_fn=_disable_coredump,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=0.7,
|
||||
)
|
||||
return p.returncode, p.stdout, p.stderr
|
||||
except subprocess.TimeoutExpired as e:
|
||||
return 0, e.stdout, e.stderr
|
||||
|
||||
|
||||
class XkbcliTool:
|
||||
xkbcli_tool = 'xkbcli'
|
||||
xkbcli_tool = "xkbcli"
|
||||
subtool = None
|
||||
|
||||
def __init__(self, subtool=None, skipIf=(), skipError=()):
|
||||
|
@ -87,7 +100,7 @@ class XkbcliTool:
|
|||
if condition:
|
||||
raise unittest.SkipTest(reason)
|
||||
if self.subtool is not None:
|
||||
tool = '{}-{}'.format(self.xkbcli_tool, self.subtool)
|
||||
tool = "{}-{}".format(self.xkbcli_tool, self.subtool)
|
||||
else:
|
||||
tool = self.xkbcli_tool
|
||||
args = [os.path.join(self.tool_path, tool)] + args
|
||||
|
@ -111,14 +124,14 @@ class XkbcliTool:
|
|||
def run_command_unrecognized_option(self, args):
|
||||
rc, stdout, stderr = self.run_command(args)
|
||||
assert rc == 2, (rc, stdout, stderr)
|
||||
assert stdout.startswith('Usage') or stdout == ''
|
||||
assert 'unrecognized option' in stderr
|
||||
assert stdout.startswith("Usage") or stdout == ""
|
||||
assert "unrecognized option" in stderr
|
||||
|
||||
def run_command_missing_arg(self, args):
|
||||
rc, stdout, stderr = self.run_command(args)
|
||||
assert rc == 2, (rc, stdout, stderr)
|
||||
assert stdout.startswith('Usage') or stdout == ''
|
||||
assert 'requires an argument' in stderr
|
||||
assert stdout.startswith("Usage") or stdout == ""
|
||||
assert "requires an argument" in stderr
|
||||
|
||||
def __str__(self):
|
||||
return str(self.subtool)
|
||||
|
@ -128,28 +141,57 @@ class TestXkbcli(unittest.TestCase):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.xkbcli = XkbcliTool()
|
||||
cls.xkbcli_list = XkbcliTool('list', skipIf=(
|
||||
(not int(os.getenv('HAVE_XKBCLI_LIST', '1')), 'xkbregistory not enabled'),
|
||||
))
|
||||
cls.xkbcli_how_to_type = XkbcliTool('how-to-type')
|
||||
cls.xkbcli_compile_keymap = XkbcliTool('compile-keymap')
|
||||
cls.xkbcli_interactive_evdev = XkbcliTool('interactive-evdev', skipIf=(
|
||||
(not int(os.getenv('HAVE_XKBCLI_INTERACTIVE_EVDEV', '1')), 'evdev not enabled'),
|
||||
(not os.path.exists('/dev/input/event0'), 'event node required'),
|
||||
(not os.access('/dev/input/event0', os.R_OK), 'insufficient permissions'),
|
||||
), skipError=(
|
||||
(lambda rc, stdout, stderr: 'Couldn\'t find any keyboards' in stderr,
|
||||
'No keyboards available'),
|
||||
cls.xkbcli_list = XkbcliTool(
|
||||
"list",
|
||||
skipIf=(
|
||||
(
|
||||
not int(os.getenv("HAVE_XKBCLI_LIST", "1")),
|
||||
"xkbregistory not enabled",
|
||||
),
|
||||
),
|
||||
)
|
||||
cls.xkbcli_how_to_type = XkbcliTool("how-to-type")
|
||||
cls.xkbcli_compile_keymap = XkbcliTool("compile-keymap")
|
||||
cls.xkbcli_interactive_evdev = XkbcliTool(
|
||||
"interactive-evdev",
|
||||
skipIf=(
|
||||
(
|
||||
not int(os.getenv("HAVE_XKBCLI_INTERACTIVE_EVDEV", "1")),
|
||||
"evdev not enabled",
|
||||
),
|
||||
(not os.path.exists("/dev/input/event0"), "event node required"),
|
||||
(
|
||||
not os.access("/dev/input/event0", os.R_OK),
|
||||
"insufficient permissions",
|
||||
),
|
||||
),
|
||||
skipError=(
|
||||
(
|
||||
lambda rc, stdout, stderr: "Couldn't find any keyboards" in stderr,
|
||||
"No keyboards available",
|
||||
),
|
||||
),
|
||||
)
|
||||
cls.xkbcli_interactive_x11 = XkbcliTool(
|
||||
"interactive-x11",
|
||||
skipIf=(
|
||||
(
|
||||
not int(os.getenv("HAVE_XKBCLI_INTERACTIVE_X11", "1")),
|
||||
"x11 not enabled",
|
||||
),
|
||||
(not os.getenv("DISPLAY"), "DISPLAY not set"),
|
||||
),
|
||||
)
|
||||
cls.xkbcli_interactive_wayland = XkbcliTool(
|
||||
"interactive-wayland",
|
||||
skipIf=(
|
||||
(
|
||||
not int(os.getenv("HAVE_XKBCLI_INTERACTIVE_WAYLAND", "1")),
|
||||
"wayland not enabled",
|
||||
),
|
||||
(not os.getenv("WAYLAND_DISPLAY"), "WAYLAND_DISPLAY not set"),
|
||||
),
|
||||
)
|
||||
cls.xkbcli_interactive_x11 = XkbcliTool('interactive-x11', skipIf=(
|
||||
(not int(os.getenv('HAVE_XKBCLI_INTERACTIVE_X11', '1')), 'x11 not enabled'),
|
||||
(not os.getenv('DISPLAY'), 'DISPLAY not set'),
|
||||
))
|
||||
cls.xkbcli_interactive_wayland = XkbcliTool('interactive-wayland', skipIf=(
|
||||
(not int(os.getenv('HAVE_XKBCLI_INTERACTIVE_WAYLAND', '1')), 'wayland not enabled'),
|
||||
(not os.getenv('WAYLAND_DISPLAY'), 'WAYLAND_DISPLAY not set'),
|
||||
))
|
||||
cls.all_tools = [
|
||||
cls.xkbcli,
|
||||
cls.xkbcli_list,
|
||||
|
@ -164,31 +206,31 @@ class TestXkbcli(unittest.TestCase):
|
|||
# --help is supported by all tools
|
||||
for tool in self.all_tools:
|
||||
with self.subTest(tool=tool):
|
||||
stdout, stderr = tool.run_command_success(['--help'])
|
||||
assert stdout.startswith('Usage:')
|
||||
assert stderr == ''
|
||||
stdout, stderr = tool.run_command_success(["--help"])
|
||||
assert stdout.startswith("Usage:")
|
||||
assert stderr == ""
|
||||
|
||||
def test_invalid_option(self):
|
||||
# --foobar generates "Usage:" for all tools
|
||||
for tool in self.all_tools:
|
||||
with self.subTest(tool=tool):
|
||||
tool.run_command_unrecognized_option(['--foobar'])
|
||||
tool.run_command_unrecognized_option(["--foobar"])
|
||||
|
||||
def test_xkbcli_version(self):
|
||||
# xkbcli --version
|
||||
stdout, stderr = self.xkbcli.run_command_success(['--version'])
|
||||
assert stdout.startswith('1')
|
||||
assert stderr == ''
|
||||
stdout, stderr = self.xkbcli.run_command_success(["--version"])
|
||||
assert stdout.startswith("1")
|
||||
assert stderr == ""
|
||||
|
||||
def test_xkbcli_too_many_args(self):
|
||||
self.xkbcli.run_command_invalid(['a'] * 64)
|
||||
self.xkbcli.run_command_invalid(["a"] * 64)
|
||||
|
||||
def test_compile_keymap_args(self):
|
||||
for args in (
|
||||
['--verbose'],
|
||||
['--rmlvo'],
|
||||
["--verbose"],
|
||||
["--rmlvo"],
|
||||
# ['--kccgst'],
|
||||
['--verbose', '--rmlvo'],
|
||||
["--verbose", "--rmlvo"],
|
||||
# ['--verbose', '--kccgst'],
|
||||
):
|
||||
with self.subTest(args=args):
|
||||
|
@ -201,8 +243,8 @@ class TestXkbcli(unittest.TestCase):
|
|||
|
||||
def test_compile_keymap_include(self):
|
||||
for args in (
|
||||
['--include', '.', '--include-defaults'],
|
||||
['--include', '/tmp', '--include-defaults'],
|
||||
["--include", ".", "--include-defaults"],
|
||||
["--include", "/tmp", "--include-defaults"],
|
||||
):
|
||||
with self.subTest(args=args):
|
||||
# Succeeds thanks to include-defaults
|
||||
|
@ -210,59 +252,59 @@ class TestXkbcli(unittest.TestCase):
|
|||
|
||||
def test_compile_keymap_include_invalid(self):
|
||||
# A non-directory is rejected by default
|
||||
args = ['--include', '/proc/version']
|
||||
args = ["--include", "/proc/version"]
|
||||
rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
|
||||
assert rc == 1, (stdout, stderr)
|
||||
assert "There are no include paths to search" in stderr
|
||||
|
||||
# A non-existing directory is rejected by default
|
||||
args = ['--include', '/tmp/does/not/exist']
|
||||
args = ["--include", "/tmp/does/not/exist"]
|
||||
rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
|
||||
assert rc == 1, (stdout, stderr)
|
||||
assert "There are no include paths to search" in stderr
|
||||
|
||||
# Valid dir, but missing files
|
||||
args = ['--include', '/tmp']
|
||||
args = ["--include", "/tmp"]
|
||||
rc, stdout, stderr = self.xkbcli_compile_keymap.run_command(args)
|
||||
assert rc == 1, (stdout, stderr)
|
||||
assert "Couldn't look up rules" in stderr
|
||||
|
||||
def test_how_to_type(self):
|
||||
# Unicode codepoint conversions, we support whatever strtol does
|
||||
for args in (['123'], ['0x123'], ['0123']):
|
||||
for args in (["123"], ["0x123"], ["0123"]):
|
||||
with self.subTest(args=args):
|
||||
self.xkbcli_how_to_type.run_command_success(args)
|
||||
|
||||
def test_how_to_type_rmlvo(self):
|
||||
for rmlvo in rmlvos:
|
||||
with self.subTest(rmlvo=rmlvo):
|
||||
args = rmlvo + ['0x1234']
|
||||
args = rmlvo + ["0x1234"]
|
||||
self.xkbcli_how_to_type.run_command_success(args)
|
||||
|
||||
def test_list_rmlvo(self):
|
||||
for args in (
|
||||
['--verbose'],
|
||||
['-v'],
|
||||
['--verbose', '--load-exotic'],
|
||||
['--load-exotic'],
|
||||
['--ruleset=evdev'],
|
||||
['--ruleset=base'],
|
||||
["--verbose"],
|
||||
["-v"],
|
||||
["--verbose", "--load-exotic"],
|
||||
["--load-exotic"],
|
||||
["--ruleset=evdev"],
|
||||
["--ruleset=base"],
|
||||
):
|
||||
with self.subTest(args=args):
|
||||
self.xkbcli_list.run_command_success(args)
|
||||
|
||||
def test_list_rmlvo_includes(self):
|
||||
args = ['/tmp/']
|
||||
args = ["/tmp/"]
|
||||
self.xkbcli_list.run_command_success(args)
|
||||
|
||||
def test_list_rmlvo_includes_invalid(self):
|
||||
args = ['/proc/version']
|
||||
args = ["/proc/version"]
|
||||
rc, stdout, stderr = self.xkbcli_list.run_command(args)
|
||||
assert rc == 1
|
||||
assert "Failed to append include path" in stderr
|
||||
|
||||
def test_list_rmlvo_includes_no_defaults(self):
|
||||
args = ['--skip-default-paths', '/tmp']
|
||||
args = ["--skip-default-paths", "/tmp"]
|
||||
rc, stdout, stderr = self.xkbcli_list.run_command(args)
|
||||
assert rc == 1
|
||||
assert "Failed to parse XKB description" in stderr
|
||||
|
@ -276,11 +318,11 @@ class TestXkbcli(unittest.TestCase):
|
|||
# Note: --enable-compose fails if $prefix doesn't have the compose tables
|
||||
# installed
|
||||
for args in (
|
||||
['--report-state-changes'],
|
||||
['--enable-compose'],
|
||||
['--consumed-mode=xkb'],
|
||||
['--consumed-mode=gtk'],
|
||||
['--without-x11-offset'],
|
||||
["--report-state-changes"],
|
||||
["--enable-compose"],
|
||||
["--consumed-mode=xkb"],
|
||||
["--consumed-mode=gtk"],
|
||||
["--without-x11-offset"],
|
||||
):
|
||||
with self.subTest(args=args):
|
||||
self.xkbcli_interactive_evdev.run_command_success(args)
|
||||
|
@ -294,21 +336,21 @@ class TestXkbcli(unittest.TestCase):
|
|||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Use our own test xkeyboard-config copy.
|
||||
os.environ['XKB_CONFIG_ROOT'] = top_srcdir + '/test/data'
|
||||
os.environ["XKB_CONFIG_ROOT"] = top_srcdir + "/test/data"
|
||||
# Use our own X11 locale copy.
|
||||
os.environ['XLOCALEDIR'] = top_srcdir + '/test/data/locale'
|
||||
os.environ["XLOCALEDIR"] = top_srcdir + "/test/data/locale"
|
||||
# Use our own locale.
|
||||
os.environ['LC_CTYPE'] = 'en_US.UTF-8'
|
||||
os.environ["LC_CTYPE"] = "en_US.UTF-8"
|
||||
# libxkbcommon has fallbacks when XDG_CONFIG_HOME isn't set so we need
|
||||
# to override it with a known (empty) directory. Otherwise our test
|
||||
# behavior depends on the system the test is run on.
|
||||
os.environ['XDG_CONFIG_HOME'] = tmpdir
|
||||
os.environ["XDG_CONFIG_HOME"] = tmpdir
|
||||
# Prevent the legacy $HOME/.xkb from kicking in.
|
||||
del os.environ['HOME']
|
||||
del os.environ["HOME"]
|
||||
# This needs to be separated if we do specific extra path testing
|
||||
os.environ['XKB_CONFIG_EXTRA_PATH'] = tmpdir
|
||||
os.environ["XKB_CONFIG_EXTRA_PATH"] = tmpdir
|
||||
|
||||
unittest.main()
|
||||
|
|
|
@ -10,11 +10,11 @@ from pathlib import Path
|
|||
|
||||
verbose = False
|
||||
|
||||
DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
|
||||
DEFAULT_RULES_XML = "@XKB_CONFIG_ROOT@/rules/evdev.xml"
|
||||
|
||||
# Meson needs to fill this in so we can call the tool in the buildir.
|
||||
EXTRA_PATH = '@MESON_BUILD_ROOT@'
|
||||
os.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
|
||||
EXTRA_PATH = "@MESON_BUILD_ROOT@"
|
||||
os.environ["PATH"] = ":".join([EXTRA_PATH, os.getenv("PATH")])
|
||||
|
||||
|
||||
def escape(s):
|
||||
|
@ -30,6 +30,7 @@ def create_progress_bar(verbose):
|
|||
if not verbose and os.isatty(sys.stdout.fileno()):
|
||||
try:
|
||||
from tqdm import tqdm
|
||||
|
||||
progress_bar = tqdm
|
||||
except ImportError:
|
||||
pass
|
||||
|
@ -56,13 +57,13 @@ class Invocation:
|
|||
def __str__(self):
|
||||
s = []
|
||||
rmlvo = [x or "" for x in self.rmlvo]
|
||||
rmlvo = ', '.join([f'"{x}"' for x in rmlvo])
|
||||
s.append(f'- rmlvo: [{rmlvo}]')
|
||||
rmlvo = ", ".join([f'"{x}"' for x in rmlvo])
|
||||
s.append(f"- rmlvo: [{rmlvo}]")
|
||||
s.append(f' cmd: "{escape(self.command)}"')
|
||||
s.append(f' status: {self.exitstatus}')
|
||||
s.append(f" status: {self.exitstatus}")
|
||||
if self.error:
|
||||
s.append(f' error: "{escape(self.error.strip())}"')
|
||||
return '\n'.join(s)
|
||||
return "\n".join(s)
|
||||
|
||||
def run(self):
|
||||
raise NotImplementedError
|
||||
|
@ -71,37 +72,45 @@ class Invocation:
|
|||
class XkbCompInvocation(Invocation):
|
||||
def run(self):
|
||||
r, m, l, v, o = self.rmlvo
|
||||
args = ['setxkbmap', '-print']
|
||||
args = ["setxkbmap", "-print"]
|
||||
if r is not None:
|
||||
args.append('-rules')
|
||||
args.append('{}'.format(r))
|
||||
args.append("-rules")
|
||||
args.append("{}".format(r))
|
||||
if m is not None:
|
||||
args.append('-model')
|
||||
args.append('{}'.format(m))
|
||||
args.append("-model")
|
||||
args.append("{}".format(m))
|
||||
if l is not None:
|
||||
args.append('-layout')
|
||||
args.append('{}'.format(l))
|
||||
args.append("-layout")
|
||||
args.append("{}".format(l))
|
||||
if v is not None:
|
||||
args.append('-variant')
|
||||
args.append('{}'.format(v))
|
||||
args.append("-variant")
|
||||
args.append("{}".format(v))
|
||||
if o is not None:
|
||||
args.append('-option')
|
||||
args.append('{}'.format(o))
|
||||
args.append("-option")
|
||||
args.append("{}".format(o))
|
||||
|
||||
xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
|
||||
xkbcomp_args = ["xkbcomp", "-xkb", "-", "-"]
|
||||
|
||||
self.command = " ".join(args + ["|"] + xkbcomp_args)
|
||||
|
||||
setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, universal_newlines=True)
|
||||
setxkbmap = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
stdout, stderr = setxkbmap.communicate()
|
||||
if "Cannot open display" in stderr:
|
||||
self.error = stderr
|
||||
self.exitstatus = 90
|
||||
else:
|
||||
xkbcomp = subprocess.Popen(xkbcomp_args, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
xkbcomp = subprocess.Popen(
|
||||
xkbcomp_args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
stdout, stderr = xkbcomp.communicate(stdout)
|
||||
if xkbcomp.returncode != 0:
|
||||
self.error = "failed to compile keymap"
|
||||
|
@ -117,23 +126,27 @@ class XkbcommonInvocation(Invocation):
|
|||
def run(self):
|
||||
r, m, l, v, o = self.rmlvo
|
||||
args = [
|
||||
'xkbcli-compile-keymap', # this is run in the builddir
|
||||
'--verbose',
|
||||
'--rules', r,
|
||||
'--model', m,
|
||||
'--layout', l,
|
||||
"xkbcli-compile-keymap", # this is run in the builddir
|
||||
"--verbose",
|
||||
"--rules",
|
||||
r,
|
||||
"--model",
|
||||
m,
|
||||
"--layout",
|
||||
l,
|
||||
]
|
||||
if v is not None:
|
||||
args += ['--variant', v]
|
||||
args += ["--variant", v]
|
||||
if o is not None:
|
||||
args += ['--options', o]
|
||||
args += ["--options", o]
|
||||
|
||||
self.command = " ".join(args)
|
||||
try:
|
||||
output = subprocess.check_output(args, stderr=subprocess.STDOUT,
|
||||
universal_newlines=True)
|
||||
output = subprocess.check_output(
|
||||
args, stderr=subprocess.STDOUT, universal_newlines=True
|
||||
)
|
||||
if self.UNRECOGNIZED_KEYSYM_ERROR in output:
|
||||
for line in output.split('\n'):
|
||||
for line in output.split("\n"):
|
||||
if self.UNRECOGNIZED_KEYSYM_ERROR in line:
|
||||
self.error = line
|
||||
self.exitstatus = 99 # tool doesn't generate this one
|
||||
|
@ -147,11 +160,11 @@ class XkbcommonInvocation(Invocation):
|
|||
|
||||
def xkbcommontool(rmlvo):
|
||||
try:
|
||||
r = rmlvo.get('r', 'evdev')
|
||||
m = rmlvo.get('m', 'pc105')
|
||||
l = rmlvo.get('l', 'us')
|
||||
v = rmlvo.get('v', None)
|
||||
o = rmlvo.get('o', None)
|
||||
r = rmlvo.get("r", "evdev")
|
||||
m = rmlvo.get("m", "pc105")
|
||||
l = rmlvo.get("l", "us")
|
||||
v = rmlvo.get("v", None)
|
||||
o = rmlvo.get("o", None)
|
||||
tool = XkbcommonInvocation(r, m, l, v, o)
|
||||
tool.run()
|
||||
return tool
|
||||
|
@ -161,11 +174,11 @@ def xkbcommontool(rmlvo):
|
|||
|
||||
def xkbcomp(rmlvo):
|
||||
try:
|
||||
r = rmlvo.get('r', 'evdev')
|
||||
m = rmlvo.get('m', 'pc105')
|
||||
l = rmlvo.get('l', 'us')
|
||||
v = rmlvo.get('v', None)
|
||||
o = rmlvo.get('o', None)
|
||||
r = rmlvo.get("r", "evdev")
|
||||
m = rmlvo.get("m", "pc105")
|
||||
l = rmlvo.get("l", "us")
|
||||
v = rmlvo.get("v", None)
|
||||
o = rmlvo.get("o", None)
|
||||
tool = XkbCompInvocation(r, m, l, v, o)
|
||||
tool.run()
|
||||
return tool
|
||||
|
@ -175,25 +188,22 @@ def xkbcomp(rmlvo):
|
|||
|
||||
def parse(path):
|
||||
root = ET.fromstring(open(path).read())
|
||||
layouts = root.findall('layoutList/layout')
|
||||
layouts = root.findall("layoutList/layout")
|
||||
|
||||
options = [
|
||||
e.text
|
||||
for e in root.findall('optionList/group/option/configItem/name')
|
||||
]
|
||||
options = [e.text for e in root.findall("optionList/group/option/configItem/name")]
|
||||
|
||||
combos = []
|
||||
for l in layouts:
|
||||
layout = l.find('configItem/name').text
|
||||
combos.append({'l': layout})
|
||||
layout = l.find("configItem/name").text
|
||||
combos.append({"l": layout})
|
||||
|
||||
variants = l.findall('variantList/variant')
|
||||
variants = l.findall("variantList/variant")
|
||||
for v in variants:
|
||||
variant = v.find('configItem/name').text
|
||||
variant = v.find("configItem/name").text
|
||||
|
||||
combos.append({'l': layout, 'v': variant})
|
||||
combos.append({"l": layout, "v": variant})
|
||||
for option in options:
|
||||
combos.append({'l': layout, 'v': variant, 'o': option})
|
||||
combos.append({"l": layout, "v": variant, "o": option})
|
||||
|
||||
return combos
|
||||
|
||||
|
@ -234,9 +244,9 @@ def run(combos, tool, njobs, keymap_output_dir):
|
|||
keymap_file = fname
|
||||
if keymap_file_fd:
|
||||
keymap_file_fd.close()
|
||||
keymap_file_fd = open(keymap_file, 'a')
|
||||
keymap_file_fd = open(keymap_file, "a")
|
||||
|
||||
rmlvo = ', '.join([x or '' for x in invocation.rmlvo])
|
||||
rmlvo = ", ".join([x or "" for x in invocation.rmlvo])
|
||||
print(f"// {rmlvo}", file=keymap_file_fd)
|
||||
print(invocation.keymap, file=keymap_file_fd)
|
||||
keymap_file_fd.flush()
|
||||
|
@ -249,37 +259,56 @@ def main(args):
|
|||
global verbose
|
||||
|
||||
tools = {
|
||||
'libxkbcommon': xkbcommontool,
|
||||
'xkbcomp': xkbcomp,
|
||||
"libxkbcommon": xkbcommontool,
|
||||
"xkbcomp": xkbcomp,
|
||||
}
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='''
|
||||
description="""
|
||||
This tool compiles a keymap for each layout, variant and
|
||||
options combination in the given rules XML file. The output
|
||||
of this tool is YAML, use your favorite YAML parser to
|
||||
extract error messages. Errors are printed to stderr.
|
||||
'''
|
||||
"""
|
||||
)
|
||||
parser.add_argument('path', metavar='/path/to/evdev.xml',
|
||||
nargs='?', type=str,
|
||||
parser.add_argument(
|
||||
"path",
|
||||
metavar="/path/to/evdev.xml",
|
||||
nargs="?",
|
||||
type=str,
|
||||
default=DEFAULT_RULES_XML,
|
||||
help='Path to xkeyboard-config\'s evdev.xml')
|
||||
parser.add_argument('--tool', choices=tools.keys(),
|
||||
type=str, default='libxkbcommon',
|
||||
help='parsing tool to use')
|
||||
parser.add_argument('--jobs', '-j', type=int,
|
||||
help="Path to xkeyboard-config's evdev.xml",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tool",
|
||||
choices=tools.keys(),
|
||||
type=str,
|
||||
default="libxkbcommon",
|
||||
help="parsing tool to use",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--jobs",
|
||||
"-j",
|
||||
type=int,
|
||||
default=os.cpu_count() * 4,
|
||||
help='number of processes to use')
|
||||
parser.add_argument('--verbose', '-v', default=False, action="store_true")
|
||||
parser.add_argument('--keymap-output-dir', default=None, type=str,
|
||||
help='Directory to print compiled keymaps to')
|
||||
parser.add_argument('--layout', default=None, type=str,
|
||||
help='Only test the given layout')
|
||||
parser.add_argument('--variant', default=None, type=str,
|
||||
help='Only test the given variant')
|
||||
parser.add_argument('--option', default=None, type=str,
|
||||
help='Only test the given option')
|
||||
help="number of processes to use",
|
||||
)
|
||||
parser.add_argument("--verbose", "-v", default=False, action="store_true")
|
||||
parser.add_argument(
|
||||
"--keymap-output-dir",
|
||||
default=None,
|
||||
type=str,
|
||||
help="Directory to print compiled keymaps to",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--layout", default=None, type=str, help="Only test the given layout"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--variant", default=None, type=str, help="Only test the given variant"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--option", default=None, type=str, help="Only test the given option"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -290,19 +319,21 @@ def main(args):
|
|||
tool = tools[args.tool]
|
||||
|
||||
if any([args.layout, args.variant, args.option]):
|
||||
combos = [{
|
||||
'l': args.layout,
|
||||
'v': args.variant,
|
||||
'o': args.option,
|
||||
}]
|
||||
combos = [
|
||||
{
|
||||
"l": args.layout,
|
||||
"v": args.variant,
|
||||
"o": args.option,
|
||||
}
|
||||
]
|
||||
else:
|
||||
combos = parse(args.path)
|
||||
failed = run(combos, tool, args.jobs, keymapdir)
|
||||
sys.exit(failed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main(sys.argv)
|
||||
except KeyboardInterrupt:
|
||||
print('# Exiting after Ctrl+C')
|
||||
print("# Exiting after Ctrl+C")
|
||||
|
|
Loading…
Reference in New Issue