import logging
import traceback
import importlib
import inspect
import sys
import os
from chiptools.common.exceptions import FileNotFoundError
log = logging.getLogger(__name__)
preprocessor_temporary_module = 'chiptools_preprocessor_temporary_module'
[docs]def get_preprocessor(path):
"""Import the Python script supplied by the path and return a handle to a
preprocessor function from the import. It is expected that the file to be
imported contains a function called 'process' that accepts a list of file
data and a file path. If these conditions are not met this function will
return None.
"""
if preprocessor_temporary_module in sys.modules:
# Clear the reference to the testPackageModule module
# TODO: Although unlikely, it is possible that we may delete an
# existing module from the modules list, is there a more robust way of
# achieving this functionality?
del sys.modules[preprocessor_temporary_module]
if not os.path.exists(path):
log.error('File not found, aborting preprocessor load: ' + str(path))
return
try:
# We are loading unchecked user code here, the import stage is
# exception checked.
importlib.machinery.SourceFileLoader(
preprocessor_temporary_module,
path,
).load_module()
import chiptools_preprocessor_temporary_module # type: ignore
except Exception:
log.error(
'The module could not be imported due to the '
+ ' following error:'
)
log.error(traceback.format_exc())
return None
# Search the module members until a function with the name 'process' is
# found. If no function can be found return None
for name, obj in inspect.getmembers(
chiptools_preprocessor_temporary_module
):
if hasattr(obj, '__name__'):
if obj.__name__ == 'process' and callable(obj):
return obj
return None
[docs]class Preprocessor:
"""Preprocessor class to handle file preprocessor execution."""
def __init__(self):
super(Preprocessor, self).__init__()
[docs] @classmethod
def process(cls, path, processor_path):
"""
Execute the preprocessor on the given file, return True on success
"""
processor = get_preprocessor(processor_path)
if processor is None:
print(processor_path + ' not found ')
return False
try:
data = cls.get_file_data(path)
except FileNotFoundError:
log.error('Preprocessor could not open {0}'.format(path))
return False
try:
data = processor(data, path)
except Exception:
log.error(
'The preprocessor caused an exception, '
+ 'no modifications were made'
)
log.error(traceback.format_exc())
return False
cls.set_file_data(path, data)
return True
[docs] @classmethod
def get_file_data(cls, path):
"""Return the file data as a list of lines."""
try:
with open(path, 'r') as fileToProcess:
return fileToProcess.readlines()
except FileNotFoundError:
log.error('Preprocessor could not open {0}'.format(path))
[docs] @classmethod
def set_file_data(cls, path, fileData):
"""Update the file with the new file data."""
if fileData is None:
return
with open(path, 'w') as fileToUpdate:
for line in fileData:
fileToUpdate.write(line)