• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Provides the BasePrinter base class for MacroChecker/Message output techniques."""
2
3# Copyright (c) 2018-2019 Collabora, Ltd.
4#
5# SPDX-License-Identifier: Apache-2.0
6#
7# Author(s):    Ryan Pavlik <ryan.pavlik@collabora.com>
8
9from abc import ABC, abstractmethod
10from pathlib import Path
11
12from .macro_checker import MacroChecker
13from .macro_checker_file import MacroCheckerFile
14from .shared import EntityData, Message, MessageContext, MessageType
15
16
17def getColumn(message_context):
18    """Return the (zero-based) column number of the message context.
19
20    If a group is specified: returns the column of the start of the group.
21    If no group, but a match is specified: returns the column of the start of
22    the match.
23    If no match: returns column 0 (whole line).
24    """
25    if not message_context.match:
26        # whole line
27        return 0
28    if message_context.group is not None:
29        return message_context.match.start(message_context.group)
30    return message_context.match.start()
31
32
33class BasePrinter(ABC):
34    """Base class for a way of outputting results of a checker execution."""
35
36    def __init__(self):
37        """Constructor."""
38        self._cwd = None
39
40    def close(self):
41        """Write the tail end of the output and close it, if applicable.
42
43        Override if you want to print a summary or are writing to a file.
44        """
45        pass
46
47    ###
48    # Output methods: these should all print/output directly.
49    def output(self, obj):
50        """Output any object.
51
52        Delegates to other output* methods, if type known,
53        otherwise uses self.outputFallback().
54        """
55        if isinstance(obj, Message):
56            self.outputMessage(obj)
57        elif isinstance(obj, MacroCheckerFile):
58            self.outputCheckerFile(obj)
59        elif isinstance(obj, MacroChecker):
60            self.outputChecker(obj)
61        else:
62            self.outputFallback(self.formatBrief(obj))
63
64    @abstractmethod
65    def outputResults(self, checker, broken_links=True,
66                      missing_includes=False):
67        """Output the full results of a checker run.
68
69        Must be implemented.
70
71        Typically will call self.output() on the MacroChecker,
72        as well as calling self.outputBrokenAndMissing()
73        """
74        raise NotImplementedError
75
76    @abstractmethod
77    def outputBrokenLinks(self, checker, broken):
78        """Output the collection of broken links.
79
80        `broken` is a dictionary of entity names: usage contexts.
81
82        Must be implemented.
83
84        Called by self.outputBrokenAndMissing() if requested.
85        """
86        raise NotImplementedError
87
88    @abstractmethod
89    def outputMissingIncludes(self, checker, missing):
90        """Output a table of missing includes.
91
92        `missing` is a iterable entity names.
93
94        Must be implemented.
95
96        Called by self.outputBrokenAndMissing() if requested.
97        """
98        raise NotImplementedError
99
100    def outputChecker(self, checker):
101        """Output the contents of a MacroChecker object.
102
103        Default implementation calls self.output() on every MacroCheckerFile.
104        """
105        for f in checker.files:
106            self.output(f)
107
108    def outputCheckerFile(self, fileChecker):
109        """Output the contents of a MacroCheckerFile object.
110
111        Default implementation calls self.output() on every Message.
112        """
113        for m in fileChecker.messages:
114            self.output(m)
115
116    def outputBrokenAndMissing(self, checker, broken_links=True,
117                               missing_includes=False):
118        """Outputs broken links and missing includes, if desired.
119
120        Delegates to self.outputBrokenLinks() (if broken_links==True)
121        and self.outputMissingIncludes() (if missing_includes==True).
122        """
123        if broken_links:
124            broken = checker.getBrokenLinks()
125            if broken:
126                self.outputBrokenLinks(checker, broken)
127        if missing_includes:
128            missing = checker.getMissingUnreferencedApiIncludes()
129            if missing:
130                self.outputMissingIncludes(checker, missing)
131
132    @abstractmethod
133    def outputMessage(self, msg):
134        """Output a Message.
135
136        Must be implemented.
137        """
138        raise NotImplementedError
139
140    @abstractmethod
141    def outputFallback(self, msg):
142        """Output some text in a general way.
143
144        Must be implemented.
145        """
146        raise NotImplementedError
147
148    ###
149    # Format methods: these should all return a string.
150    def formatContext(self, context, _message_type=None):
151        """Format a message context in a verbose way, if applicable.
152
153        May override, default implementation delegates to
154        self.formatContextBrief().
155        """
156        return self.formatContextBrief(context)
157
158    def formatContextBrief(self, context, _with_color=True):
159        """Format a message context in a brief way.
160
161        May override, default is relativeFilename:line:column
162        """
163        return '{}:{}:{}'.format(self.getRelativeFilename(context.filename),
164                                 context.lineNum, getColumn(context))
165
166    def formatMessageTypeBrief(self, message_type, _with_color=True):
167        """Format a message type in a brief way.
168
169        May override, default is message_type:
170        """
171        return '{}:'.format(message_type)
172
173    def formatEntityBrief(self, entity_data, _with_color=True):
174        """Format an entity in a brief way.
175
176        May override, default is macro:entity.
177        """
178        return '{}:{}'.format(entity_data.macro, entity_data.entity)
179
180    def formatBrief(self, obj, with_color=True):
181        """Format any object in a brief way.
182
183        Delegates to other format*Brief methods, if known,
184        otherwise uses str().
185        """
186        if isinstance(obj, MessageContext):
187            return self.formatContextBrief(obj, with_color)
188        if isinstance(obj, MessageType):
189            return self.formatMessageTypeBrief(obj, with_color)
190        if isinstance(obj, EntityData):
191            return self.formatEntityBrief(obj, with_color)
192        return str(obj)
193
194    @property
195    def cwd(self):
196        """Get the current working directory, fully resolved.
197
198        Lazy initialized.
199        """
200        if not self._cwd:
201            self._cwd = Path('.').resolve()
202        return self._cwd
203
204    ###
205    # Helper function
206    def getRelativeFilename(self, fn):
207        """Return the given filename relative to the current directory,
208        if possible.
209        """
210        try:
211            return str(Path(fn).relative_to(self.cwd))
212        except ValueError:
213            return str(Path(fn))
214