"""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 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))