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 copy import deepcopy
21from uuid import UUID, uuid1
22
23# ##-- end stdlib imports
24
25# ##-- 3rd party imports
26import bibtexparser
27import bibtexparser.model as model
28from bibtexparser import middlewares as ms
29from bibtexparser.middlewares.middleware import (BlockMiddleware,
30 LibraryMiddleware)
31from jgdv import Mixin, Proto
32
33# ##-- end 3rd party imports
34
35# ##-- 1st party imports
36import bibble._interface as API
37from . import _interface as MAPI
38from bibble.util.middlecore import IdenBlockMiddleware
39from bibble.util.mixins import ErrorRaiser_m
40# ##-- end 1st party imports
41
42# ##-- types
43# isort: off
44import abc
45import collections.abc
46from typing import TYPE_CHECKING, cast, assert_type, assert_never
47from typing import Generic, NewType
48# Protocols:
49from typing import Protocol, runtime_checkable
50# Typing Decorators:
51from typing import no_type_check, final, override, overload
52
53if TYPE_CHECKING:
54 from jgdv import Maybe, Rx, RxStr
55 from typing import Final
56 from typing import ClassVar, Any, LiteralString
57 from typing import Never, Self, Literal
58 from typing import TypeGuard
59 from collections.abc import Iterable, Iterator, Callable, Generator
60 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
61
62##--|
63
64# isort: on
65# ##-- end types
66
67##-- logging
68logging = logmod.getLogger(__name__)
69##-- end logging
70##--|
71
[docs]
72@Proto(API.ReadTime_p)
73@Mixin(ErrorRaiser_m)
74class KeyLocker(IdenBlockMiddleware):
75 """ Ensure key/crossref consistency by:
76 removing unwanted chars in the key,
77 'locking' the key with a specific suffix (by default a '_').
78
79 Also formats crossref values so they match.
80 Already locked keys are ignored.
81
82 __init__ takes:
83 - regex = the regex of chars to remove.
84 - sub = the substitute for removed chars
85 """
86
87 def __init__(self, *, regex:Maybe[RxStr|Rx]=None, sub:Maybe[str]=None, lock_suffix:Maybe[str]=None, key_suffix:Maybe[RxStr|Rx]=None, **kwargs):
88 super().__init__(**kwargs)
89 self._remove_re : Rx = re.compile(regex or MAPI.KEY_CLEAN_RE)
90 self._key_suffix_re : Rx = re.compile(key_suffix or MAPI.KEY_SUFFIX_RE)
91 self._sub : str = sub or MAPI.KEY_SUB_CHAR
92 self._lock_suffix : str = MAPI.LOCK_SUFFIX
93 self._bad_lock : str = f"{self._lock_suffix}{self._lock_suffix}"
94
[docs]
95 def on_read(self):
96 Never()
97
[docs]
98 def transform_Entry(self, entry, library) -> list:
99 entry = deepcopy(entry)
100 entry.key = self.clean_key(entry.key)
101 match entry.get(MAPI.CROSSREF_K):
102 case None:
103 pass
104 case model.Field(value=value):
105 entry.set_field(model.Field(MAPI.CROSSREF_K, self.clean_key(value)))
106
107 return [entry]
108
[docs]
109 def clean_key(self, key:str) -> str:
110 """ Convert the entry key to a canonical form """
111 if key.endswith(self._lock_suffix) and not key.endswith(self._bad_lock):
112 return key
113
114 # Remove bad chars
115 clean_key = self._remove_re.sub(self._sub, key)
116 # Enforce the correct suffix
117 clean_key = self._key_suffix_re.sub(self._lock_suffix, clean_key)
118 return clean_key