1#!/usr/bin/env python3
2"""
3
4"""
5
6# Imports:
7from __future__ import annotations
8
9# ##-- stdlib imports
10import datetime
11import enum
12import functools as ftz
13import itertools as itz
14import logging as logmod
15import pathlib as pl
16import re
17import time
18import types
19import weakref
20from uuid import UUID, uuid1
21
22# ##-- end stdlib imports
23
24# ##-- 3rd party imports
25from jgdv import Mixin
26import bibtexparser
27import bibtexparser.model as model
28from bibtexparser import middlewares as ms
29from bibtexparser.middlewares.middleware import (BlockMiddleware,
30 LibraryMiddleware)
31from jgdv.files.tags import SubstitutionFile
32
33# ##-- end 3rd party imports
34
35# ##-- 1st party imports
36from bibble.util.mixins import ErrorRaiser_m, FieldMatcher_m
37from bibble.util.middlecore import IdenBlockMiddleware
38
39# ##-- end 1st party imports
40
41# ##-- types
42# isort: off
43import abc
44import collections.abc
45from typing import TYPE_CHECKING, cast, assert_type, assert_never
46from typing import Generic, NewType
47# Protocols:
48from typing import Protocol, runtime_checkable
49# Typing Decorators:
50from typing import no_type_check, final, override, overload
51
52if TYPE_CHECKING:
53 from jgdv import Maybe
54 from typing import Final
55 from typing import ClassVar, Any, LiteralString
56 from typing import Never, Self, Literal
57 from typing import TypeGuard
58 from collections.abc import Iterable, Iterator, Callable, Generator
59 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
60
61##--|
62
63# isort: on
64# ##-- end types
65
66##-- logging
67logging = logmod.getLogger(__name__)
68##-- end logging
69
[docs]
70@Mixin(ErrorRaiser_m, FieldMatcher_m)
71class FieldSubstitutor(IdenBlockMiddleware):
72 """
73 For a given field(s), and a given jgdv.SubstitutionFile,
74 replace the field value as necessary in each entry.
75
76 If force_single_value is True, only the first replacement will be used,
77 others will be discarded
78
79 eg: for target=['tags'],
80 and subs({'AI': ['artificial_intelligence', 'agents', 'machine_learning'])
81 and entry(fields={'tags': 'ai'})
82 will give: entry(fields={''tags': ['artificial_intelligence', 'agents', 'machine_learning']})
83
84 """
85
86 def __init__(self, *, fields:list[str], subs:SubstitutionFile, force_single_value:bool=False, **kwargs):
87 super().__init__(**kwargs)
88 match fields:
89 case list():
90 self._target_fields = fields
91 case x:
92 raise TypeError(type(x))
93
94 self._subs = subs
95 self._force_single_value = force_single_value
96 self.set_field_matchers(white=self._target_fields, black=[])
97
[docs]
98 def transform_Entry(self, entry, library):
99 if self._subs is None or not bool(self._subs):
100 return [entry]
101
102 match self.match_on_fields(entry, library):
103 case model.Entry() as x:
104 return [x]
105 case Exception() as err:
106 return [self.make_error_block(entry, err)]
107 case x:
108 raise TypeError(type(x))
109
[docs]
110 def field_h(self, field, entry):
111 """ """
112 match field.value:
113 case str() as value if self._force_single_value:
114 head, *_ = list(self._subs.sub(value))
115 return [model.Field(field.key, head)]
116 case str() as value:
117 subs = list(self._subs.sub(value))
118 return [model.Field(field.key, subs)]
119 case list() | set() as value:
120 result = self._subs.sub_many(*value)
121 return [model.Field(field.key, result)]
122 case value:
123 return ValueError("Unsupported replacement field value type", entry.key, type(value))
124
125 return []