blackref 0.1.7__py3-none-any.whl

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.
blackref/__init__.py ADDED
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env python3
2
+
3
+ try:
4
+ import sys
5
+ from pathlib import Path
6
+ import textwrap
7
+ import re
8
+ import configargparse
9
+ import bibtexparser
10
+ import isbnlib
11
+ from pylatexenc.latex2text import LatexNodes2Text
12
+ from pylatexenc.latexencode import unicode_to_latex
13
+ except ImportError:
14
+ sys.stderr.write('Some libraries are missing.\n')
15
+
16
+
17
+ __version__ = '0.1.7'
18
+ __author__ = 'David Völgyes'
19
+ __email__ = 'david.volgyes@ieee.org'
20
+ __license__ = 'AGPLv3'
21
+ __summary__ = 'An uncompromising BibTeX/BibLaTeX reference list formatter.'
22
+ __description__ = __summary__
23
+
24
+
25
+ def eprint(*args, **kwargs):
26
+ print(*args, **kwargs, file=sys.stderr)# noqa: T001
27
+
28
+
29
+ def fix_paragraphs(text):
30
+ text = re.sub(r'(\s(?P<pattern>BACKGROUND|Background))', r'\n\n\g<pattern>', text)
31
+ text = re.sub(r'(\s(?P<pattern>METHODS|Methods))', r'\n\n\g<pattern>', text)
32
+ text = re.sub(r'(\s(?P<pattern>CONCLUSION|Conclusion))', r'\n\n\g<pattern>', text)
33
+ text = re.sub(r'(\s(?P<pattern>RESULTS|Results))', r'\n\n\g<pattern>', text)
34
+ return text
35
+
36
+
37
+ def fix_wrap(text, key, indent=10, line_length=80, relax=10):
38
+ text = ' '.join(text.split()) # removing all extra whitespaces
39
+ text = fix_paragraphs(text)
40
+ wrapper = textwrap.TextWrapper()
41
+ wrapper.width = line_length - indent
42
+ wrapper.break_long_words = False
43
+ wrapper.break_on_hyphen = False
44
+ indent_text = ' ' * indent
45
+
46
+ if (key.lower() in ['abstract', 'title', 'booktitle']
47
+ and len(text) > wrapper.width + relax):
48
+ N = len(text)
49
+ if N < 2 * line_length:
50
+ p = text[N // 2:].find(' ')
51
+ if p >= 0:
52
+ p += N // 2
53
+ parts = [text[:p], text[p:]]
54
+ else:
55
+ parts = wrapper.wrap(text)
56
+ else:
57
+ parts = wrapper.wrap(text)
58
+ result = f'\n{indent_text}'.join(map(str.strip, parts))
59
+ return result
60
+
61
+ if key.lower() in ['author', 'editor']:
62
+ names = text.strip().split(' and ')
63
+ N = max(map(len, map(str.strip, names)))
64
+ padded_names = []
65
+ for name in names:
66
+ k = N - len(name)
67
+ padded_names.append(name + (' ' * k))
68
+ result = f' and\n{indent_text}'.join(padded_names).strip()
69
+ return result
70
+
71
+ if key.lower() in ['keywords', 'keyword']:
72
+ keywords = text.strip().replace(';', ',').replace(', ', ',')
73
+ keywords = keywords.split(',')
74
+ keywords = ', '.join(map(str.strip, keywords))
75
+ parts = wrapper.wrap(keywords)
76
+ result = f'\n{indent_text}'.join(map(str.strip, parts))
77
+ return result.replace('_', ' ')
78
+
79
+ return text.strip()
80
+
81
+
82
+ def fix_isbn(entry):
83
+ if 'isbn' in entry:
84
+ value = entry['isbn']
85
+ if isbnlib.is_isbn10(value):
86
+ value = isbnlib.to_isbn13(value)
87
+ if not isbnlib.is_isbn13(value):
88
+ raise Exception(f'invalid isbn in {entry["ID"]}: {entry["isbn"]}')
89
+ entry['isbn'] = isbnlib.mask(value, separator='-')
90
+ return entry
91
+
92
+
93
+ def fix_issn(entry):
94
+ if 'issn' in entry:
95
+ value = entry['issn'].replace('-', '')
96
+ value = value[0:4] + '-' + value[4:]
97
+ if len(value) != 9:
98
+ raise Exception(f'invalid issn in {entry["ID"]}: {entry["issn"]}')
99
+ entry['issn'] = value
100
+ return entry
101
+
102
+
103
+ def fix_pages(entry):
104
+ if 'pages' in entry:
105
+ value = entry['pages'].replace(' ', '')
106
+ re.sub(r'([^-])-([^-])', r'r\g<1>--\g<2>', value)
107
+ entry['pages'] = value
108
+ return entry
109
+
110
+
111
+ def remove_empty_keys(entry):
112
+ for key in list(entry.keys()):
113
+ if entry[key] is None:
114
+ entry[key] = ''
115
+ v = str(entry[key]).strip()
116
+ if len(v) == 0:
117
+ entry.pop(key)
118
+ return entry
119
+
120
+
121
+ def fix_utf8_field(entry, field, args):
122
+ if field not in entry:
123
+ return entry
124
+
125
+ value = entry[field]
126
+ if field is args.utf8:
127
+ value = LatexNodes2Text().latex_to_text(value)
128
+ elif field is args.latex:
129
+ value = unicode_to_latex(value)
130
+ entry[field] = value
131
+
132
+ return entry
133
+
134
+
135
+ def fix_authors(entry, args):
136
+ return fix_utf8_field(entry, 'author', args)
137
+
138
+
139
+ def fix_abstract(entry, args):
140
+ return fix_utf8_field(entry, 'abstract', args)
141
+
142
+
143
+ def formatter(bib, args):
144
+ display_order, sort = args.display_order, args.sort
145
+ writer = bibtexparser.bwriter.BibTexWriter()
146
+ writer.add_trailing_comma = True
147
+ writer.display_order = display_order
148
+ writer.order_entries_by = None
149
+ writer.align_values = 10
150
+ writer.indent = ' '
151
+ writer.contents = ['preambles', 'entries', 'strings']
152
+
153
+ max_key_length = 0
154
+ for entry in bib.entries:
155
+ entry = remove_empty_keys(entry)
156
+ entry = fix_authors(entry, args)
157
+ entry = fix_abstract(entry, args)
158
+ entry = fix_isbn(entry)
159
+ entry = fix_issn(entry)
160
+ entry = fix_pages(entry)
161
+ for key in entry.keys():
162
+ max_key_length = max(max_key_length, len(key) + len(writer.indent) + 4)
163
+
164
+ for entry in bib.entries:
165
+ for key in entry.keys():
166
+ entry[key] = fix_wrap(entry[key], key, indent=max_key_length)
167
+
168
+ for skey in reversed(sort):
169
+ reverse = skey[-1] == '-'
170
+ if reverse:
171
+ skey = skey[:-1]
172
+ bib.entries = sorted(bib.entries,
173
+ key=lambda x: x.get(skey, '').lower(),
174
+ reverse=reverse)
175
+
176
+ return writer.write(bib)
177
+
178
+
179
+ def main(cli_args=None):
180
+ class LazyOpen:
181
+
182
+ def __init__(self, s, mode):
183
+ self.s = s
184
+ self.mode = mode
185
+ self.fh = None
186
+
187
+ def __enter__(self):
188
+ if isinstance(self.s, str):
189
+ self.fh = open(self.s, self.mode)
190
+ else:
191
+ self.fh = self.s
192
+ return self.fh
193
+
194
+ def __exit__(self, *exc):
195
+ if isinstance(self.s, str):
196
+ self.fh.close()
197
+ return
198
+
199
+ parser = configargparse.ArgParser(
200
+ auto_env_var_prefix='BLACKREF_',
201
+ add_env_var_help=True,
202
+ add_config_file_help=True,
203
+ default_config_files=['~/.config/blackref.conf'],
204
+ description='The uncompromising reference formatter.'
205
+ )
206
+
207
+ parser.add_argument(
208
+ '-c', '--config',
209
+ is_config_file=True,
210
+ dest='configfile',
211
+ help='Config file with "key: value" items.'
212
+ ' CLI options have higher precedence than config values.')
213
+
214
+ parser.add_argument(
215
+ '-w', '--write-back',
216
+ dest='writeback',
217
+ action='store_true',
218
+ help='Write modifications back to the original file.',
219
+ default=False
220
+ )
221
+
222
+ parser.add_argument(
223
+ '-U', '--utf8',
224
+ dest='utf8',
225
+ metavar='FIELD[,FIELD]',
226
+ help='Comma separated fieldnames for UTF8 encoding. Default: abstract',
227
+ default='abstract'
228
+ )
229
+
230
+ parser.add_argument(
231
+ '-L', '--latex',
232
+ dest='latex',
233
+ metavar='FIELD[,FIELD]',
234
+ help='Comma separated fieldnames for LaTeX encoding. Default: author,title',
235
+ default='author,title'
236
+ )
237
+
238
+ parser.add_argument(
239
+ '-o',
240
+ '--output',
241
+ dest='output',
242
+ metavar='DST',
243
+ type=str,
244
+ help='output file',
245
+ default=sys.stdout,
246
+ )
247
+
248
+ parser.add_argument(
249
+ '-s',
250
+ '--sort',
251
+ dest='sort',
252
+ metavar='KEYS',
253
+ type=str,
254
+ help='Comma separated list of BibTeX fields for sorting entries.'
255
+ ' Default: ID',
256
+ default='ID'
257
+ )
258
+
259
+ order = ','.join(('title',
260
+ 'booktitle',
261
+ 'author',
262
+ 'editor',
263
+ 'abstract',
264
+ 'journal',
265
+ 'issn',
266
+ 'volume',
267
+ 'year',
268
+ 'month',
269
+ 'number',
270
+ 'pages',
271
+ 'publisher',
272
+ 'address',
273
+ 'doi',
274
+ 'pubmedid',
275
+ 'url',
276
+ 'notes'))
277
+
278
+ parser.add_argument(
279
+ '-d',
280
+ '--display-order',
281
+ dest='display_order',
282
+ metavar='FIELDS',
283
+ type=str,
284
+ help=f'Order of display for BibTeX fields. Default: {order}',
285
+ default=order
286
+ )
287
+
288
+ parser.add_argument(
289
+ 'src',
290
+ metavar='SRC',
291
+ nargs='?',
292
+ type=str,
293
+ help='source file',
294
+ default=sys.stdin
295
+ )
296
+
297
+ if cli_args is not None:
298
+ args = parser.parse_args(cli_args)
299
+ else:
300
+ args = parser.parse_args()
301
+
302
+ args.sort = tuple(x.strip() for x in args.sort.split(','))
303
+ args.utf8 = {x.strip() for x in args.utf8.lower().split(',')}
304
+ args.latex = {x.strip() for x in args.latex.lower().split(',')}
305
+ args.utf8 = args.utf8 - args.latex
306
+ args.display_order = tuple(x.strip() for x in args.display_order.split(','))
307
+
308
+ if args.writeback:
309
+ if args.output == sys.stdout and args.src != sys.stdin:
310
+ args.output = args.src
311
+
312
+ if args.src != sys.stdin and not Path(args.src).exists():
313
+ eprint(f'Invalid input file: {args.src}')
314
+ sys.exit(-1)
315
+
316
+ with LazyOpen(args.src, 'rt') as fh:
317
+ bib = bibtexparser.loads(fh.read())
318
+
319
+ with LazyOpen(args.output, 'wt') as f:
320
+ f.write(formatter(bib, args))
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: blackref
3
+ Version: 0.1.7
4
+ Summary: An uncompromising BibTeX/BibLaTeX reference list formatter.
5
+ Project-URL: Homepage, https://github.com/dvolgyes/blackref
6
+ Project-URL: Repository, https://github.com/dvolgyes/blackref
7
+ Project-URL: Issues, https://github.com/dvolgyes/blackref/issues
8
+ Author-email: David Völgyes <david.volgyes@ieee.org>
9
+ License: AGPL-3.0
10
+ License-File: LICENSE.txt
11
+ Keywords: biblatex,bibtex,code formatting,latex
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
15
+ Classifier: Natural Language :: English
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Requires-Python: >=3.8
22
+ Requires-Dist: bibtexparser
23
+ Requires-Dist: configargparse
24
+ Requires-Dist: isbnlib
25
+ Requires-Dist: pylatexenc
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Blackref: an uncompromising BibTeX formatter
29
+
30
+ Travis build: [![Build Status](https://travis-ci.org/dvolgyes/blackref.svg?branch=master)](https://travis-ci.org/dvolgyes/blackref)
31
+ Coverage: [![Coverage Status](https://img.shields.io/coveralls/github/dvolgyes/blackref/master)](https://img.shields.io/coveralls/github/dvolgyes/blackref/master)
32
+
33
+ BibTeX files are sometimes hard to read for human beings.
34
+ I decided to start two pet project of my own:
35
+ - reflint for checking BibTeX files, fixing fields, but not changing formatting
36
+ - blackref for fixing the BibTeX code style, but not changing any content
37
+ (not counting formatting changes, e.g. ISBN formatting)
38
+
39
+ Ideally, reflint fixes / warns about missing fields, incorrect values,
40
+ and blackref formats everything nicely, but does not do any semantic changes.
41
+
42
+ Both of them should run both as a command line tool or as a pre-commit hook.
43
+ (work in progress)
@@ -0,0 +1,6 @@
1
+ blackref/__init__.py,sha256=NbVCKqeHRvOOUsRn2N-fJ6SYLQRhQB1BQD4HHlbyhIA,9295
2
+ blackref-0.1.7.dist-info/METADATA,sha256=EVoVT3TQ8wk02ld1H78XDzxrhP7Pr0MArida9uwwF40,1991
3
+ blackref-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
+ blackref-0.1.7.dist-info/entry_points.txt,sha256=AJjvg1PxiXPNa2lszSv-5wwZVpw6iLQHutJJBg-1sMc,43
5
+ blackref-0.1.7.dist-info/licenses/LICENSE.txt,sha256=nRZsEuIb2f6bBK03vNPPY88GPUlM4supGXr-xtmKZ4E,1081
6
+ blackref-0.1.7.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ blackref = blackref:main
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 David Völgyes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.