import re
import sys
import colorama
from loguru import logger
[docs]def user_note(
note_heading, note_text, color=colorama.Fore.YELLOW, dsymbols=["``"], hints=[]
):
"""
Notify the user about something. In the future this should also write in the log.
Parameters
----------
note_heading : str
Note type used for the heading.
text : str
Text clarifying the note.
"""
reset_s = colorama.Style.RESET_ALL
# Convert a list of strings to a single string
if isinstance(note_text, list):
new_note_text = ""
for item in note_text:
new_note_text = f"{new_note_text}- {item}\n"
note_text = new_note_text
# Add hints to the note_text
note_text = user_note_hints(note_text, hints)
# Add color to the note_text
for dsymbol in dsymbols:
note_text = re.sub(
f"{dsymbol}([^{dsymbol}]*){dsymbol}", f"{color}\\1{reset_s}", str(note_text)
)
print(f"\n{color}{note_heading}\n{'-' * len(note_heading)}{reset_s}")
print(f"{note_text}\n")
[docs]def user_error(error_type, error_text, exit_code=1, dsymbols=["``"], hints=[]):
"""
User-friendly error using ``sys.exit()`` instead of an ``Exception``.
Parameters
----------
error_type : str
Error type used for the error heading.
text : str
Text clarifying the error.
exit_code : int
The exit code to send back to the parent process (default to 1)
"""
error_title = "ERROR: " + error_type
user_note(
error_title, error_text, color=colorama.Fore.RED, dsymbols=dsymbols, hints=hints
)
sys.exit(exit_code)
[docs]def user_note_hints(note_text, hints):
"""
Add hints to the note text. The hints are added to the note text by replacing
the placeholders "@HINT_#@" with the actual hints from the hints list.
Parameters
----------
note_text : str
The note text with placeholders for the hints. The placeholders are in the
form "@HINT_#@", where # is the index of the hint in the hints list.
hints : list
A list of hints to be added to the note text. Each hint is a dictionary with
the following keys:
- type: The type of the hint (e.g., "prov" for provenance)
- text: The text of the hint. This text can contain a placeholder "@HINT@"
which will be replaced with the actual hint corresponding to its index.
- object: The object to which the hint applies
Returns
-------
note_text : str
The note text with the placeholders replaced with the hints.
"""
# Find all hints matching r"@HINT_(\d+)@" in the note_text
pattern = r"@HINT_(\d+)@"
hint_indexes = [int(x) for x in list(set(re.findall(pattern, note_text)))]
hint_indexes.sort()
# Check whether all hint indexes are represented in the hints list
if hint_indexes != list(range(len(hints))):
raise ValueError(
"The hint indexes in the note_text do not match the hints list."
)
# Loop through the hints and replace the placeholders in the note_text
for indx, hint in enumerate(hints):
# Hint type provenance
if hint["type"] == "prov":
hint_text = hint["text"]
mapping_with_provenance = hint["object"]
# Get the provenance
provenance = None
if hasattr(mapping_with_provenance, "extract_first_nested_values_provenance"):
provenance = mapping_with_provenance.extract_first_nested_values_provenance()
elif hasattr(mapping_with_provenance, "provenance"):
provenance = mapping_with_provenance.provenance[-1]
else:
logger.debug("No provenance found for %s", mapping_with_provenance)
# If the provenance is found, replace the placeholder with the provenance,
# otherwise remove the placeholder (provenance might not always exist)
if provenance:
prov_string = (
f"``{provenance.get('yaml_file')}``,"
f"line:``{provenance.get('line')}``,"
f"col:``{provenance.get('col')}``"
)
# Replace the HINT placeholder of the hint with the provenance string
hint_text = hint["text"].replace("@HINT@", prov_string)
# Replace the HINT placeholder on the note message with the final hint
note_text = note_text.replace(f"@HINT_{indx}@", hint_text)
else:
note_text = note_text.replace(f"@HINT_{indx}@", "")
else:
raise NotImplementedError(
f"Hint type {hint['type']} is not implemented yet."
)
return note_text