import datetime
from jinja2 import Environment, FileSystemLoader
from loguru import logger
from pathlib import Path
from typing import Any, Union, Optional
from . import TEMPLATE_DIR
#----------------------------------------------------------------------------------------------------------------------#
# region COLOR MAP
#----------------------------------------------------------------------------------------------------------------------#
[docs]
class ColorMap:
""" Color map object for handling the color space of the report """
def __init__(self, config: dict[str, Any], color_mode: str, text_color: Optional[str]=None):
""" Initialize a new color map given a config dict
Args:
config (dict[str, Any]): The configuration for this report. Mainly responsible for defining new color
profiles that can later be used
color_mode (str): Select the current color mode of the report, i.e., `dark` or `light`
text_color (Optional[str]): Override for the text color of the report. For a more granular approach use the
config file
"""
self.config = config
self.color_mode = color_mode
self.text_color = text_color
# Prepare the jinja templater
self.file_loader = FileSystemLoader(TEMPLATE_DIR)
self.jinja_env = Environment(loader=self.file_loader)
# Default color map - Maps the default bootstrap colors to their respective color names
self.cm = {
'blue': 'primary',
'gray': 'secondary',
'green': 'success',
'red': 'danger',
'yellow': 'warning',
'teal': 'info',
'black': 'dark',
'primary': 'primary',
'secondary': 'secondary',
'success': 'success',
'danger': 'danger',
'warning': 'warning',
'info': 'info',
'dark': 'dark',
'light': 'light',
}
# Create the custom color CSS that will be injected and make the profiles available in the color map
for cc in self.config['custom_colors']:
self.cm[cc] = cc
#---------------#
# Magic Methods #
#---------------#
def __getitem__(self, key: str) -> str:
""" Verify the color name and return the proper color mapping """
return self.verify_and_get_color(key)
#---------#
# Private #
#---------#
def _make_color_css_files(self, output_dir: Path):
""" Create the custom color css file """
# Create CSS string
color_items = [v | {'name': k} for k,v in self.config['custom_colors'].items()]
# Color CSS
template = self.jinja_env.get_template('colors.css')
css = template.render(color_items=color_items)
# Dump to scripts dir
custom_color_file = output_dir / 'custom_colors.css'
custom_color_file.write_text(css)
# Color CSS
template = self.jinja_env.get_template('navbar.css')
css = template.render(color_items=color_items)
# Dump to scripts dir
navbar_file = output_dir / 'navbar.css'
navbar_file.write_text(css)
def _make_css_file(self, element_type: str, output_dir: Path):
""" Create the custom css files for the element type """
# Create CSS string
colors = self.config['css'][element_type][self.color_mode]
template = self.jinja_env.get_template(f'{element_type}.css')
css = template.render(**colors)
# Dump to scripts dir
css_file = output_dir / f'{element_type}.css'
css_file.write_text(css)
#-----#
# API #
#-----#
[docs]
def verify_and_get_color(self, color: str) -> str:
""" Check that the requested color is actually available
Args:
color (str): Color to check
Returns:
str - color name to use
"""
if color not in self.cm:
msg = f'No color `{color}` found. Available: {self.cm.keys()}'
logger.error(msg)
raise RuntimeError(msg)
return self.cm[color]
[docs]
def get_default_color(self, field: str) -> str:
""" Determine the correct default color for a given color mode and field
Args:
field (str): The type/field the color should be used for
color_mode (str): The color mode of the report, i.e., either `light` or `dark`
Returns:
str - The color to be used
"""
if self.text_color is None or field not in ['text', 'table', 'report_ball']:
color = self.config['default_color_profiles'][self.color_mode].get(field)
else:
color = self.text_color
if color is None:
msg = f'No default color defined for `{field}` with color mode `{self.color_mode}`'
logger.error(msg)
raise RuntimeError(msg)
return color
[docs]
def make_css_files(self, output_dir: Path):
""" Create the custom css files for the site
Args:
output_dir (Path): The reports css and script dir
"""
self._make_color_css_files(output_dir)
for element_type in ['site', 'tabs', 'accordion', 'table']:
self._make_css_file(element_type, output_dir)
#----------------------------------------------------------------------------------------------------------------------#
# region MISC HELPER
#----------------------------------------------------------------------------------------------------------------------#
[docs]
def coerce_to_date(date: Union[str, int, datetime.date, datetime.datetime]) -> datetime.date:
""" Coerce random data formats to a datetime.date object
Args:
date (Union[str, int, datetime.date, datetime.datetime]): The date to coerce
Returns:
datetime.date - The coerce date object
"""
if isinstance(date, datetime.date):
return date
elif isinstance(date, datetime.datetime):
return date.date
elif isinstance(date, int):
date = str(int)
if not isinstance(date, str):
msg = f'Cannot coerce date {date}'
logger.error(msg)
raise RuntimeError(msg)
if len(date) == 6: # noqa: PLR2004
year = int(date[:2])
if year < 50: # noqa: PLR2004
year += 2000
else:
year += 1900
month = int(date[2:4])
day = int(date[4:])
elif len(date) == 8: # noqa: PLR2004
if date.isdigit():
year = int(date[:4])
month = int(date[4:6])
day = int(date[6:])
else:
year = int(date[:2])
if year < 50: # noqa: PLR2004
year += 2000
else:
year += 1900
month = int(date[3:5])
day = int(date[6:])
elif len(date) == 10: # noqa: PLR2004
year = int(date[:4])
month = int(date[5:7])
day = int(date[8:])
return datetime.date(year, month, day)
[docs]
def verify_alignment(align: str) -> str:
""" Check that the requested alignment is actually available
Args:
align (str): The alignment tag that needs to be verified
Returns:
str - The alignment if it is acceptable
"""
alignments = ['left', 'center', 'right']
if align not in alignments:
msg = f'Unknown alignment {align}. Available: {alignments}'
logger.error(msg)
raise RuntimeError(msg)
return align
[docs]
def verify_color_mode(color_mode: str) -> str:
""" Check that the requested alignment is actually available
Args:
color_mode (str): The alignment tag that needs to be verified
Returns:
str - The alignment if it is acceptable
"""
color_modes = ['light', 'dark']
if color_mode not in color_modes:
msg = f'Unknown color_mode {color_mode}. Available: {color_modes}'
logger.error(msg)
raise RuntimeError(msg)
return color_mode
[docs]
def default(var: Any, default: Any) -> Any:
""" Helper function to check kwargs and set them to a default
You could simply use var or default. But for future additions we will use a separat function here
Args:
var (Any): The input variable
default (Any): Default value for the variable
Returns
Any - Either the variable itself, or the default value if var is `None`
"""
if var is None:
return default
else:
return var