bibtexparser 2.0.0b5__tar.gz → 2.0.0b7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/PKG-INFO +2 -2
  2. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/README.md +1 -1
  3. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/__init__.py +1 -1
  4. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/entrypoint.py +23 -15
  5. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/exceptions.py +2 -2
  6. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/__init__.py +1 -0
  7. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/enclosing.py +4 -4
  8. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/interpolate.py +2 -2
  9. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/middleware.py +6 -6
  10. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/month.py +7 -7
  11. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/names.py +98 -37
  12. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/sorting_entry_fields.py +4 -4
  13. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/model.py +62 -21
  14. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/PKG-INFO +2 -2
  15. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_model.py +57 -0
  16. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/LICENSE +0 -0
  17. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/library.py +0 -0
  18. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/latex_encoding.py +0 -0
  19. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/parsestack.py +0 -0
  20. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/sorting_blocks.py +0 -0
  21. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/splitter.py +0 -0
  22. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/writer.py +0 -0
  23. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/SOURCES.txt +0 -0
  24. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/dependency_links.txt +0 -0
  25. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/requires.txt +0 -0
  26. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/top_level.txt +0 -0
  27. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/setup.cfg +0 -0
  28. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/setup.py +0 -0
  29. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_entrypoint.py +0 -0
  30. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_library.py +0 -0
  31. {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_writer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bibtexparser
3
- Version: 2.0.0b5
3
+ Version: 2.0.0b7
4
4
  Summary: Bibtex parser for python 3
5
5
  Home-page: https://github.com/sciunto-org/python-bibtexparser
6
6
  Author: Michael Weiss and other contributors
@@ -120,7 +120,7 @@ Writes the content of a bibtex library to a `.bib` file. Optional formatting par
120
120
 
121
121
  ## About
122
122
 
123
- Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)).
123
+ Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)). In 2024, Tom de Geus ([@tdegeus](https://github.com/tdegeus)) joined as co-maintainer.
124
124
 
125
125
  Credits and thanks to the many contributors who helped creating this library, including
126
126
  François Boulogne ([@sciunto](https://github.com/sciunto/), creator of the first version) and Olivier Mangin ([@omangin](https://github.com/omangin/), long-term contributor).
@@ -90,7 +90,7 @@ Writes the content of a bibtex library to a `.bib` file. Optional formatting par
90
90
 
91
91
  ## About
92
92
 
93
- Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)).
93
+ Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)). In 2024, Tom de Geus ([@tdegeus](https://github.com/tdegeus)) joined as co-maintainer.
94
94
 
95
95
  Credits and thanks to the many contributors who helped creating this library, including
96
96
  François Boulogne ([@sciunto](https://github.com/sciunto/), creator of the first version) and Olivier Mangin ([@omangin](https://github.com/omangin/), long-term contributor).
@@ -5,4 +5,4 @@ from bibtexparser.entrypoint import parse_file, parse_string, write_file, write_
5
5
  from bibtexparser.library import Library
6
6
  from bibtexparser.writer import BibtexFormat
7
7
 
8
- __version__ = "2.0.0b5"
8
+ __version__ = "2.0.0b7"
@@ -76,14 +76,19 @@ def parse_string(
76
76
  ):
77
77
  """Parse a BibTeX string.
78
78
 
79
- Args:
80
- bibtex_str (str): BibTeX string to parse
81
- parse_stack (Optional[Iterable[Middleware]], optional): List of middleware to apply to the database after splitting. If None (default), a default stack will be used providing simple standard functionality will be used.
82
- append_middleware (Optional[Iterable[Middleware]], optional): List of middleware to append to the default stack (ignored if a not-None parse_stack is passed).
83
- library (Optional[Library], optional): Library to add entries to. If None (default), a new library will be created.
84
-
85
- Returns:
86
- Library: Parsed BibTeX database
79
+ :param bibtex_str: BibTeX string to parse
80
+ :param parse_stack:
81
+ List of middleware to apply to the database after splitting.
82
+ If ``None`` (default), a default stack will be used providing simple standard functionality.
83
+
84
+ :param append_middleware:
85
+ List of middleware to append to the default stack
86
+ (ignored if a not-``None`` parse_stack is passed).
87
+
88
+ :param library:
89
+ Library to add entries to. If ``None`` (default), a new library will be created.
90
+
91
+ :return: Library: Parsed BibTeX database
87
92
  """
88
93
  splitter = Splitter(bibstr=bibtex_str)
89
94
  library = splitter.split(library=library)
@@ -103,14 +108,17 @@ def parse_file(
103
108
  ) -> Library:
104
109
  """Parse a BibTeX file
105
110
 
106
- Args:
107
- path (str): Path to BibTeX file
108
- parse_stack (Optional[Iterable[Middleware]], optional): List of middleware to apply to the database after splitting. If None (default), a default stack will be used providing simple standard functionality will be used.
109
- append_middleware (Optional[Iterable[Middleware]], optional): List of middleware to append to the default stack (ignored if a not-None parse_stack is passed).
110
- encoding: Encoding of the .bib file. Default encoding is "UTF-8".
111
+ :param path: Path to BibTeX file
112
+ :param parse_stack:
113
+ List of middleware to apply to the database after splitting.
114
+ If ``None`` (default), a default stack will be used providing simple standard functionality.
115
+
116
+ :param append_middleware:
117
+ List of middleware to append to the default stack
118
+ (ignored if a not-``None`` parse_stack is passed).
111
119
 
112
- Returns:
113
- Library: Parsed BibTeX library
120
+ :param encoding: Encoding of the .bib file. Default encoding is ``"UTF-8"``.
121
+ :return: Library: Parsed BibTeX library
114
122
  """
115
123
  with open(path, encoding=encoding) as f:
116
124
  bibtex_str = f.read()
@@ -40,8 +40,8 @@ class ParserStateException(ParsingException):
40
40
  class RegexMismatchException(ParserStateException):
41
41
  """Raised when regex matches are inconsistent, implying a bug in the parser.
42
42
 
43
- For example, raised when first match `@string{`
44
- is not followed by an overlapping match `}`.
43
+ For example, raised when first match ``@string{``
44
+ is not followed by an overlapping match ``}``.
45
45
  """
46
46
 
47
47
  def __init__(self, first_match, expected_match, second_match):
@@ -7,6 +7,7 @@ from bibtexparser.middlewares.latex_encoding import (
7
7
  LatexDecodingMiddleware,
8
8
  LatexEncodingMiddleware,
9
9
  )
10
+ from bibtexparser.middlewares.middleware import BlockMiddleware, LibraryMiddleware
10
11
  from bibtexparser.middlewares.month import (
11
12
  MonthAbbreviationMiddleware,
12
13
  MonthIntMiddleware,
@@ -37,8 +37,8 @@ class RemoveEnclosingMiddleware(BlockMiddleware):
37
37
  )
38
38
 
39
39
  # docstr-coverage: inherited
40
- @staticmethod
41
- def metadata_key() -> str:
40
+ @classmethod
41
+ def metadata_key(cls) -> str:
42
42
  return REMOVED_ENCLOSING_KEY
43
43
 
44
44
  @staticmethod
@@ -109,8 +109,8 @@ class AddEnclosingMiddleware(BlockMiddleware):
109
109
  self._enclose_integers = enclose_integers
110
110
 
111
111
  # docstr-coverage: inherited
112
- @staticmethod
113
- def metadata_key() -> str:
112
+ @classmethod
113
+ def metadata_key(cls) -> str:
114
114
  return "remove_enclosing"
115
115
 
116
116
  def _enclose(
@@ -28,8 +28,8 @@ class ResolveStringReferencesMiddleware(LibraryMiddleware):
28
28
  super().__init__(allow_inplace_modification)
29
29
 
30
30
  # docstr-coverage: inherited
31
- @staticmethod
32
- def metadata_key() -> str:
31
+ @classmethod
32
+ def metadata_key(cls) -> str:
33
33
  return "ResolveStringReferences"
34
34
 
35
35
  # docstr-coverage: inherited
@@ -21,7 +21,9 @@ class Middleware(abc.ABC):
21
21
  or LibraryMiddleware"""
22
22
 
23
23
  def __init__(
24
- self, allow_parallel_execution: bool, allow_inplace_modification: bool = True
24
+ self,
25
+ allow_parallel_execution: bool = True,
26
+ allow_inplace_modification: bool = True,
25
27
  ):
26
28
  """
27
29
 
@@ -62,14 +64,12 @@ class BlockMiddleware(Middleware, abc.ABC):
62
64
  except if `allow_inplace_modification` is true.
63
65
  """
64
66
 
65
- @staticmethod
66
- @abc.abstractmethod
67
- def metadata_key() -> str:
67
+ @classmethod
68
+ def metadata_key(cls) -> str:
68
69
  """Identifier of the middleware.
69
-
70
70
  This key is used to identify the middleware in a blocks metadata.
71
71
  """
72
- pass
72
+ return cls.__name__
73
73
 
74
74
  # docstr-coverage: inherited
75
75
  def transform(self, library: "Library") -> "Library":
@@ -12,7 +12,7 @@ class _MonthInterpolator(BlockMiddleware, abc.ABC):
12
12
  """Abstract class to handle month-conversions."""
13
13
 
14
14
  # docstr-coverage: inherited
15
- def __init__(self, allow_inplace_modification: bool):
15
+ def __init__(self, allow_inplace_modification: bool = True):
16
16
  super().__init__(
17
17
  allow_inplace_modification=allow_inplace_modification,
18
18
  allow_parallel_execution=True,
@@ -80,8 +80,8 @@ class MonthLongStringMiddleware(_MonthInterpolator):
80
80
  and unenclosed."""
81
81
 
82
82
  # docstr-coverage: inherited
83
- @staticmethod
84
- def metadata_key() -> str:
83
+ @classmethod
84
+ def metadata_key(cls) -> str:
85
85
  return "MonthLongStringMiddleware"
86
86
 
87
87
  # docstr-coverage: inherited
@@ -124,8 +124,8 @@ class MonthAbbreviationMiddleware(_MonthInterpolator):
124
124
  The created abbreviations are always lowercase and unenclosed."""
125
125
 
126
126
  # docstr-coverage: inherited
127
- @staticmethod
128
- def metadata_key() -> str:
127
+ @classmethod
128
+ def metadata_key(cls) -> str:
129
129
  return "MonthAbbreviationMiddleware"
130
130
 
131
131
  # docstr-coverage: inherited
@@ -159,8 +159,8 @@ class MonthIntMiddleware(_MonthInterpolator):
159
159
  The created int-months are always integers and unenclosed."""
160
160
 
161
161
  # docstr-coverage: inherited
162
- @staticmethod
163
- def metadata_key() -> str:
162
+ @classmethod
163
+ def metadata_key(cls) -> str:
164
164
  return "MonthIntMiddleware"
165
165
 
166
166
  # docstr-coverage: inherited
@@ -5,7 +5,7 @@ Much of the code is taken from Blair Bonnetts never merged v0 pull request
5
5
  """
6
6
  import abc
7
7
  import dataclasses
8
- from typing import List, Tuple
8
+ from typing import List, Literal, Tuple
9
9
 
10
10
  from bibtexparser.model import Block, Entry, Field, MiddlewareErrorBlock
11
11
 
@@ -62,8 +62,8 @@ class SeparateCoAuthors(_NameTransformerMiddleware):
62
62
  """Middleware to separate multi-person fields (e.g. co-authors, co-editors)."""
63
63
 
64
64
  # docstr-coverage: inherited
65
- @staticmethod
66
- def metadata_key() -> str:
65
+ @classmethod
66
+ def metadata_key(cls) -> str:
67
67
  return "separate_coauthors"
68
68
 
69
69
  # docstr-coverage: inherited
@@ -75,8 +75,8 @@ class MergeCoAuthors(_NameTransformerMiddleware):
75
75
  """Middleware to merge multi-person-list fields (e.g. co-authors, co-editors)."""
76
76
 
77
77
  # docstr-coverage: inherited
78
- @staticmethod
79
- def metadata_key() -> str:
78
+ @classmethod
79
+ def metadata_key(cls) -> str:
80
80
  return "merge_coauthors"
81
81
 
82
82
  # docstr-coverage: inherited
@@ -114,6 +114,35 @@ class NameParts:
114
114
  ]
115
115
  )
116
116
 
117
+ @property
118
+ def merge_last_name_first(self) -> str:
119
+ """Merging the name parts into a single string, last-name-first (with commas) format.
120
+
121
+ The structure of the output is: `von Last, Jr, First`
122
+ """
123
+
124
+ def escape_last_slash(string: str) -> str:
125
+ """Escape the last slash in a string if it is not escaped."""
126
+ # Find the number of trailing slashes
127
+ stripped = string.rstrip("\\")
128
+ num_slashes = len(string) - len(stripped)
129
+ if num_slashes % 2 == 0:
130
+ # Even number: everything is escaped
131
+ return string
132
+ else:
133
+ # Odd number: need to escape one.
134
+ return string + "\\"
135
+
136
+ first = " ".join(self.first) if self.first else None
137
+ von = " ".join(self.von) if self.von else None
138
+ last = " ".join(self.last) if self.last else None
139
+ jr = " ".join(self.jr) if self.jr else None
140
+
141
+ von_last = " ".join(name for name in [von, last] if name)
142
+ return ", ".join(
143
+ escape_last_slash(name) for name in [von_last, jr, first] if name
144
+ )
145
+
117
146
 
118
147
  class SplitNameParts(_NameTransformerMiddleware):
119
148
  """Middleware to split a persons name into its parts (first, von, last, jr).
@@ -123,8 +152,8 @@ class SplitNameParts(_NameTransformerMiddleware):
123
152
  """
124
153
 
125
154
  # docstr-coverage: inherited
126
- @staticmethod
127
- def metadata_key() -> str:
155
+ @classmethod
156
+ def metadata_key(cls) -> str:
128
157
  return "split_name_parts"
129
158
 
130
159
  def _transform_field_value(self, name) -> List[NameParts]:
@@ -142,18 +171,38 @@ class MergeNameParts(_NameTransformerMiddleware):
142
171
  """Middleware to merge a persons name parts (first, von, last, jr) into a single string.
143
172
 
144
173
  Name fields (e.g. author, editor, translator) are expected to be lists of NameParts.
174
+
175
+ The merging style indicates whether:
176
+ - the merging is done without commas in first-name-first order ("first"), or
177
+ - the merging is done with commas in last-name-first order ("last").
145
178
  """
146
179
 
180
+ def __init__(
181
+ self,
182
+ style: Literal["last", "first"] = "last",
183
+ allow_inplace_modification: bool = True,
184
+ name_fields: Tuple[str] = ("author", "editor", "translator"),
185
+ ):
186
+ self.style = style
187
+ super().__init__(allow_inplace_modification, name_fields)
188
+
147
189
  # docstr-coverage: inherited
148
- @staticmethod
149
- def metadata_key() -> str:
190
+ @classmethod
191
+ def metadata_key(cls) -> str:
150
192
  return "merge_name_parts"
151
193
 
152
194
  def _transform_field_value(self, name) -> List[str]:
153
195
  if not isinstance(name, list) and all(isinstance(n, NameParts) for n in name):
154
196
  raise ValueError("Expected a list of NameParts, got {}. ".format(name))
155
197
 
156
- return [n.merge_first_name_first for n in name]
198
+ if self.style == "last":
199
+ return [n.merge_last_name_first for n in name]
200
+ elif self.style == "first":
201
+ return [n.merge_first_name_first for n in name]
202
+ else:
203
+ raise ValueError(
204
+ """Expected "first" or "last" style, got {}. """.format(self.style)
205
+ )
157
206
 
158
207
 
159
208
  def parse_single_name_into_parts(name, strict=True):
@@ -229,33 +278,38 @@ def parse_single_name_into_parts(name, strict=True):
229
278
  for char in nameiter:
230
279
  # An escape.
231
280
  if char == "\\":
232
- escaped = next(nameiter)
233
-
234
- # BibTeX doesn't allow whitespace escaping. Copy the slash and fall
235
- # through to the normal case to handle the whitespace.
236
- if escaped in whitespace:
281
+ try:
282
+ escaped = next(nameiter)
283
+
284
+ # BibTeX doesn't allow whitespace escaping. Copy the slash and fall
285
+ # through to the normal case to handle the whitespace.
286
+ if escaped in whitespace:
287
+ word.append(char)
288
+ char = escaped
289
+
290
+ else:
291
+ # Is this the first character in a brace?
292
+ if bracestart:
293
+ bracestart = False
294
+ controlseq = escaped.isalpha()
295
+ specialchar = True
296
+
297
+ # Can we use it to determine the case?
298
+ elif (case == -1) and escaped.isalpha():
299
+ if escaped.isupper():
300
+ case = 1
301
+ else:
302
+ case = 0
303
+
304
+ # Copy the escape to the current word and go to the next
305
+ # character in the input.
306
+ word.append(char)
307
+ word.append(escaped)
308
+ continue
309
+
310
+ # If we're at the end of the string, then the \ is just a \.
311
+ except StopIteration:
237
312
  word.append(char)
238
- char = escaped
239
-
240
- else:
241
- # Is this the first character in a brace?
242
- if bracestart:
243
- bracestart = False
244
- controlseq = escaped.isalpha()
245
- specialchar = True
246
-
247
- # Can we use it to determine the case?
248
- elif (case == -1) and escaped.isalpha():
249
- if escaped.isupper():
250
- case = 1
251
- else:
252
- case = 0
253
-
254
- # Copy the escape to the current word and go to the next
255
- # character in the input.
256
- word.append(char)
257
- word.append(escaped)
258
- continue
259
313
 
260
314
  # Start of a braced expression.
261
315
  if char == "{":
@@ -510,12 +564,19 @@ def split_multiple_persons_names(names):
510
564
 
511
565
  # Escaped character.
512
566
  if char == "\\":
513
- next(namesiter)
567
+ try:
568
+ next(namesiter)
569
+ # If we're at the end of the string, then the \ is just a \.
570
+ except StopIteration:
571
+ pass
514
572
  pos += 1
515
573
  continue
516
574
 
517
575
  # Change in brace level.
518
576
  if char == "{":
577
+ if step == NEXT_WORD:
578
+ spans[-1].append(possible_end)
579
+ spans.append([pos - 1])
519
580
  bracelevel += 1
520
581
  step = START_WHITESPACE
521
582
  continue
@@ -22,8 +22,8 @@ class SortFieldsAlphabeticallyMiddleware(BlockMiddleware):
22
22
  return entry
23
23
 
24
24
  # docstr-coverage: inherited
25
- @staticmethod
26
- def metadata_key() -> str:
25
+ @classmethod
26
+ def metadata_key(cls) -> str:
27
27
  return "sorted_fields_alphabetically"
28
28
 
29
29
 
@@ -71,6 +71,6 @@ class SortFieldsCustomMiddleware(BlockMiddleware):
71
71
  return entry
72
72
 
73
73
  # docstr-coverage: inherited
74
- @staticmethod
75
- def metadata_key() -> str:
74
+ @classmethod
75
+ def metadata_key(cls) -> str:
76
76
  return "sorted_fields_custom"
@@ -5,7 +5,8 @@ from typing import Any, Dict, List, Optional, Set
5
5
  class Block(abc.ABC):
6
6
  """A abstract superclass of all top-level building blocks of a bibtex file.
7
7
 
8
- E.g. a `@string` block, a `@preamble` block, an `@entry` block, a comment, etc."""
8
+ E.g. a ``@string`` block, a ``@preamble`` block, an ``@entry`` block, a comment, etc.
9
+ """
9
10
 
10
11
  def __init__(
11
12
  self,
@@ -51,15 +52,15 @@ class Block(abc.ABC):
51
52
  return self._parser_metadata
52
53
 
53
54
  def get_parser_metadata(self, key: str) -> Optional[Any]:
54
- """EXPERIMENTAL: get auxiliary information stored in `parser_metadata`.
55
+ """EXPERIMENTAL: get auxiliary information stored in ``parser_metadata``.
55
56
 
56
- See attribute `parser_metadata` for more information."""
57
+ See attribute ``parser_metadata`` for more information."""
57
58
  return self._parser_metadata.get(key, None)
58
59
 
59
60
  def set_parser_metadata(self, key: str, value: Any):
60
- """EXPERIMENTAL: set auxiliary information stored in `parser_metadata`.
61
+ """EXPERIMENTAL: set auxiliary information stored in ``parser_metadata``.
61
62
 
62
- See attribute `parser_metadata` for more information."""
63
+ See attribute ``parser_metadata`` for more information."""
63
64
  self._parser_metadata[key] = value
64
65
 
65
66
  def __eq__(self, other):
@@ -72,7 +73,7 @@ class Block(abc.ABC):
72
73
 
73
74
 
74
75
  class String(Block):
75
- """Bibtex Blocks of the `@string` type, e.g. @string{me = "My Name"}."""
76
+ """Bibtex Blocks of the ``@string`` type, e.g. ``@string{me = "My Name"}``."""
76
77
 
77
78
  def __init__(
78
79
  self,
@@ -87,7 +88,7 @@ class String(Block):
87
88
 
88
89
  @property
89
90
  def key(self) -> str:
90
- """The key of the string, e.g. `me` in `@string{me = "My Name"}`."""
91
+ """The key of the string, e.g. ``me`` in ``@string{me = "My Name"}``."""
91
92
  return self._key
92
93
 
93
94
  @key.setter
@@ -96,7 +97,7 @@ class String(Block):
96
97
 
97
98
  @property
98
99
  def value(self) -> str:
99
- """The value of the string, e.g. `"My Name"` in `@string{me = "My Name"}`."""
100
+ """The value of the string, e.g. ``"My Name"`` in ``@string{me = "My Name"}``."""
100
101
  return self._value
101
102
 
102
103
  @value.setter
@@ -114,7 +115,7 @@ class String(Block):
114
115
 
115
116
 
116
117
  class Preamble(Block):
117
- """Bibtex Blocks of the `@preamble` type, e.g. @preamble{This is a preamble}."""
118
+ """Bibtex Blocks of the ``@preamble`` type, e.g. ``@preamble{This is a preamble}``."""
118
119
 
119
120
  def __init__(
120
121
  self, value: str, start_line: Optional[int] = None, raw: Optional[str] = None
@@ -124,7 +125,7 @@ class Preamble(Block):
124
125
 
125
126
  @property
126
127
  def value(self) -> str:
127
- """The value of the preamble, e.g. `blabla` in `@preamble{blabla}`."""
128
+ """The value of the preamble, e.g. ``blabla`` in ``@preamble{blabla}``."""
128
129
  return self._value
129
130
 
130
131
  @value.setter
@@ -142,7 +143,7 @@ class Preamble(Block):
142
143
 
143
144
 
144
145
  class ExplicitComment(Block):
145
- """Bibtex Blocks of the `@comment` type, e.g. @comment{This is a comment}."""
146
+ """Bibtex Blocks of the ``@comment`` type, e.g. ``@comment{This is a comment}``."""
146
147
 
147
148
  def __init__(
148
149
  self, comment: str, start_line: Optional[int] = None, raw: Optional[str] = None
@@ -152,7 +153,7 @@ class ExplicitComment(Block):
152
153
 
153
154
  @property
154
155
  def comment(self) -> str:
155
- """The value of the comment, e.g. `blabla` in `@comment{blabla}`."""
156
+ """The value of the comment, e.g. ``blabla`` in ``@comment{blabla}``."""
156
157
  return self._comment
157
158
 
158
159
  @comment.setter
@@ -170,7 +171,7 @@ class ExplicitComment(Block):
170
171
 
171
172
 
172
173
  class ImplicitComment(Block):
173
- """Bibtex outside of an @{...} block, which is treated as a comment."""
174
+ """Bibtex outside of an ``@{...}`` block, which is treated as a comment."""
174
175
 
175
176
  def __init__(
176
177
  self, comment: str, start_line: Optional[int] = None, raw: Optional[str] = None
@@ -198,7 +199,7 @@ class ImplicitComment(Block):
198
199
 
199
200
 
200
201
  class Field:
201
- """A field of a Bibtex entry, e.g. `author = {John Doe}`."""
202
+ """A field of a Bibtex entry, e.g. ``author = {John Doe}``."""
202
203
 
203
204
  def __init__(self, key: str, value: Any, start_line: Optional[int] = None):
204
205
  self._start_line = start_line
@@ -207,7 +208,7 @@ class Field:
207
208
 
208
209
  @property
209
210
  def key(self) -> str:
210
- """The key of the field, e.g. `author` in `author = {John Doe}`."""
211
+ """The key of the field, e.g. ``author`` in ``author = {John Doe}``."""
211
212
  return self._key
212
213
 
213
214
  @key.setter
@@ -216,7 +217,7 @@ class Field:
216
217
 
217
218
  @property
218
219
  def value(self) -> Any:
219
- """The value of the field, e.g. `{John Doe}` in `author = {John Doe}`."""
220
+ """The value of the field, e.g. ``{John Doe}`` in ``author = {John Doe}``."""
220
221
  return self._value
221
222
 
222
223
  @value.setter
@@ -247,7 +248,7 @@ class Field:
247
248
 
248
249
 
249
250
  class Entry(Block):
250
- """Bibtex Blocks of the `@entry` type, e.g. @article{Cesar2013, ...}."""
251
+ """Bibtex Blocks of the ``@entry`` type, e.g. ``@article{Cesar2013, ...}``."""
251
252
 
252
253
  def __init__(
253
254
  self,
@@ -264,7 +265,7 @@ class Entry(Block):
264
265
 
265
266
  @property
266
267
  def entry_type(self):
267
- """The type of the entry, e.g. `article` in `@article{Cesar2013, ...}`."""
268
+ """The type of the entry, e.g. ``article`` in ``@article{Cesar2013, ...}``."""
268
269
  return self._entry_type
269
270
 
270
271
  @entry_type.setter
@@ -273,7 +274,7 @@ class Entry(Block):
273
274
 
274
275
  @property
275
276
  def key(self):
276
- """The key of the entry, e.g. `Cesar2013` in `@article{Cesar2013, ...}`."""
277
+ """The key of the entry, e.g. ``Cesar2013`` in ``@article{Cesar2013, ...}``."""
277
278
  return self._key
278
279
 
279
280
  @key.setter
@@ -282,7 +283,7 @@ class Entry(Block):
282
283
 
283
284
  @property
284
285
  def fields(self) -> List[Field]:
285
- """The key-value attributes of an entry, as `Field` instances."""
286
+ """The key-value attributes of an entry, as ``Field`` instances."""
286
287
  return self._fields
287
288
 
288
289
  @fields.setter
@@ -304,6 +305,30 @@ class Entry(Block):
304
305
  else:
305
306
  self._fields.append(field)
306
307
 
308
+ def pop(self, key: str, default=None) -> Optional[Field]:
309
+ """Removes and returns the field with the given key.
310
+
311
+ :param key: The key of the field to remove.
312
+ :param default: The value to return if the field does not exist."""
313
+ try:
314
+ field = self.fields_dict.pop(key)
315
+ except KeyError:
316
+ return default
317
+
318
+ self._fields = [f for f in self._fields if f.key != key]
319
+ return field
320
+
321
+ def get(self, key: str, default=None) -> Optional[Field]:
322
+ """Returns the field with the given key, or the default value if it does not exist.
323
+
324
+ :param key: The key of the field.
325
+ :param default: The value to return if the field does not exist."""
326
+ return self.fields_dict.get(key, default)
327
+
328
+ def __contains__(self, key: str) -> bool:
329
+ """Dict-mimicking ``in`` operator."""
330
+ return key in self.fields_dict
331
+
307
332
  def __getitem__(self, key: str) -> Any:
308
333
  """Dict-mimicking index.
309
334
 
@@ -318,6 +343,22 @@ class Entry(Block):
318
343
  return self.key
319
344
  return self.fields_dict[key].value
320
345
 
346
+ def __setitem__(self, key: str, value: Any):
347
+ """Dict-mimicking index.
348
+
349
+ This serves for partial v1.x backwards compatibility,
350
+ as well as for a shorthand for `set_field`.
351
+ """
352
+ self.set_field(Field(key, value))
353
+
354
+ def __delitem__(self, key):
355
+ """Dict-mimicking index.
356
+
357
+ This serves for partial v1.x backwards compatibility,
358
+ as well as for a shorthand for `pop`.
359
+ """
360
+ self.pop(key)
361
+
321
362
  def items(self):
322
363
  """Dict-mimicking, for partial v1.x backwards compatibility.
323
364
 
@@ -410,7 +451,7 @@ class DuplicateBlockKeyBlock(ParsingFailedBlock):
410
451
 
411
452
  @property
412
453
  def key(self) -> str:
413
- """The key of the entry, e.g. `Cesar2013` in `@article{Cesar2013, ...}`."""
454
+ """The key of the entry, e.g. ``Cesar2013`` in ``@article{Cesar2013, ...}``."""
414
455
  return self._key
415
456
 
416
457
  @key.setter
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bibtexparser
3
- Version: 2.0.0b5
3
+ Version: 2.0.0b7
4
4
  Summary: Bibtex parser for python 3
5
5
  Home-page: https://github.com/sciunto-org/python-bibtexparser
6
6
  Author: Michael Weiss and other contributors
@@ -120,7 +120,7 @@ Writes the content of a bibtex library to a `.bib` file. Optional formatting par
120
120
 
121
121
  ## About
122
122
 
123
- Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)).
123
+ Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)). In 2024, Tom de Geus ([@tdegeus](https://github.com/tdegeus)) joined as co-maintainer.
124
124
 
125
125
  Credits and thanks to the many contributors who helped creating this library, including
126
126
  François Boulogne ([@sciunto](https://github.com/sciunto/), creator of the first version) and Olivier Mangin ([@omangin](https://github.com/omangin/), long-term contributor).
@@ -1,6 +1,8 @@
1
1
  from copy import copy, deepcopy
2
2
  from textwrap import dedent
3
3
 
4
+ import pytest
5
+
4
6
  from bibtexparser.model import (
5
7
  Entry,
6
8
  ExplicitComment,
@@ -75,6 +77,34 @@ def test_entry_deepcopy():
75
77
  assert entry_1.fields_dict["field"] == entry_2.fields_dict["field"]
76
78
 
77
79
 
80
+ def test_entry_get():
81
+ entry1 = Entry(
82
+ "article", "key", [Field("field", "value", 1), Field("foo", "bar", 2)], 1, "raw"
83
+ )
84
+ entry2 = Entry(
85
+ "article", "key", [Field("field", "value", 1), Field("foo", "bar", 2)], 1, "raw"
86
+ )
87
+ assert entry1.get("other", "default") == "default"
88
+ assert entry1.get("foo") == Field("foo", "bar", 2)
89
+ assert entry1 == entry2
90
+
91
+
92
+ def test_entry_pop():
93
+ entry1 = Entry(
94
+ "article", "key", [Field("field", "value", 1), Field("foo", "bar", 2)], 1, "raw"
95
+ )
96
+ entry2 = Entry("article", "key", [Field("field", "value", 1)], 1, "raw")
97
+ assert entry1.pop("other", "default") == "default"
98
+ assert entry1.pop("foo") == Field("foo", "bar", 2)
99
+ assert entry1 == entry2
100
+
101
+
102
+ def test_entry_contains():
103
+ entry = Entry("article", "key", [Field("field", "value", 1)], 1, "raw")
104
+ assert "field" in entry
105
+ assert "other" not in entry
106
+
107
+
78
108
  def test_string_equality():
79
109
  # Equal to itself
80
110
  string_1 = String(
@@ -290,3 +320,30 @@ def test_entry_str():
290
320
  )
291
321
 
292
322
  assert str(entry) == expected
323
+
324
+
325
+ def test_entry_fields_shorthand():
326
+ entry = Entry(
327
+ entry_type="article",
328
+ key="myEntry",
329
+ fields=[
330
+ Field("myFirstField", "firstValue"),
331
+ Field("mySecondField", "secondValue"),
332
+ ],
333
+ )
334
+
335
+ entry["myFirstField"] = "changed_value"
336
+ assert entry["myFirstField"] == "changed_value"
337
+ assert entry.fields_dict["myFirstField"].value == "changed_value"
338
+
339
+ entry["myNewField"] = "new_value"
340
+ assert entry["myNewField"] == "new_value"
341
+ assert entry.fields_dict["myNewField"].key == "myNewField"
342
+ assert entry.fields_dict["myNewField"].value == "new_value"
343
+ assert entry.fields_dict["myNewField"].start_line is None
344
+
345
+ del entry["myNewField"]
346
+ assert "myNewField" not in entry.fields_dict
347
+ assert len([f for f in entry.fields if f.key == "myNewField"]) == 0
348
+ with pytest.raises(KeyError):
349
+ entry["myNewField"]
File without changes
File without changes
File without changes