import logging
import os
import shutil
from dataclasses import dataclass, field
from . import utils
from .config import TEMPLATE_DIR, ENCODING
# Default templates, used to create custom template or document if no custom on exists
DEFAULT_TEMPLATES = {
"protocol": os.path.abspath(
os.path.join(os.path.dirname(__file__), "templates", "template_protocol.md")
),
}
logger = logging.getLogger(__name__)
[docs]@dataclass
class TemplateManager:
"""Class to manage templates used to documentate a measurement.
Args:
templates_dir (str): The path to the directory containing custom templates.
default_templates: Dictionary with keys of known *document types* and values
specifying the paths of the fallback templates for the corresponding
document types.
Raises:
ValueError: If an unknown *document type* is provided in any of its methods.
"""
template_dir: str = TEMPLATE_DIR
default_templates: dict[str, str] = field(default_factory=lambda: DEFAULT_TEMPLATES)
separator: str = ": "
@property
def types(self):
"""List with known document types"""
return list(self.default_templates.keys())
def _validate_type(self, document_type) -> None:
"""Raise a ValueError of an unknown document_type is provided."""
if document_type not in self.types:
raise ValueError(f"Unknown document type specified ({document_type!r}).")
def _get_default_template(self, document_type: str) -> str:
"""Returns path to the default template for a certain document type."""
return self.default_templates[document_type]
def _get_filename_prefix(self, document_type: str) -> str:
"""Get the prefix for a certain document type."""
return document_type + "-template_"
def _get_name_from_filename(self, document_type: str, filename: str) -> str:
"""Get the template name for a certain filename and document type."""
prefix = self._get_filename_prefix(document_type)
if not filename.startswith(prefix):
raise ValueError(f"Template filename should start with {prefix!r}.")
return os.path.splitext(filename.replace(prefix, ""))[0]
def _get_filename_from_name(self, document_type: str, name: str) -> str:
"""Get the filename for a certain template name and document type."""
prefix = self._get_filename_prefix(document_type)
_, ext = os.path.splitext(self._get_default_template(document_type))
return prefix + name + ext
[docs] def get_type_name_list(self) -> list[str]:
"""Returns list of registered templates and their document type."""
if not os.path.isdir(self.template_dir):
return None
basenames = [os.path.splitext(i)[0] for i in os.listdir(self.template_dir)]
return [i.replace("-template_", self.separator) for i in basenames]
[docs] def get_template_dict(self) -> dict[str, list[str]]:
"""Get a dictionary with document types as keys and list of names as values."""
type_name_lst = self.get_type_name_list()
types = list(set([i.split(self.separator)[0] for i in type_name_lst]))
tmplt_dct = {k: [] for k in types}
for item in type_name_lst:
k, name = item.split(self.separator)
tmplt_dct[k].append(name)
return tmplt_dct
[docs] def get_type(self, name: str) -> str:
"""Get the document type of a registered template.
Args:
name (str): The name of a registered template whose type shall be returned.
Returns:
str: The document type of the provided template name.
"""
for document_type, tmplates in self.get_template_dict().items():
if name in tmplates:
return document_type
[docs] def get_template(self, document_type: str, name: str) -> str:
"""Get the file path of a registered template.
Args:
document_type (str): The document type of the template to be returned.
name (str): The name of a registered template that shall be returned.
Returns:
str: The path to the template with the provided name and document type.
"""
filename = self._get_filename_from_name(document_type, name)
return os.path.join(self.template_dir, filename)
[docs] def get_templates(self, document_type: str, names: bool = False) -> list[str]:
"""Get a list to paths (or names) of available templates.
If no custom templates exist, the list will contain the path to the fallback
template for the specified document_type, otherwise the fallback template will
be excluded.
If names are requested, 'None' is returned if no custom template is registered
for the specified type.
Args:
document_type (str): The document type of the templates to be considered.
names (bool): Flag to indicate that template names instead of paths shall
be returned.
Returns:
list[str]: A list with paths to or names of available templates for the
specified document type.
"""
if os.path.isdir(self.template_dir):
prefix = self._get_filename_prefix(document_type)
templates = [
i for i in os.listdir(self.template_dir) if i.startswith(prefix)
]
if names:
lst = [
self._get_name_from_filename(document_type, i) for i in templates
]
else:
lst = [os.path.join(self.template_dir, i) for i in templates]
if len(lst) > 0:
return lst
if names:
return [None]
return [self._get_default_template(document_type)]
[docs] def create_template(
self,
name: str,
document_type: str,
force: bool = False,
) -> None:
"""
Create a custom template for a certain document type.
Args:
name (str): The name of the template.
document_type (str): The type of document that the template will be used for.
force (bool, optional): Whether to overwrite the template if it already exists.
Defaults to False.
Raises:
FileExistsError: If a template with the same name already exists and 'force'
is set to False.
"""
self._validate_type(document_type)
default_file = self._get_default_template(document_type)
file_path = self.get_template(document_type, name)
if not force:
utils.raise_file_exists(file_path, force=True)
utils.make_dirs(file_path)
shutil.copyfile(default_file, file_path)
logger.debug(f"Created custom measurement {document_type!r} template: {name!r}")
[docs] def remove_template(self, document_type: str, name: str) -> None:
"""Delete a custom template file."""
self._validate_type(document_type)
template = self.get_template(document_type, name)
os.remove(template)
logger.info(f"Removed template: {name!r} ({document_type})")
[docs]def get_template_cli(
template_manager: TemplateManager, document_type: str, template_name: str = None
) -> tuple[str, str]:
"""CLI to get the (name of) and path to a template for the specified document types.
Args:
template_manager (TemplateManager): TemplateManager instance initiated with the
template directory of interest.
document_type (str): The document type of the template that shall be returned.
template_name (str, optional): Specify the name of the desired document template.
Returns:
tuple[str,str]: A tuple containing the (1.) template_name, and the corresponding (2.) file_path.
"""
template_file = None
if template_name is None:
options = template_manager.get_templates(document_type, names=True)
if len(options) == 1 and options[0] is None:
template_file = template_manager.default_templates[document_type]
template_name = "default"
elif len(options) == 1:
template_name = options[0]
else:
msg = f"Choose a measurement {document_type} template:"
template_name = utils.select_option(options, msg)
if template_file is None:
template_file = template_manager.get_template(document_type, template_name)
return template_name, template_file
# import jinja2
# from .config import ENCODING
# TEMPLATES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "templates"))
# TEMPLATES = {
# "measurement_protocol": "template_protocol.md",
# }
# @dataclass
# class TemplateProcessor:
# """
# Processing of jinja2 templates.
# :templates_dir: Path to the templates directory.
# :encoding: Encoding used to read and write files.
# """
# templates_dir: str = TEMPLATES_DIR
# encoding: str = ENCODING
# def _get_env_obj(self) -> jinja2.Environment:
# """Get Jinja2 Environment object.
# Returns:
# jinja2.Environment: jinja2 Environment object
# """
# loader = jinja2.FileSystemLoader(TEMPLATES_DIR)
# return jinja2.Environment(loader=loader)
# def list_templates(self) -> list[str]:
# """List available Jinja2 templates.
# Returns:
# list[str]: list of templates
# """
# return self._get_env_obj().list_templates()
# def get_template(self, template_name) -> jinja2.Template:
# """Load jinja2 Template object from template file.
# Returns:
# jinja2.Template: jinja2 Template object
# """
# env = self._get_env_obj()
# return env.get_template(template_name)
# def create_file(
# self, values: dict[str, any], template_name: str, file_path: str
# ) -> None:
# """Create a file from the template and values.
# Args:
# values: dict[str, any]
# dictionary with the values to be used in the template
# file_path: str
# path to the file to be created
# """
# # Render the template using the values
# output = self.get_template(template_name).render(**values)
# # Save the rendered template to a file
# with open(file_path, "w", encoding=self.encoding) as fobj:
# fobj.write(output)