1#!/usr/bin/env python3
2"""
3
4"""
5# Imports:
6from __future__ import annotations
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 types
18import collections
19import contextlib
20import hashlib
21from copy import deepcopy
22from uuid import UUID, uuid1
23from weakref import ref
24import atexit # for @atexit.register
25import faulthandler
26# ##-- end stdlib imports
27
28from bibtexparser import model
29from bibtexparser.library import Library
30
31# ##-- types
32# isort: off
33import abc
34import collections.abc
35from typing import TYPE_CHECKING, cast, assert_type, assert_never
36from typing import Generic, NewType
37# Protocols:
38from typing import Protocol, runtime_checkable
39# Typing Decorators:
40from typing import no_type_check, final, override, overload
41
42if TYPE_CHECKING:
43 from jgdv import Maybe, Either, Result
44 from typing import Final
45 from typing import ClassVar, Any, LiteralString
46 from typing import Never, Self, Literal
47 from typing import TypeGuard
48 from collections.abc import Iterable, Iterator, Callable, Generator
49 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
50
51 from bibble.util import NameParts_d
52 from bibtexparser.middlewares import NameParts
53 type StringBlock = model.String
54 type Preamble = model.Preamble
55 type Field = model.Field
56 type Block = model.Block
57 type Entry = model.Entry
58 type String = model.String
59 type FailedBlock = model.ParsingFailedBlock
60 type ErrorBlock = model.MiddlewareErrorBlock
61 type CommentBlock = model.ExplicitComment | model.ImplicitComment
62 type UniMiddleware = Middleware_p | AdaptiveMiddleware_p
63 type BidiMiddleware = BidirectionalMiddleware_p
64 type Middleware = UniMiddleware | BidiMiddleware
65
66 type StrLike = list|set|String|NameParts_d|NameParts
67##--|
68
69# isort: on
70# ##-- end types
71
72##-- logging
73logging = logmod.getLogger(__name__)
74##-- end logging
75
76# Vars:
77ALLOW_INPLACE_MOD_K : Final[str] = "allow_inplace_modification"
78ALLOW_PARALLEL_K : Final[str] = "allow_parallel_execution"
79LOGGER_K : Final[str] = "logger"
80KEEP_MATH_K : Final[str] = "keep_math"
81ENCLOSE_URLS_K : Final[str] = "enclose_urls"
82
83TQDM_WIDTH : Final[int] = 150
84##--|
85## Enums / Flags
86
[docs]
87class Capability_f(enum.Flag):
88 """ A Flag for where middlewares can be in the read/write stack """
89
90 insist_front = enum.auto()
91 insist_end = enum.auto()
92 read_time = enum.auto()
93 write_time = enum.auto()
94 validate = enum.auto()
95 transform = enum.auto()
96 report = enum.auto()
97 dependent = enum.auto()
98
99##--|
100## Bibtexparser protcols
101
[docs]
102@runtime_checkable
103class Library_p(Protocol):
104 """ The core methods of a library """
105
[docs]
106 def add(self, blocks:list[Block], fail_on_duplicate_key:bool = False) -> None: ...
107
[docs]
108 def remove(self, blocks:list[Block]) -> None: ...
109
[docs]
110 def replace(self, old_block:Block, new_block:Block, fail_on_duplicate_key:bool=True) -> None: ...
111
[docs]
112 def blocks(self) -> list[Block]: ...
113
[docs]
114 def failed_blocks(self) -> list[FailedBlock]: ...
115
[docs]
116 def strings(self) -> list[StringBlock]: ...
117
[docs]
118 def strings_dict(self) -> dict[str, StringBlock]: ...
119
[docs]
120 def entries(self) -> list[Entry]: ...
121
[docs]
122 def entries_dict(self) -> dict[str, Entry]: ...
123
[docs]
124 def preambles(self) -> list[Preamble]: ...
125
127
[docs]
128@runtime_checkable
129class Middleware_p(Protocol):
130 """ A Middleware is something with a 'transform' method """
131
133
[docs]
134@runtime_checkable
135class BlockMiddleware_p(Protocol):
136 """ A (non-adaptive) block middleware has 'transform_x' methods """
137
139
141
[docs]
142 def transform_entry(self, entry:Entry, library:Library_p) -> list[Block]: ...
143
145
147
149
151
152##--| New Middleware protocols:
153
[docs]
154@runtime_checkable
155class BidirectionalMiddleware_p(Protocol):
156 """ A Single middleware that holds the logic for reading and writing,
157 Intended for undoing what is done on read, prior to writing.
158
159 eg: Latex Decoding, then Encoding
160 """
161
163
165
[docs]
166@runtime_checkable
167class AdaptiveMiddleware_p(Protocol):
168 """ Middleware that looks up defined transforms using the type name, by mro.
169 The form for method lookup is either:
170 - transform_{type(block).__class__}
171 - {direction}_transform_{type(block).__class__}
172
173 An adaptive middleware doesn't need all the 'transform_X' methods a BlockMiddleware_p does.
174
175 """
176
178
179##--| IO Protocols
180
[docs]
181@runtime_checkable
182class PairStack_p(Protocol):
183 """ Protocol for both storing both read and write middlewares
184
185 """
186
[docs]
187 def add(self, *args:BidiMiddleware, read:Maybe[list|Middleware]=None, write:Maybe[list|Middleware]=None) -> Self: ...
188
[docs]
189 def read_stack(self) -> list[Middleware]: ...
190
[docs]
191 def write_stack(self) -> list[Middleware]: ...
192
[docs]
193@runtime_checkable
194class Reader_p(Protocol):
195 """ Readers take source text, or a file, or a directory,
196 parses the read bibtex, running middlewares on the parsed bibtex
197 """
198
[docs]
199 def read(self, source:str|pl.Path, *, into:Maybe[Library]=None, append:Maybe[list[Middleware]]=None) -> Maybe[Library]: ...
200
[docs]
201 def read_dir(self, source:pl.Path, *, ext:str, into:Maybe[Library]=None, append:Maybe[list[Middleware]]=None) -> Maybe[Library]: ...
202
[docs]
203@runtime_checkable
204class Writer_p(Protocol):
205 """ Writers take a library, format it, and write it to a file.
206 *typically* it formats as bibtex, but doesn't *have* to.
207 (eg: RstWriter)
208 """
209
[docs]
210 def write(self, library:Library, *, file:Maybe[pl.Path]=None, append:Maybe[list[Middleware]]=None) -> str: ...
211
212##--| Middleware protocols
213
[docs]
214@runtime_checkable
215class CustomWriteBlock_p(Protocol):
216 """ Writers can be Visitors, in which case they call ths visit method on
217 blocks
218
219 """
220
[docs]
221 def visit(self, writer:Writer_p) -> list[str]: ...
222
[docs]
223@runtime_checkable
224class ReadTime_p(Protocol):
225 """ Protocol for signifying a middleware is for use on parsing bibtex to
226 data
227 """
228
[docs]
229 def on_read(self) -> Never: ...
230
[docs]
231@runtime_checkable
232class WriteTime_p(Protocol):
233 """ Protocol for signifying middleware is for use on writing data to bibtex
234
235 """
236
[docs]
237 def on_write(self) -> Never: ...
238
[docs]
239@runtime_checkable
240class EntrySkipper_p(Protocol):
241 """ A whitelist based test for middlewares.
242 Middleware's set their skiplist on init,
243 and can call 'should_skip_entry' when transforming blocks
244
245 eg: for only processing type='article' entries, not books
246 """
247
[docs]
248 def set_entry_skiplist(self, whitelist:list[str]) -> None: ...
249
[docs]
250 def should_skip_entry(self, entry:Entry, library:Library) -> bool: ...
251
[docs]
252@runtime_checkable
253class FieldMatcher_p(Protocol):
254 """ The protocol util.FieldMatcher_m relies on
255
256 A Middleware with the FieldMatcher_m mixin will call the implemented field_h
257 on each field that matches in an entry.
258 """
259
[docs]
260 def set_field_matchers(self, *, white:list[str], black:list[str]) -> Self: ...
261
[docs]
262 def match_on_fields(self, entry: Entry, library: Library) -> Result[Entry, Exception]: ...
263
[docs]
264 def field_h(self, field:Field, entry:Entry) -> Result[list[Field], Exception]: ...
265
273
[docs]
274@runtime_checkable
275class DependentMiddleware_p(Protocol):
276 """
277 For middlewares that depend on a middleware to be able to work themselves.
278
279 eg: metadata applicator requires path reader
280 """
281
[docs]
282 def requires_in_same_stack(self) -> list[type]: ...
283 """ The given types need to be in the same stack """
284
[docs]
285 def requires_in_parse_stack(self) -> list[type]: ...
286 """ The given types need to be in the parse stack """