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