1#!/usr/bin/env python3
2"""
3
4"""
5from __future__ import annotations
6# Imports:
7
8# ##-- stdlib imports
9import datetime
10import enum
11import functools as ftz
12import itertools as itz
13import logging as logmod
14import pathlib as pl
15import re
16import time
17import collections
18import contextlib
19import hashlib
20from copy import deepcopy
21from uuid import UUID, uuid1
22from weakref import ref
23import atexit # for @atexit.register
24import faulthandler
25# ##-- end stdlib imports
26
27import jinja2
28from .writer import BibbleWriter
29from bibble import _interface as API
30from . import _interface as API_W
31from bibble.util.mixins import MiddlewareValidator_m
32from bibble.model import MetaBlock
33from bibble.util import PairStack
34from bibtexparser.writer import BibtexFormat
35
36from ._util import Runner_m
37
38# ##-- types
39# isort: off
40# General
41import abc
42import collections.abc
43import typing
44import types
45from typing import cast, assert_type, assert_never
46from typing import Generic, NewType, Never
47from typing import no_type_check, final, override, overload
48# Protocols and Interfaces:
49from typing import Protocol, runtime_checkable
50# isort: on
51# ##-- end types
52
53# ##-- type checking
54# isort: off
55if typing.TYPE_CHECKING:
56 from typing import Final, ClassVar, Any, Self
57 from typing import Literal, LiteralString
58 from typing import TypeGuard
59 from collections.abc import Iterable, Iterator, Callable, Generator
60 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
61 from logmod import Logger
62 from bibtexparser.model import Block
63
64 from jgdv import Maybe
65
66 from bibtexparser.library import Library
67 from bibtexparser.writer import BibtexFormat
68
69 type Middleware = API.Middleware_p | API.BidirectionalMiddleware_p
70## isort: on
71# ##-- end type checking
72
73##-- logging
74logging = logmod.getLogger(__name__)
75##-- end logging
76
77# Vars:
78NEWLINE : Final[str] = "\n"
79DEFAULT_TEMPLATES : Final[dict] = {
80 "lib" : "lib.bib.jinja",
81 "header" : "header.bib.jinja",
82 "entry" : "entry.bib.jinja",
83 "footer" : "footer.bib.jinja",
84}
85DEFAULT_LOADER : Final[jinja2.BaseLoader] = jinja2.PackageLoader("bibble", "_templates")
86
87# Body:
88
[docs]
89class JinjaWriter(BibbleWriter):
90 """
91 Use jinja templates to write out bibtex
92 """
93 _env : jinja2.Environment
94 _templates : dict[str, Maybe[jinja2.Template]]
95
96 def __init__(self, stack:PairStack, *, format:Maybe[BibtexFormat]=None, logger:Maybe[Logger]=None, templates:Maybe[pl.Path]=None) -> None:
97 x : Any
98 super().__init__(stack, format=format, logger=logger)
99 self._join_char = NEWLINE
100 match templates:
101 case None:
102 loaders = [DEFAULT_LOADER]
103 case pl.Path() as x:
104 loaders = [jinja2.FileSystemLoader(x), DEFAULT_LOADER]
105 self._env = jinja2.Environment(
106 loader=jinja2.ChoiceLoader(loaders),
107 autoescape=jinja2.select_autoescape(),
108 trim_blocks=True,
109 lstrip_blocks=True,
110 )
111 self._env.filters['wrap'] = self._wrap_braces
112 self._templates = {}
113 self.update_templates(DEFAULT_TEMPLATES)
114
[docs]
115 def update_templates(self, templates:dict[str, Maybe[str]]) -> None:
116 for key, name in templates.items():
117 match name:
118 case None:
119 self._templates[key] = None
120 case str() as x:
121 self._templates[key] = self._env.get_template(name)
122
127
132
[docs]
133 def make_lib(self, *, header:list[str], body:list[str], footer:list[str]) -> str:
134 if self._templates['lib'] is None:
135 return self._join_char.join([*header, *body, *footer]).strip()
136
137 return self._templates['lib'].render(
138 header=self._join_char.join(header),
139 body=self._join_char.join(body),
140 footer=self._join_char.join(footer),
141 ).strip()
142
[docs]
143 def visit_entry(self, block:Block) -> list[str]:
144 return [self._templates['entry'].render(entry_type=block.entry_type,
145 key=block.key,
146 available_keys=block.fields_dict.keys(),
147 entry=block.fields_dict,
148 )]
149
150
[docs]
151 def write(self, library, *, templates:Maybe[dict]=None, **kwargs) -> str:
152 x : Any
153 match templates:
154 case None:
155 pass
156 case dict() as x:
157 self.update_templates(templates)
158 return super().write(library, **kwargs)
159
160
[docs]
161 def _wrap_braces(self, val:str) -> str:
162 return "".join(["{", str(val), "}"])