import logging from base64 import decodebytes from collections import OrderedDict from contextlib import suppress from pathlib import Path from time import localtime, strftime import jinja2 from common_helper_files import human_readable_file_size GENERIC_TEMPLATE = 'generic.tex' MAIN_TEMPLATE = 'main.tex' META_TEMPLATE = 'meta.tex' PLUGIN_TEMPLATE_BLUEPRINT = '{}.tex' LOGO_FILE = 'fact_logo.png' def render_number_as_size(number, verbose=True): if not isinstance(number, (int, float)): return 'not available' if verbose: return '{} ({})'.format(human_readable_file_size(int(number)), format(number, ',d') + ' Byte') return human_readable_file_size(int(number)) def render_unix_time(unix_time_stamp): if not isinstance(unix_time_stamp, (int, float)): return 'not available' return strftime('%Y-%m-%d %H:%M:%S', localtime(unix_time_stamp)) def render_number_as_string(number): if isinstance(number, int): return '{:,}'.format(number) if isinstance(number, float): return '{:,.2f}'.format(number) if isinstance(number, str): with suppress(ValueError): return str(int(number)) return 'not available' def replace_special_characters(data): latex_character_escapes = OrderedDict() latex_character_escapes['\\'] = '' latex_character_escapes['\''] = '' latex_character_escapes['$'] = '\\$' latex_character_escapes['('] = '$($' latex_character_escapes[')'] = '$)$' latex_character_escapes['['] = '$[$' latex_character_escapes[']'] = '$]$' latex_character_escapes['#'] = '\\#' latex_character_escapes['%'] = '\\%' latex_character_escapes['&'] = '\\&' latex_character_escapes['_'] = '\\_' latex_character_escapes['{'] = '\\{' latex_character_escapes['}'] = '\\}' latex_character_escapes['^'] = '\\textasciicircum{}' latex_character_escapes['~'] = '\\textasciitilde{}' latex_character_escapes['>'] = '\\textgreater{}' latex_character_escapes['<'] = '\\textless{}' latex_character_escapes['\n'] = '\\newline ' for character, replacement in latex_character_escapes.items(): if character in data: data = data.replace(character, replacement) return data def decode_base64_to_file(base64_string, filename, directory, suffix='png'): file_path = Path(directory, '{}.{}'.format(filename, suffix)) file_path.write_bytes(decodebytes(base64_string.encode('utf-8'))) return str(file_path) def replace_characters_in_list(list_of_strings): return [ replace_special_characters(item) for item in list_of_strings ] def split_hash_string(hash_string, max_length=61): if len(hash_string) > max_length: hash_string = '{}\n{}'.format(hash_string[:max_length], hash_string[max_length:]) return hash_string def split_long_lines(multiline_string, max_length=92): def evaluate_split(line): return line if len(line) <= max_length else '{}\n{}'.format(line[:max_length], line[max_length:]) return ''.join( evaluate_split(line) for line in multiline_string.splitlines(keepends=True) ) def item_contains_string(item, string): if not isinstance(item, str): return False return string in item def create_jinja_environment(templates_to_use='default'): template_directory = Path(Path(__file__).parent.parent, 'templates', templates_to_use) environment = jinja2.Environment( block_start_string=r'\BLOCK{', block_end_string='}', variable_start_string=r'\VAR{', variable_end_string='}', comment_start_string=r'\#{', comment_end_string='}', line_statement_prefix='%%', line_comment_prefix='%#', trim_blocks=True, autoescape=False, loader=jinja2.FileSystemLoader(str(template_directory)) ) _add_filters_to_jinja(environment) return environment def plugin_name(name): return ' '.join((part.title() for part in name.split('_'))) def _add_filters_to_jinja(environment): environment.filters['number_format'] = render_number_as_size environment.filters['nice_unix_time'] = render_unix_time environment.filters['nice_number'] = render_number_as_string environment.filters['filter_chars'] = replace_special_characters environment.filters['elements_count'] = len environment.filters['base64_to_png'] = decode_base64_to_file environment.filters['check_list'] = lambda x: x if x else ['list is empty'] environment.filters['plugin_name'] = plugin_name environment.filters['filter_list'] = replace_characters_in_list environment.filters['split_hash'] = split_hash_string environment.filters['split_output_lines'] = split_long_lines environment.filters['contains'] = item_contains_string class TemplateEngine: def __init__(self, template_folder=None, tmp_dir=None): self._environment = create_jinja_environment(template_folder if template_folder else 'default') self._tmp_dir = tmp_dir def render_main_template(self, analysis, meta_data): template = self._environment.get_template(MAIN_TEMPLATE) return template.render(analysis=analysis, meta_data=meta_data) def render_meta_template(self, meta_data): template = self._environment.get_template(META_TEMPLATE) return template.render(meta_data=meta_data) def render_analysis_template(self, plugin, analysis): try: template = self._environment.get_template(PLUGIN_TEMPLATE_BLUEPRINT.format(plugin)) except jinja2.TemplateNotFound: logging.warning('Falling back on generic template for {}'.format(plugin)) template = self._environment.get_template(GENERIC_TEMPLATE) return template.render(plugin_name=plugin, selected_analysis=analysis, tmp_dir=self._tmp_dir)