xkeyboard-config
210 строк · 5.9 Кб
1#!/usr/bin/env python3
2#
3# Builds a tree view of a symbols file (showing all includes)
4#
5# This file is formatted with Python Black
6
7import argparse8import pathlib9import dataclasses10from pyparsing import (11Word,12Literal,13LineEnd,14OneOrMore,15oneOf,16Or,17And,18QuotedString,19Regex,20cppStyleComment,21alphanums,22Optional,23ParseException,24)
25
26xkb_basedir = None27
28
29@dataclasses.dataclass30class XkbSymbols:31file: pathlib.Path # Path to the file this section came from32name: str33includes: list[str] = dataclasses.field(default_factory=list)34
35@property36def layout(self) -> str:37return self.file.name # XKb - filename is the layout name38
39def __str__(self):40return f"{self.layout}({self.name}): {self.includes}"41
42
43class XkbLoader:44"""45Wrapper class to avoid loading the same symbols file over and over
46again.
47"""
48
49class XkbParserException(Exception):50pass51
52_instance = None53
54def __init__(self, xkb_basedir):55self.xkb_basedir = xkb_basedir56self.loaded = {}57
58@classmethod59def create(cls, xkb_basedir):60assert cls._instance is None61cls._instance = XkbLoader(xkb_basedir)62
63@classmethod64def instance(cls):65assert cls._instance is not None66return cls._instance67
68@classmethod69def load_symbols(cls, file):70return cls.instance().load_symbols_file(file)71
72def load_symbols_file(self, file) -> list[XkbSymbols]:73file = self.xkb_basedir / file74try:75return self.loaded[file]76except KeyError:77pass78
79sections = []80
81def quoted(name):82return QuotedString(quoteChar='"', unquoteResults=True)83
84# Callback, toks[0] is "foo" for xkb_symbols "foo"85def new_symbols_section(name, loc, toks):86assert len(toks) == 187sections.append(XkbSymbols(file, toks[0]))88
89# Callback, toks[0] is "foo(bar)" for include "foo(bar)"90def append_includes(name, loc, toks):91assert len(toks) == 192sections[-1].includes.append(toks[0])93
94EOL = LineEnd().suppress()95SECTIONTYPE = (96"default",97"partial",98"hidden",99"alphanumeric_keys",100"modifier_keys",101"keypad_keys",102"function_keys",103"alternate_group",104)105NAME = quoted("name").setParseAction(new_symbols_section)106INCLUDE = (107lit("include") + quoted("include").setParseAction(append_includes) + EOL108)109# We only care about includes110OTHERLINE = And([~lit("};"), ~lit("include") + Regex(".*")]) + EOL111
112with open(file) as fd:113types = OneOrMore(oneOf(SECTIONTYPE)).suppress()114include_or_other = Or([INCLUDE, OTHERLINE.suppress()])115section = (116types
117+ lit("xkb_symbols")118+ NAME119+ lit("{")120+ OneOrMore(include_or_other)121+ lit("};")122)123grammar = OneOrMore(section)124grammar.ignore(cppStyleComment)125try:126grammar.parseFile(fd)127except ParseException as e:128raise XkbLoader.XkbParserException(str(e))129
130self.loaded[file] = sections131
132return sections133
134
135def lit(string):136return Literal(string).suppress()137
138
139def print_section(s: XkbSymbols, filter_section: str | None = None, indent=0):140if filter_section and s.name != filter_section:141return142
143layout = Word(alphanums + "_/").setResultsName("layout")144variant = Optional(145lit("(") + Word(alphanums + "_").setResultsName("variant") + lit(")")146)147grammar = layout + variant148
149prefix = ""150if indent > 0:151prefix = " " * (indent - 2) + "|-> "152print(f"{prefix}{s.layout}({s.name})")153for include in s.includes:154result = grammar.parseString(include)155# Should really find the "default" section but for this script156# hardcoding "basic" is good enough157layout, variant = result.layout, result.variant or "basic"158
159include_sections = XkbLoader.load_symbols(layout)160for include_section in include_sections:161print_section(include_section, filter_section=variant, indent=indent + 4)162
163
164def list_sections(sections: list[XkbSymbols], filter_section: str | None = None):165for section in sections:166print_section(section, filter_section)167
168
169if __name__ == "__main__":170parser = argparse.ArgumentParser(171description="""172XKB symbol tree viewer.
173
174This tool takes a symbols file and optionally a section in that
175file and recursively walks the include directives in that section.
176The resulting tree may be useful for checking which files
177are affected when a single section is modified.
178"""
179)180parser.add_argument(181"file",182metavar="file-or-directory",183type=pathlib.Path,184help="The XKB symbols file or directory",185)186parser.add_argument(187"section", type=str, default=None, nargs="?", help="The section (optional)"188)189ns = parser.parse_args()190
191if ns.file.is_dir():192xkb_basedir = ns.file.resolve()193files = sorted([f for f in ns.file.iterdir() if not f.is_dir()])194else:195# Note: this requires that the file given on the cmdline is not one of196# the sun_vdr/de or others inside a subdirectory. meh.197xkb_basedir = ns.file.parent.resolve()198files = [ns.file]199
200XkbLoader.create(xkb_basedir)201
202try:203for file in files:204try:205sections = XkbLoader.load_symbols(file.resolve())206list_sections(sections, filter_section=ns.section)207except XkbLoader.XkbParserException:208pass209except KeyboardInterrupt:210pass211