File: base_printer.py

package info (click to toggle)
openxr-sdk-source 1.0.14~dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,564 kB
  • sloc: python: 16,103; cpp: 12,052; ansic: 8,813; xml: 3,480; sh: 410; makefile: 338; ruby: 247
file content (213 lines) | stat: -rw-r--r-- 6,653 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
"""Provides the BasePrinter base class for MacroChecker/Message output techniques."""

# Copyright (c) 2018-2019 Collabora, Ltd.
#
# SPDX-License-Identifier: Apache-2.0
#
# Author(s):    Ryan Pavlik <ryan.pavlik@collabora.com>

from abc import ABC, abstractmethod
from pathlib import Path

from .macro_checker import MacroChecker
from .macro_checker_file import MacroCheckerFile
from .shared import EntityData, Message, MessageContext, MessageType


def getColumn(message_context):
    """Return the (zero-based) column number of the message context.

    If a group is specified: returns the column of the start of the group.
    If no group, but a match is specified: returns the column of the start of
    the match.
    If no match: returns column 0 (whole line).
    """
    if not message_context.match:
        # whole line
        return 0
    if message_context.group is not None:
        return message_context.match.start(message_context.group)
    return message_context.match.start()


class BasePrinter(ABC):
    """Base class for a way of outputting results of a checker execution."""

    def __init__(self):
        """Constructor."""
        self._cwd = None

    def close(self):
        """Write the tail end of the output and close it, if applicable.

        Override if you want to print a summary or are writing to a file.
        """
        pass

    ###
    # Output methods: these should all print/output directly.
    def output(self, obj):
        """Output any object.

        Delegates to other output* methods, if type known,
        otherwise uses self.outputFallback().
        """
        if isinstance(obj, Message):
            self.outputMessage(obj)
        elif isinstance(obj, MacroCheckerFile):
            self.outputCheckerFile(obj)
        elif isinstance(obj, MacroChecker):
            self.outputChecker(obj)
        else:
            self.outputFallback(self.formatBrief(obj))

    @abstractmethod
    def outputResults(self, checker, broken_links=True,
                      missing_includes=False):
        """Output the full results of a checker run.

        Must be implemented.

        Typically will call self.output() on the MacroChecker,
        as well as calling self.outputBrokenAndMissing()
        """
        raise NotImplementedError

    @abstractmethod
    def outputBrokenLinks(self, checker, broken):
        """Output the collection of broken links.

        `broken` is a dictionary of entity names: usage contexts.

        Must be implemented.

        Called by self.outputBrokenAndMissing() if requested.
        """
        raise NotImplementedError

    @abstractmethod
    def outputMissingIncludes(self, checker, missing):
        """Output a table of missing includes.

        `missing` is a iterable entity names.

        Must be implemented.

        Called by self.outputBrokenAndMissing() if requested.
        """
        raise NotImplementedError

    def outputChecker(self, checker):
        """Output the contents of a MacroChecker object.

        Default implementation calls self.output() on every MacroCheckerFile.
        """
        for f in checker.files:
            self.output(f)

    def outputCheckerFile(self, fileChecker):
        """Output the contents of a MacroCheckerFile object.

        Default implementation calls self.output() on every Message.
        """
        for m in fileChecker.messages:
            self.output(m)

    def outputBrokenAndMissing(self, checker, broken_links=True,
                               missing_includes=False):
        """Outputs broken links and missing includes, if desired.

        Delegates to self.outputBrokenLinks() (if broken_links==True)
        and self.outputMissingIncludes() (if missing_includes==True).
        """
        if broken_links:
            broken = checker.getBrokenLinks()
            if broken:
                self.outputBrokenLinks(checker, broken)
        if missing_includes:
            missing = checker.getMissingUnreferencedApiIncludes()
            if missing:
                self.outputMissingIncludes(checker, missing)

    @abstractmethod
    def outputMessage(self, msg):
        """Output a Message.

        Must be implemented.
        """
        raise NotImplementedError

    @abstractmethod
    def outputFallback(self, msg):
        """Output some text in a general way.

        Must be implemented.
        """
        raise NotImplementedError

    ###
    # Format methods: these should all return a string.
    def formatContext(self, context, _message_type=None):
        """Format a message context in a verbose way, if applicable.

        May override, default implementation delegates to
        self.formatContextBrief().
        """
        return self.formatContextBrief(context)

    def formatContextBrief(self, context, _with_color=True):
        """Format a message context in a brief way.

        May override, default is relativeFilename:line:column
        """
        return '{}:{}:{}'.format(self.getRelativeFilename(context.filename),
                                 context.lineNum, getColumn(context))

    def formatMessageTypeBrief(self, message_type, _with_color=True):
        """Format a message type in a brief way.

        May override, default is message_type:
        """
        return '{}:'.format(message_type)

    def formatEntityBrief(self, entity_data, _with_color=True):
        """Format an entity in a brief way.

        May override, default is macro:entity.
        """
        return '{}:{}'.format(entity_data.macro, entity_data.entity)

    def formatBrief(self, obj, with_color=True):
        """Format any object in a brief way.

        Delegates to other format*Brief methods, if known,
        otherwise uses str().
        """
        if isinstance(obj, MessageContext):
            return self.formatContextBrief(obj, with_color)
        if isinstance(obj, MessageType):
            return self.formatMessageTypeBrief(obj, with_color)
        if isinstance(obj, EntityData):
            return self.formatEntityBrief(obj, with_color)
        return str(obj)

    @property
    def cwd(self):
        """Get the current working directory, fully resolved.

        Lazy initialized.
        """
        if not self._cwd:
            self._cwd = Path('.').resolve()
        return self._cwd

    ###
    # Helper function
    def getRelativeFilename(self, fn):
        """Return the given filename relative to the current directory,
        if possible.
        """
        try:
            return str(Path(fn).relative_to(self.cwd))
        except ValueError:
            return str(Path(fn))