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