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 str()|pl.Path() as x:
102 loaders = [jinja2.FileSystemLoader(pl.Path(x)), DEFAULT_LOADER]
103 case [*xs]:
104 loaders = [jinja2.FileSystemLoader(pl.Path(x)) for x in xs]
105 loaders.append(DEFAULT_LOADER)
106 case None:
107 loaders = [DEFAULT_LOADER]
108 self._env = jinja2.Environment(
109 loader=jinja2.ChoiceLoader(loaders),
110 autoescape=jinja2.select_autoescape(),
111 trim_blocks=True,
112 lstrip_blocks=True,
113 )
114 self._env.filters['wrap'] = self._wrap_braces
115 self._templates = {}
116 self.update_templates(DEFAULT_TEMPLATES)
117
[docs]
118 def update_templates(self, templates:dict[str, Maybe[str]]) -> None:
119 for key, name in templates.items():
120 match name:
121 case None:
122 self._templates[key] = None
123 case str() as x:
124 self._templates[key] = self._env.get_template(name)
125
130
135
[docs]
136 def make_lib(self, *, header:list[str], body:list[str], footer:list[str]) -> str:
137 if self._templates['lib'] is None:
138 return self._join_char.join([*header, *body, *footer]).strip()
139
140 return self._templates['lib'].render(
141 header=self._join_char.join(header),
142 body=self._join_char.join(body),
143 footer=self._join_char.join(footer),
144 ).strip()
145
[docs]
146 def visit_entry(self, block:Block) -> list[str]:
147 return [self._templates['entry'].render(entry_type=block.entry_type,
148 key=block.key,
149 available_keys=block.fields_dict.keys(),
150 entry=block.fields_dict,
151 )]
152
153
[docs]
154 def write(self, library, *, templates:Maybe[dict]=None, **kwargs) -> str:
155 x : Any
156 match templates:
157 case None:
158 pass
159 case dict() as x:
160 self.update_templates(templates)
161 return super().write(library, **kwargs)
162
163
[docs]
164 def _wrap_braces(self, val:str) -> str:
165 return "".join(["{", str(val), "}"])