Форк
0
/
symbols-tree.py 
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

7
import argparse
8
import pathlib
9
import dataclasses
10
from pyparsing import (
11
    Word,
12
    Literal,
13
    LineEnd,
14
    OneOrMore,
15
    oneOf,
16
    Or,
17
    And,
18
    QuotedString,
19
    Regex,
20
    cppStyleComment,
21
    alphanums,
22
    Optional,
23
    ParseException,
24
)
25

26
xkb_basedir = None
27

28

29
@dataclasses.dataclass
30
class XkbSymbols:
31
    file: pathlib.Path  # Path to the file this section came from
32
    name: str
33
    includes: list[str] = dataclasses.field(default_factory=list)
34

35
    @property
36
    def layout(self) -> str:
37
        return self.file.name  # XKb - filename is the layout name
38

39
    def __str__(self):
40
        return f"{self.layout}({self.name}): {self.includes}"
41

42

43
class XkbLoader:
44
    """
45
    Wrapper class to avoid loading the same symbols file over and over
46
    again.
47
    """
48

49
    class XkbParserException(Exception):
50
        pass
51

52
    _instance = None
53

54
    def __init__(self, xkb_basedir):
55
        self.xkb_basedir = xkb_basedir
56
        self.loaded = {}
57

58
    @classmethod
59
    def create(cls, xkb_basedir):
60
        assert cls._instance is None
61
        cls._instance = XkbLoader(xkb_basedir)
62

63
    @classmethod
64
    def instance(cls):
65
        assert cls._instance is not None
66
        return cls._instance
67

68
    @classmethod
69
    def load_symbols(cls, file):
70
        return cls.instance().load_symbols_file(file)
71

72
    def load_symbols_file(self, file) -> list[XkbSymbols]:
73
        file = self.xkb_basedir / file
74
        try:
75
            return self.loaded[file]
76
        except KeyError:
77
            pass
78

79
        sections = []
80

81
        def quoted(name):
82
            return QuotedString(quoteChar='"', unquoteResults=True)
83

84
        # Callback, toks[0] is "foo" for xkb_symbols "foo"
85
        def new_symbols_section(name, loc, toks):
86
            assert len(toks) == 1
87
            sections.append(XkbSymbols(file, toks[0]))
88

89
        # Callback, toks[0] is "foo(bar)" for include "foo(bar)"
90
        def append_includes(name, loc, toks):
91
            assert len(toks) == 1
92
            sections[-1].includes.append(toks[0])
93

94
        EOL = LineEnd().suppress()
95
        SECTIONTYPE = (
96
            "default",
97
            "partial",
98
            "hidden",
99
            "alphanumeric_keys",
100
            "modifier_keys",
101
            "keypad_keys",
102
            "function_keys",
103
            "alternate_group",
104
        )
105
        NAME = quoted("name").setParseAction(new_symbols_section)
106
        INCLUDE = (
107
            lit("include") + quoted("include").setParseAction(append_includes) + EOL
108
        )
109
        # We only care about includes
110
        OTHERLINE = And([~lit("};"), ~lit("include") + Regex(".*")]) + EOL
111

112
        with open(file) as fd:
113
            types = OneOrMore(oneOf(SECTIONTYPE)).suppress()
114
            include_or_other = Or([INCLUDE, OTHERLINE.suppress()])
115
            section = (
116
                types
117
                + lit("xkb_symbols")
118
                + NAME
119
                + lit("{")
120
                + OneOrMore(include_or_other)
121
                + lit("};")
122
            )
123
            grammar = OneOrMore(section)
124
            grammar.ignore(cppStyleComment)
125
            try:
126
                grammar.parseFile(fd)
127
            except ParseException as e:
128
                raise XkbLoader.XkbParserException(str(e))
129

130
        self.loaded[file] = sections
131

132
        return sections
133

134

135
def lit(string):
136
    return Literal(string).suppress()
137

138

139
def print_section(s: XkbSymbols, filter_section: str | None = None, indent=0):
140
    if filter_section and s.name != filter_section:
141
        return
142

143
    layout = Word(alphanums + "_/").setResultsName("layout")
144
    variant = Optional(
145
        lit("(") + Word(alphanums + "_").setResultsName("variant") + lit(")")
146
    )
147
    grammar = layout + variant
148

149
    prefix = ""
150
    if indent > 0:
151
        prefix = " " * (indent - 2) + "|-> "
152
    print(f"{prefix}{s.layout}({s.name})")
153
    for include in s.includes:
154
        result = grammar.parseString(include)
155
        # Should really find the "default" section but for this script
156
        # hardcoding "basic" is good enough
157
        layout, variant = result.layout, result.variant or "basic"
158

159
        include_sections = XkbLoader.load_symbols(layout)
160
        for include_section in include_sections:
161
            print_section(include_section, filter_section=variant, indent=indent + 4)
162

163

164
def list_sections(sections: list[XkbSymbols], filter_section: str | None = None):
165
    for section in sections:
166
        print_section(section, filter_section)
167

168

169
if __name__ == "__main__":
170
    parser = argparse.ArgumentParser(
171
        description="""
172
            XKB symbol tree viewer.
173

174
            This tool takes a symbols file and optionally a section in that
175
            file and recursively walks the include directives in that section.
176
            The resulting tree may be useful for checking which files
177
            are affected when a single section is modified.
178
            """
179
    )
180
    parser.add_argument(
181
        "file",
182
        metavar="file-or-directory",
183
        type=pathlib.Path,
184
        help="The XKB symbols file or directory",
185
    )
186
    parser.add_argument(
187
        "section", type=str, default=None, nargs="?", help="The section (optional)"
188
    )
189
    ns = parser.parse_args()
190

191
    if ns.file.is_dir():
192
        xkb_basedir = ns.file.resolve()
193
        files = sorted([f for f in ns.file.iterdir() if not f.is_dir()])
194
    else:
195
        # Note: this requires that the file given on the cmdline is not one of
196
        # the sun_vdr/de or others inside a subdirectory. meh.
197
        xkb_basedir = ns.file.parent.resolve()
198
        files = [ns.file]
199

200
    XkbLoader.create(xkb_basedir)
201

202
    try:
203
        for file in files:
204
            try:
205
                sections = XkbLoader.load_symbols(file.resolve())
206
                list_sections(sections, filter_section=ns.section)
207
            except XkbLoader.XkbParserException:
208
                pass
209
    except KeyboardInterrupt:
210
        pass
211

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.