"""
Built-in template asset management for :mod:`pyfcstm`.
This module provides the runtime-facing API for packaged built-in templates.
The repository keeps editable template sources under the top-level
``templates/`` directory, while packaged distributions ship zipped template
assets under :mod:`pyfcstm.template` together with an ``index.json`` metadata
file.
The functions in this module intentionally do only three things:
* list the built-in template names available in the installed package
* return metadata for one packaged template
* extract one packaged template into a normal directory so the existing
:class:`pyfcstm.render.StateMachineCodeRenderer` can consume it
This separation keeps built-in template distribution independent from the
renderer itself. The module does not parse DSL code, does not render output
files directly, and does not implement any template-specific business logic.
The module contains the following public components:
* :func:`list_templates` - Return the names of packaged built-in templates
* :func:`has_template` - Check whether one built-in template is available
* :func:`get_template_info` - Return metadata for one packaged template
* :func:`extract_template` - Extract one packaged template into a directory
Example::
>>> from pyfcstm.template import list_templates, extract_template
>>> isinstance(list_templates(), list)
True
.. note::
The packaged template assets are generated from repository-root template
sources during the template packaging step. This module only reads the
packaged results already present inside :mod:`pyfcstm`.
"""
from __future__ import annotations
import json
import os
import shutil
import zipfile
from typing import Dict, List
__all__ = [
'list_templates',
'has_template',
'get_template_info',
'extract_template',
]
def _module_dir() -> str:
"""
Return the absolute directory path of this module.
:return: Absolute directory path containing packaged template assets.
:rtype: str
"""
return os.path.dirname(os.path.abspath(__file__))
def _index_path() -> str:
"""
Return the absolute path to the packaged template index file.
:return: Absolute path of ``index.json`` inside this module directory.
:rtype: str
"""
return os.path.join(_module_dir(), 'index.json')
def _load_index() -> Dict[str, List[Dict[str, object]]]:
"""
Load the packaged template index metadata from disk.
:return: Decoded JSON object from ``index.json``.
:rtype: Dict[str, List[Dict[str, object]]]
:raises FileNotFoundError: If the packaged template index is missing.
:raises json.JSONDecodeError: If the packaged template index is invalid JSON.
"""
with open(_index_path(), 'r', encoding='utf-8') as f:
return json.load(f)
def _repo_template_source_dir(name: str) -> str:
"""
Return the editable repository template source directory for ``name``.
This helper is used as a development-checkout fallback when packaged zip
assets are not present next to :mod:`pyfcstm.template`.
:param name: Built-in template name.
:type name: str
:return: Absolute path to the repository template source directory.
:rtype: str
"""
repo_root = os.path.abspath(os.path.join(_module_dir(), '..', '..'))
return os.path.join(repo_root, 'templates', name)
[docs]
def list_templates() -> List[str]:
"""
Return the names of packaged built-in templates.
The names are read from the packaged ``index.json`` file and returned in
the stored order. The result is suitable for CLI validation, documentation
display, or built-in template discovery.
:return: Built-in template names available in the installed package.
:rtype: List[str]
:raises FileNotFoundError: If the packaged template index is missing.
:raises json.JSONDecodeError: If the packaged template index is invalid JSON.
Example::
>>> from pyfcstm.template import list_templates
>>> templates = list_templates()
>>> isinstance(templates, list)
True
"""
return [item['name'] for item in _load_index().get('templates', [])]
[docs]
def has_template(name: str) -> bool:
"""
Check whether a packaged built-in template exists.
:param name: Built-in template name to check.
:type name: str
:return: ``True`` if the template exists, ``False`` otherwise.
:rtype: bool
:raises FileNotFoundError: If the packaged template index is missing.
:raises json.JSONDecodeError: If the packaged template index is invalid JSON.
Example::
>>> from pyfcstm.template import has_template
>>> has_template('python') in (True, False)
True
"""
return any(item == name for item in list_templates())
[docs]
def get_template_info(name: str) -> Dict[str, object]:
"""
Return metadata for one packaged built-in template.
The returned dictionary is a shallow copy of the metadata entry stored in
``index.json``. Callers may modify the returned mapping without affecting
the packaged metadata loaded by subsequent calls.
:param name: Built-in template name.
:type name: str
:return: Metadata dictionary for the requested built-in template.
:rtype: Dict[str, object]
:raises LookupError: If the named template does not exist.
:raises FileNotFoundError: If the packaged template index is missing.
:raises json.JSONDecodeError: If the packaged template index is invalid JSON.
Example::
>>> from pyfcstm.template import get_template_info
>>> info = get_template_info('python') # doctest: +SKIP
>>> info['name'] # doctest: +SKIP
'python'
"""
for item in _load_index().get('templates', []):
if item['name'] == name:
return dict(item)
raise LookupError('Built-in template {name!r} not found.'.format(name=name))