Source code for bibble._interface

  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
[docs] 126 def comments(self) -> list[CommentBlock]: ...
127
[docs] 128@runtime_checkable 129class Middleware_p(Protocol): 130 """ A Middleware is something with a 'transform' method """ 131
[docs] 132 def transform(self, library:Library_p) -> Library_p: ...
133
[docs] 134@runtime_checkable 135class BlockMiddleware_p(Protocol): 136 """ A (non-adaptive) block middleware has 'transform_x' methods """ 137
[docs] 138 def transform(self, library:Library_p) -> Library_p: ...
139
[docs] 140 def transform_block(self, block:Block, library:Library_p) -> list[Block]: ...
141
[docs] 142 def transform_entry(self, entry:Entry, library:Library_p) -> list[Block]: ...
143
[docs] 144 def transform_string(self, string:model.StringBlock, library:Library_p) -> list[Block]: ...
145
[docs] 146 def transform_preamble(self, preamble:model.Preamble, library:Library_p) -> list[Block]: ...
147
[docs] 148 def transform_explicit_comment(self, comment:model.ExplicitComment, library:Library_p) -> list[Block]: ...
149
[docs] 150 def transform_implicit_comment(self, comment:model.ImplicitComment, library:Library_p) -> list[Block]: ...
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
[docs] 162 def read_transform(self, library:Library_p) -> Library_p: ...
163
[docs] 164 def write_transform(self, library:Library_p) -> Library_p: ...
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
[docs] 177 def get_transforms_for(self, block:Block, *, direction:Maybe[str]=None) -> list[Callable[[Block, Library_p], list[Block]]]: ...
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
[docs] 266@runtime_checkable 267class StrTransformer_p(Protocol): 268 """ Describes the StringTransform_m """ 269
[docs] 270 def transform_strlike(self, slike:StrLike) -> Result[StrLike, Exception]: ...
271
[docs] 272 def _transform_raw_str(self, python_string:str) -> Result[str, Exception]: ...
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 """