voy 0.0.2__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.
voy-0.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Florin Gogianu
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.
voy-0.0.2/PKG-INFO ADDED
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.1
2
+ Name: voy
3
+ Version: 0.0.2
4
+ Summary: A CLI for following arXiv authors.
5
+ Author-email: Florin Gogianu <florin.gogianu@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Florin Gogianu
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/floringogianu/voy
29
+ Keywords: arxiv,feed
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Programming Language :: Python
32
+ Classifier: Programming Language :: Python :: 3
33
+ Requires-Python: >=3.12.0
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: arxiv>=2.1.0
37
+ Requires-Dist: colorful>=0.5.6
38
+ Requires-Dist: datargs>=1.1.0
39
+ Requires-Dist: jsonlines>=4.0.0
40
+ Requires-Dist: platformdirs>=4.1.0
41
+ Requires-Dist: xxhash>=3.4.1
42
+ Requires-Dist: windows-curses>=2.3.2; sys_platform == "win32"
43
+
44
+ # voy
45
+
46
+ A CLI for following arxiv authors.
47
+
48
+ ## Installation
49
+
50
+ Configure an environment with `python 3.12` and run the command:
51
+
52
+ ```sh
53
+ pip install git+https://github.com/floringogianu/voy.git
54
+ ```
55
+
56
+ I used some experimental syntax that's only in `3.11` and above and I haven't aimed at backward compatibility yet.
57
+
58
+ ## Usage
59
+
60
+ Start with `voy --help`. A general flow would be:
61
+ - `voy search [FirstName] LastName`: queries arxiv and returns a list of authors and their papers matching the name
62
+ - `voy follow [FirstName] LastName`: follow the author so that their new papers show up when calling `voy show`
63
+ - `voy update`: after each `voy follow` and then daily. It updates the database with the most recent papers by the authors you follow.
64
+ In addition it commits to the database every co-author on these papers.
65
+ This makes it easy to search for authors in your database, using `voy show Some Researcher`.
66
+ - `voy show` / `voy show [FirstName] LastName`: **probably the command you will use most often**. It lists the most recent papers of
67
+ the people you follow or of a specific author. Check `voy show --help` for the many options of this command.
68
+ - `voy triage`: quickly review the papers in your feed.
69
+
70
+ ### Getting started
71
+
72
+ https://github.com/floringogianu/voy/assets/1670348/65a6bc6f-e813-4516-a321-76774b4f4cf7
73
+
74
+ ### Triage
75
+
76
+ Let's say you have a large number of researchers you follow and you want to quickly review and triage papers so that they don't show anymore in the `voy show` feed.
77
+ You can achieve this with `voy triage`.
78
+
79
+ https://github.com/floringogianu/voy/assets/1670348/6457b97f-980e-4c7d-b5f5-2f03e288554d
80
+
voy-0.0.2/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # voy
2
+
3
+ A CLI for following arxiv authors.
4
+
5
+ ## Installation
6
+
7
+ Configure an environment with `python 3.12` and run the command:
8
+
9
+ ```sh
10
+ pip install git+https://github.com/floringogianu/voy.git
11
+ ```
12
+
13
+ I used some experimental syntax that's only in `3.11` and above and I haven't aimed at backward compatibility yet.
14
+
15
+ ## Usage
16
+
17
+ Start with `voy --help`. A general flow would be:
18
+ - `voy search [FirstName] LastName`: queries arxiv and returns a list of authors and their papers matching the name
19
+ - `voy follow [FirstName] LastName`: follow the author so that their new papers show up when calling `voy show`
20
+ - `voy update`: after each `voy follow` and then daily. It updates the database with the most recent papers by the authors you follow.
21
+ In addition it commits to the database every co-author on these papers.
22
+ This makes it easy to search for authors in your database, using `voy show Some Researcher`.
23
+ - `voy show` / `voy show [FirstName] LastName`: **probably the command you will use most often**. It lists the most recent papers of
24
+ the people you follow or of a specific author. Check `voy show --help` for the many options of this command.
25
+ - `voy triage`: quickly review the papers in your feed.
26
+
27
+ ### Getting started
28
+
29
+ https://github.com/floringogianu/voy/assets/1670348/65a6bc6f-e813-4516-a321-76774b4f4cf7
30
+
31
+ ### Triage
32
+
33
+ Let's say you have a large number of researchers you follow and you want to quickly review and triage papers so that they don't show anymore in the `voy show` feed.
34
+ You can achieve this with `voy triage`.
35
+
36
+ https://github.com/floringogianu/voy/assets/1670348/6457b97f-980e-4c7d-b5f5-2f03e288554d
37
+
@@ -0,0 +1,36 @@
1
+ [project]
2
+ name = "voy"
3
+ version = "0.0.2"
4
+ description = "A CLI for following arXiv authors."
5
+ readme = "README.md"
6
+ authors = [{ name = "Florin Gogianu", email = "florin.gogianu@gmail.com" }]
7
+ license = { file = "LICENSE" }
8
+ classifiers = [
9
+ "License :: OSI Approved :: MIT License",
10
+ "Programming Language :: Python",
11
+ "Programming Language :: Python :: 3",
12
+ ]
13
+ keywords = ["arxiv", "feed"]
14
+ dependencies = [
15
+ "arxiv>=2.1.0",
16
+ "colorful>=0.5.6",
17
+ "datargs>=1.1.0",
18
+ "jsonlines>=4.0.0",
19
+ "platformdirs>=4.1.0",
20
+ "xxhash>=3.4.1",
21
+ "windows-curses>=2.3.2; sys_platform == 'win32'"
22
+ ]
23
+ requires-python = ">=3.12.0"
24
+
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/floringogianu/voy"
28
+
29
+
30
+ [project.scripts]
31
+ voy = "voy.cmd:voy"
32
+
33
+
34
+ [build-system]
35
+ requires = ["setuptools"]
36
+ build-backend = "setuptools.build_meta"
voy-0.0.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,61 @@
1
+ """Package constants and settings."""
2
+
3
+ import logging
4
+ import logging.config
5
+
6
+ import colorful as cf
7
+ from platformdirs import user_data_path, user_log_path
8
+
9
+ # set globals
10
+ VOY_PATH = user_data_path("voy", ensure_exists=True)
11
+ VOY_LOGS = user_log_path("voy", ensure_exists=True) / "voy.log"
12
+ CATEGORIES = ["cs.CV", "cs.LG", "cs.CL", "cs.AI", "cs.NE", "cs.RO"]
13
+
14
+
15
+ # set logging
16
+ logging.config.dictConfig(
17
+ config={
18
+ "version": 1,
19
+ "disable_existing_loggers": False,
20
+ "formatters": {
21
+ "simple": {"format": "%(levelname)s: %(message)s"},
22
+ "detailed": {
23
+ "format": "[%(asctime)s %(levelname)s %(module)s:L%(lineno)d] %(message)s",
24
+ "datefmt": "%Y-%m-%dT%H:%M:%S%z",
25
+ },
26
+ },
27
+ "handlers": {
28
+ "stderr": {
29
+ "class": "logging.StreamHandler",
30
+ "formatter": "simple",
31
+ "level": "CRITICAL",
32
+ "stream": "ext://sys.stderr",
33
+ },
34
+ "stdout": {
35
+ "class": "logging.StreamHandler",
36
+ "formatter": "simple",
37
+ "level": "WARNING",
38
+ "stream": "ext://sys.stdout",
39
+ }, # configured but unused
40
+ "file": {
41
+ "backupCount": 5,
42
+ "class": "logging.handlers.RotatingFileHandler",
43
+ "filename": VOY_LOGS,
44
+ "formatter": "detailed",
45
+ "level": "DEBUG",
46
+ "maxBytes": 100_000,
47
+ },
48
+ },
49
+ "loggers": {"root": {"level": "DEBUG", "handlers": ["stderr", "file"]}},
50
+ }
51
+ )
52
+
53
+ # get application logger
54
+ log = logging.getLogger("voy")
55
+
56
+
57
+ # set colors
58
+ cf.use_8_ansi_colors()
59
+
60
+
61
+ log.info("set up complete.")
voy-0.0.2/voy/cmd.py ADDED
@@ -0,0 +1,11 @@
1
+ """Here we define wrapper functions to be called when one invokes
2
+ console commands.
3
+ """
4
+
5
+ # pylint: disable=import-outside-toplevel
6
+
7
+
8
+ def voy():
9
+ from .voy import main as _voy
10
+
11
+ _voy()
File without changes
@@ -0,0 +1,214 @@
1
+ import re
2
+ from typing import Dict, Match, Pattern
3
+
4
+ """
5
+ Copyright 2017 Cornell University
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
8
+ this software and associated documentation files (the "Software"), to deal in
9
+ the Software without restriction, including without limitation the rights to
10
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11
+ of the Software, and to permit persons to whom the Software is furnished to do
12
+ so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+ """
25
+
26
+ """Convert between TeX escapes and UTF8."""
27
+
28
+ # fmt: off
29
+ accents = {
30
+ # first accents with non-letter prefix, e.g. \'A
31
+ "'A": 0x00c1, "'C": 0x0106, "'E": 0x00c9, "'I": 0x00cd,
32
+ "'L": 0x0139, "'N": 0x0143, "'O": 0x00d3, "'R": 0x0154,
33
+ "'S": 0x015a, "'U": 0x00da, "'Y": 0x00dd, "'Z": 0x0179,
34
+ "'a": 0x00e1, "'c": 0x0107, "'e": 0x00e9, "'i": 0x00ed,
35
+ "'l": 0x013a, "'n": 0x0144, "'o": 0x00f3, "'r": 0x0155,
36
+ "'s": 0x015b, "'u": 0x00fa, "'y": 0x00fd, "'z": 0x017a,
37
+ '"A': 0x00c4, '"E': 0x00cb, '"I': 0x00cf, '"O': 0x00d6,
38
+ '"U': 0x00dc, '"Y': 0x0178, '"a': 0x00e4, '"e': 0x00eb,
39
+ '"i': 0x00ef, '"o': 0x00f6, '"u': 0x00fc, '"y': 0x00ff,
40
+ '.A': 0x0226, '.C': 0x010a, '.E': 0x0116, '.G': 0x0120,
41
+ '.I': 0x0130, '.O': 0x022e, '.Z': 0x017b, '.a': 0x0227,
42
+ '.c': 0x010b, '.e': 0x0117, '.g': 0x0121, '.o': 0x022f,
43
+ '.z': 0x017c, '=A': 0x0100, '=E': 0x0112, '=I': 0x012a,
44
+ '=O': 0x014c, '=U': 0x016a, '=Y': 0x0232, '=a': 0x0101,
45
+ '=e': 0x0113, '=i': 0x012b, '=o': 0x014d, '=u': 0x016b,
46
+ '=y': 0x0233, '^A': 0x00c2, '^C': 0x0108, '^E': 0x00ca,
47
+ '^G': 0x011c, '^H': 0x0124, '^I': 0x00ce, '^J': 0x0134,
48
+ '^O': 0x00d4, '^S': 0x015c, '^U': 0x00db, '^W': 0x0174,
49
+ '^Y': 0x0176, '^a': 0x00e2, '^c': 0x0109, '^e': 0x00ea,
50
+ '^g': 0x011d, '^h': 0x0125, '^i': 0x00ee, '^j': 0x0135,
51
+ '^o': 0x00f4, '^s': 0x015d, '^u': 0x00fb, '^w': 0x0175,
52
+ '^y': 0x0177, '`A': 0x00c0, '`E': 0x00c8, '`I': 0x00cc,
53
+ '`O': 0x00d2, '`U': 0x00d9, '`a': 0x00e0, '`e': 0x00e8,
54
+ '`i': 0x00ec, '`o': 0x00f2, '`u': 0x00f9, '~A': 0x00c3,
55
+ '~I': 0x0128, '~N': 0x00d1, '~O': 0x00d5, '~U': 0x0168,
56
+ '~a': 0x00e3, '~i': 0x0129, '~n': 0x00f1, '~o': 0x00f5,
57
+ '~u': 0x0169,
58
+ # and now ones with letter prefix \c{c} etc..
59
+ 'HO': 0x0150, 'HU': 0x0170, 'Ho': 0x0151, 'Hu': 0x0171,
60
+ 'cC': 0x00c7, 'cE': 0x0228,
61
+ 'cG': 0x0122, 'cK': 0x0136, 'cL': 0x013b, 'cN': 0x0145,
62
+ 'cR': 0x0156, 'cS': 0x015e, 'cT': 0x0162, 'cc': 0x00e7,
63
+ 'ce': 0x0229, 'cg': 0x0123, 'ck': 0x0137, 'cl': 0x013c,
64
+ # Commented out due ARXIVDEV-2322 (bug reported by PG)
65
+ # 'ci' : 'i\x{0327}' = chr(0x69).ch(0x327) # i with combining cedilla
66
+ 'cn': 0x0146, 'cr': 0x0157, 'cs': 0x015f, 'ct': 0x0163,
67
+ 'kA': 0x0104, 'kE': 0x0118, 'kI': 0x012e, 'kO': 0x01ea,
68
+ 'kU': 0x0172, 'ka': 0x0105, 'ke': 0x0119, 'ki': 0x012f,
69
+ 'ko': 0x01eb, 'ku': 0x0173, 'rA': 0x00c5, 'rU': 0x016e,
70
+ 'ra': 0x00e5, 'ru': 0x016f, 'uA': 0x0102, 'uE': 0x0114,
71
+ 'uG': 0x011e, 'uI': 0x012c, 'uO': 0x014e, 'uU': 0x016c,
72
+ 'ua': 0x0103, 'ue': 0x0115, 'ug': 0x011f,
73
+ 'ui': 0x012d, 'uo': 0x014f, 'uu': 0x016d,
74
+ 'vA': 0x01cd, 'vC': 0x010c, 'vD': 0x010e,
75
+ 'vE': 0x011a, 'vG': 0x01e6, 'vH': 0x021e, 'vI': 0x01cf,
76
+ 'vK': 0x01e8, 'vL': 0x013d, 'vN': 0x0147, 'vO': 0x01d1,
77
+ 'vR': 0x0158, 'vS': 0x0160, 'vT': 0x0164, 'vU': 0x01d3,
78
+ 'vZ': 0x017d, 'va': 0x01ce, 'vc': 0x010d, 'vd': 0x010f,
79
+ 've': 0x011b, 'vg': 0x01e7, 'vh': 0x021f, 'vi': 0x01d0,
80
+ 'vk': 0x01e9, 'vl': 0x013e, 'vn': 0x0148, 'vo': 0x01d2,
81
+ 'vr': 0x0159, 'vs': 0x0161, 'vt': 0x0165, 'vu': 0x01d4,
82
+ 'vz': 0x017e
83
+ }
84
+ r"""
85
+ Hash to lookup tex markup and convert to Unicode.
86
+
87
+ macron: a line above character (overbar \={} in TeX)
88
+ caron: v-shape above character (\v{ } in TeX)
89
+ See: http://www.unicode.org/charts/
90
+
91
+ """
92
+
93
+ textlet = {
94
+ 'AA': 0x00c5, 'AE': 0x00c6, 'DH': 0x00d0, 'DJ': 0x0110,
95
+ 'ETH': 0x00d0, 'L': 0x0141, 'NG': 0x014a, 'O': 0x00d8,
96
+ 'oe': 0x0153, 'OE': 0x0152, 'TH': 0x00de, 'aa': 0x00e5,
97
+ 'ae': 0x00e6,
98
+ 'dh': 0x00f0, 'dj': 0x0111, 'eth': 0x00f0, 'i': 0x0131,
99
+ 'l': 0x0142, 'ng': 0x014b, 'o': 0x00f8, 'ss': 0x00df,
100
+ 'th': 0x00fe,
101
+ # Greek (upper)
102
+ 'Gamma': 0x0393, 'Delta': 0x0394, 'Theta': 0x0398,
103
+ 'Lambda': 0x039b, 'Xi': 0x039E, 'Pi': 0x03a0,
104
+ 'Sigma': 0x03a3, 'Upsilon': 0x03a5, 'Phi': 0x03a6,
105
+ 'Psi': 0x03a8, 'Omega': 0x03a9,
106
+ # Greek (lower)
107
+ 'alpha': 0x03b1, 'beta': 0x03b2, 'gamma': 0x03b3,
108
+ 'delta': 0x03b4, 'epsilon': 0x03b5, 'zeta': 0x03b6,
109
+ 'eta': 0x03b7, 'theta': 0x03b8, 'iota': 0x03b9,
110
+ 'kappa': 0x03ba, 'lambda': 0x03bb, 'mu': 0x03bc,
111
+ 'nu': 0x03bd, 'xi': 0x03be, 'omicron': 0x03bf,
112
+ 'pi': 0x03c0, 'rho': 0x03c1, 'varsigma': 0x03c2,
113
+ 'sigma': 0x03c3, 'tau': 0x03c4, 'upsion': 0x03c5,
114
+ 'varphi': 0x03C6, # φ
115
+ 'phi': 0x03D5, # ϕ
116
+ 'chi': 0x03c7, 'psi': 0x03c8, 'omega': 0x03c9,
117
+ }
118
+
119
+
120
+ def _p_to_match(tex_to_chr: Dict[str, int]) -> Pattern:
121
+ # textsym and textlet both use the same sort of regex pattern.
122
+ keys = r'\\(' + '|'.join(tex_to_chr.keys()) + ')'
123
+ pstr = r'({)?' + keys + r'(\b|(?=_))(?(1)}|(\\(?= )| |{}|)?)'
124
+ return re.compile(pstr)
125
+
126
+
127
+ textlet_pattern = _p_to_match(textlet)
128
+
129
+ textsym = {
130
+ 'P': 0x00b6, 'S': 0x00a7, 'copyright': 0x00a9,
131
+ 'guillemotleft': 0x00ab, 'guillemotright': 0x00bb,
132
+ 'pounds': 0x00a3, 'dag': 0x2020, 'ddag': 0x2021,
133
+ 'div': 0x00f7, 'deg': 0x00b0}
134
+
135
+ # fmt: on
136
+
137
+ textsym_pattern = _p_to_match(textsym)
138
+
139
+
140
+ def _textlet_sub(match: Match) -> str:
141
+ return chr(textlet[match.group(2)])
142
+
143
+
144
+ def _textsym_sub(match: Match) -> str:
145
+ return chr(textsym[match.group(2)])
146
+
147
+
148
+ def texch2UTF(acc: str) -> str:
149
+ """Convert single character TeX accents to UTF-8.
150
+
151
+ Strip non-whitepsace characters from any sequence not recognized (hence
152
+ could return an empty string if there are no word characters in the input
153
+ string).
154
+
155
+ chr(num) will automatically create a UTF8 string for big num
156
+ """
157
+ if acc in accents:
158
+ return chr(accents[acc])
159
+ else:
160
+ return re.sub(r"[^\w]+", "", acc, flags=re.IGNORECASE)
161
+
162
+
163
+ def tex2utf(tex: str, letters: bool = True) -> str:
164
+ r"""Convert some TeX accents and greek symbols to UTF-8 characters.
165
+
166
+ :param tex: Text to filter.
167
+
168
+ :param letters: If False, do not convert greek letters or
169
+ ligatures. Greek symbols can cause problems. Ex. \phi is not
170
+ suppose to look like φ. φ looks like \varphi. See ARXIVNG-1612
171
+
172
+ :returns: string, possibly with some TeX replaced with UTF8
173
+
174
+ """
175
+ # Do dotless i,j -> plain i,j where they are part of an accented i or j
176
+ utf = re.sub(r"/(\\['`\^\"\~\=\.uvH])\{\\([ij])\}", r"\g<1>\{\g<2>\}", tex)
177
+
178
+ # Now work on the Tex sequences, first those with letters only match
179
+ if letters:
180
+ utf = textlet_pattern.sub(_textlet_sub, utf)
181
+
182
+ utf = textsym_pattern.sub(_textsym_sub, utf)
183
+
184
+ utf = re.sub(r"\{\\j\}|\\j\s", "j", utf) # not in Unicode?
185
+
186
+ # reduce {{x}}, {{{x}}}, ... down to {x}
187
+ while re.search(r"\{\{([^\}]*)\}\}", utf):
188
+ utf = re.sub(r"\{\{([^\}]*)\}\}", r"{\g<1>}", utf)
189
+
190
+ # Accents which have a non-letter prefix in TeX, first \'e
191
+ utf = re.sub(r'\\([\'`^"~=.][a-zA-Z])', lambda m: texch2UTF(m.group(1)), utf)
192
+
193
+ # then \'{e} form:
194
+ utf = re.sub(
195
+ r'\\([\'`^"~=.])\{([a-zA-Z])\}',
196
+ lambda m: texch2UTF(m.group(1) + m.group(2)),
197
+ utf,
198
+ )
199
+
200
+ # Accents which have a letter prefix in TeX
201
+ # \u{x} u above (breve), \v{x} v above (caron), \H{x} double accute...
202
+ utf = re.sub(
203
+ r"\\([Hckoruv])\{([a-zA-Z])\}",
204
+ lambda m: texch2UTF(m.group(1) + m.group(2)),
205
+ utf,
206
+ )
207
+
208
+ # Don't do \t{oo} yet,
209
+ utf = re.sub(r"\\t{([^\}])\}", r"\g<1>", utf)
210
+
211
+ # bdc34: commented out in original Perl
212
+ # $utf =~ s/\{(.)\}/$1/g; # remove { } from around {x}
213
+
214
+ return utf