blob: f48905acde5192249b74a83d4ffb91342f5ffee6 [file] [log] [blame]
"""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))