173 lines
5.6 KiB
Python
173 lines
5.6 KiB
Python
|
"""Provides the BasePrinter base class for MacroChecker/Message output techniques."""
|
||
|
|
||
|
# Copyright (c) 2018-2019 Collabora, Ltd.
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
#
|
||
|
# 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 possibly outputting
|
||
|
broken links (if broken_links==True)
|
||
|
and missing includes (if missing_includes==True).
|
||
|
"""
|
||
|
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)
|
||
|
|
||
|
@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)
|
||
|
|
||
|
###
|
||
|
# Helper function
|
||
|
def getRelativeFilename(self, fn):
|
||
|
"""Return the given filename relative to the current directory, if possible."""
|
||
|
if not self.cwd:
|
||
|
# lazy init this member.
|
||
|
self.cwd = Path('.').resolve()
|
||
|
try:
|
||
|
return str(Path(fn).relative_to(self.cwd))
|
||
|
except ValueError:
|
||
|
return str(Path(fn))
|