# -*- coding: utf-8 -*-
"""Generic `Parser` implementation that can easily be extended to work with any of the `cod-tools` scripts."""
import traceback
from aiida.common import exceptions
from aiida.orm import Dict
from aiida.parsers.parser import Parser
from aiida.plugins import CalculationFactory, DataFactory
CifBaseCalculation = CalculationFactory('codtools.cif_base') # pylint: disable=invalid-name
CifData = DataFactory('core.cif') # pylint: disable=invalid-name
[docs]class CifBaseParser(Parser):
"""Generic `Parser` implementation that can easily be extended to work with any of the `cod-tools` scripts."""
# pylint: disable=inconsistent-return-statements
_supported_calculation_class = CifBaseCalculation
def __init__(self, node):
super().__init__(node)
if not issubclass(node.process_class, self._supported_calculation_class):
supported = self._supported_calculation_class
raise exceptions.ParsingError(
f'Node process class must be a {supported} but node<{node.uuid}> has process class {node.process_class}'
)
[docs] def parse(self, **kwargs):
"""Parse the contents of the output files retrieved in the `FolderData`."""
output_folder = self.retrieved
filename_stdout = self.node.get_attribute('output_filename')
filename_stderr = self.node.get_attribute('error_filename')
try:
with output_folder.open(filename_stderr, 'r') as handle:
exit_code = self.parse_stderr(handle)
except (OSError, IOError):
self.logger.exception('Failed to read the stderr file\n%s', traceback.format_exc())
return self.exit_codes.ERROR_READING_ERROR_FILE
if exit_code:
return exit_code
try:
with output_folder.open(filename_stdout, 'rb') as handle:
handle.seek(0)
exit_code = self.parse_stdout(handle)
except (OSError, IOError):
self.logger.exception('Failed to read the stdout file\n%s', traceback.format_exc())
return self.exit_codes.ERROR_READING_OUTPUT_FILE
if exit_code:
return exit_code
[docs] def parse_stdout(self, filelike):
"""Parse the content written by the script to standard out into a `CifData` object.
:param filelike: filelike object of stdout
:returns: an exit code in case of an error, None otherwise
"""
from CifFile import StarError
if not filelike.read().strip():
return self.exit_codes.ERROR_EMPTY_OUTPUT_FILE
try:
filelike.seek(0)
cif = CifData(file=filelike)
except StarError:
self.logger.exception('Failed to parse a `CifData` from the stdout file\n%s', traceback.format_exc())
return self.exit_codes.ERROR_PARSING_CIF_DATA
else:
self.out('cif', cif)
return
[docs] def parse_stderr(self, filelike):
"""Parse the content written by the script to standard err.
:param filelike: filelike object of stderr
:returns: an exit code in case of an error, None otherwise
"""
marker_error = 'ERROR,'
marker_warning = 'WARNING,'
messages = {'errors': [], 'warnings': []}
for line in filelike.readlines():
if marker_error in line:
messages['errors'].append(line.split(marker_error)[-1].strip())
if marker_warning in line:
messages['warnings'].append(line.split(marker_warning)[-1].strip())
if self.node.get_option('attach_messages'):
self.out('messages', Dict(dict=messages))
for error in messages['errors']:
if 'unknown option' in error:
return self.exit_codes.ERROR_INVALID_COMMAND_LINE_OPTION
return