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 Proto, 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
35import bibble._interface as API
36from bibble.util.middlecore import IdenBlockMiddleware
37
38# ##-- types
39# isort: off
40import abc
41import collections.abc
42from typing import TYPE_CHECKING, cast, assert_type, assert_never
43from typing import Generic, NewType
44# Protocols:
45from typing import Protocol, runtime_checkable
46# Typing Decorators:
47from typing import no_type_check, final, override, overload
48
49if TYPE_CHECKING:
50 from jgdv import Maybe
51 from typing import Final
52 from typing import ClassVar, Any, LiteralString
53 from typing import Never, Self, Literal
54 from typing import TypeGuard
55 from collections.abc import Iterable, Iterator, Callable, Generator
56 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
57
58 type Entry = model.Entry
59 type Field = model.Field
60 from bibtexparser.library import Library
61##--|
62
63# isort: on
64# ##-- end types
65
66##-- logging
67logging = logmod.getLogger(__name__)
68##-- end logging
69
[docs]
70@Proto(API.WriteTime_p)
71class FieldSorter(IdenBlockMiddleware):
72 """ Sort the entries of a field
73 firsts are exact matches that go at the front.
74 lasts are a list of patterns to match on
75 """
76
77 _first_defaults : ClassVar[list[str]] = []
78 _last_defaults : ClassVar[list[str]] = []
79
80 def __init__(self, *, first:Maybe[list[str]]=None, last:Maybe[list[str]]=None, **kwargs):
81 super().__init__(**kwargs)
82 self._firsts = first or self._first_defaults
83 self._lasts = last or self._last_defaults
84 self._stem_re = re.compile("^[a-zA-Z_]+")
85
[docs]
86 def on_write(self):
87 Never()
88
[docs]
89 def field_sort_key(self, field:Field) -> str|tuple:
90 match self._stem_re.match(field.key):
91 case None:
92 key = field.key
93 case x:
94 key = x[0]
95 try:
96 return (self._lasts.index(key), field.key)
97 except ValueError:
98 return key
99
[docs]
100 def transform_Entry(self, entry:Entry, library:Library) -> list[Entry]:
101 # Get the firsts in order if they exist
102 firsts = [y for x in self._firsts if (y:=entry.get(x,None)) is not None]
103 rest, lasts = [], []
104 for field in entry.fields:
105 match self._stem_re.match(field.key):
106 case None:
107 key = field.key
108 case x:
109 key = x[0]
110 if key in self._firsts:
111 continue
112 if key not in self._lasts:
113 rest.append(field)
114 else:
115 lasts.append(field)
116
117 # Sort the lasts
118 rest = sorted(rest, key=self.field_sort_key)
119 lasts = sorted(lasts, key=self.field_sort_key)
120 entry.fields = firsts + rest + lasts
121 return [entry]