1#!/usr/bin/env python3
2"""
3Refactored from bibtexparser.middlewares.names
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 collections
20import contextlib
21import hashlib
22from copy import deepcopy
23from uuid import UUID, uuid1
24from weakref import ref
25import atexit # for @atexit.register
26import faulthandler
27# ##-- end stdlib imports
28
29# ##-- types
30# isort: off
31import abc
32import collections.abc
33from typing import TYPE_CHECKING, cast, assert_type, assert_never
34from typing import Generic, NewType
35# Protocols:
36from typing import Protocol, runtime_checkable
37# Typing Decorators:
38from typing import no_type_check, final, override, overload
39# from dataclasses import InitVar, dataclass, field
40# from pydantic import BaseModel, Field, model_validator, field_validator, ValidationError
41
42if TYPE_CHECKING:
43 from jgdv import Maybe, M_
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 type MbList = Maybe[list[str]]
52##--|
53
54# isort: on
55# ##-- end types
56
57##-- logging
58logging = logmod.getLogger(__name__)
59##-- end logging
60
61# Vars:
62JOIN_STR : Final[str] = ", "
63# Body:
64
[docs]
65def escape_last_slash(string: str) -> str:
66 """Escape the last slash in a string if it is not escaped."""
67 # Find the number of trailing slashes
68 stripped = string.rstrip("\\")
69 num_slashes = len(string) - len(stripped)
70 if num_slashes % 2 == 0:
71 # Even number: everything is escaped
72 return string
73 else:
74 # Odd number: need to escape one.
75 return string + "\\"
76
77##--|
78
[docs]
79class NameParts_d:
80 """A dataclass representing the parts of a person name.
81
82 The different parts are defined according to BibTex's implementation
83 of name parts (first, von, last, jr).
84 """
85 __slots__ = ("first", "von", "last", "jr")
86
87 first : list[str]
88 von : list[str]
89 last : list[str]
90 jr : list[str]
91
92 def __init__(self, *, first:MbList=None, von:MbList=None, last:MbList=None, jr:MbList=None):
93 self.first = first or []
94 self.von = von or []
95 self.last = last or []
96 self.jr = jr or []
97
[docs]
98 def merge(self, *, format:Maybe[list[str]]=None) -> str:
99 match format:
100 case None:
101 format = ["von_last", "jr", "first"]
102 case str():
103 pass
104 case x:
105 raise TypeError(type(x))
106
107 parts = {
108 "first" : " ".join(self.first) if self.first else None,
109 "von" : " ".join(self.von) if self.von else None,
110 "last" : " ".join(self.last) if self.last else None,
111 "jr" : " ".join(self.jr) if self.jr else None,
112 }
113 parts['von_last'] = " ".join(name for name in [parts['von'], parts['last']] if name)
114 ordered = [parts[x] for x in format]
115 return JOIN_STR.join(escape_last_slash(name) for name in ordered if name)
116
117 def __repr__(self) -> str:
118 return f"<{self.__class__.__name__} : {self.merge()}>"