fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.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.
Files changed (140) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cffLib/CFF2ToCFF.py +65 -10
  4. fontTools/cffLib/__init__.py +61 -26
  5. fontTools/cffLib/specializer.py +4 -1
  6. fontTools/cffLib/transforms.py +11 -6
  7. fontTools/config/__init__.py +15 -0
  8. fontTools/cu2qu/cu2qu.c +6567 -5579
  9. fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
  10. fontTools/cu2qu/cu2qu.py +36 -4
  11. fontTools/cu2qu/ufo.py +14 -0
  12. fontTools/designspaceLib/__init__.py +8 -3
  13. fontTools/designspaceLib/statNames.py +14 -7
  14. fontTools/feaLib/ast.py +24 -15
  15. fontTools/feaLib/builder.py +139 -66
  16. fontTools/feaLib/error.py +1 -1
  17. fontTools/feaLib/lexer.c +7038 -7995
  18. fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
  19. fontTools/feaLib/parser.py +75 -40
  20. fontTools/feaLib/variableScalar.py +6 -1
  21. fontTools/fontBuilder.py +50 -44
  22. fontTools/merge/__init__.py +1 -1
  23. fontTools/merge/cmap.py +33 -1
  24. fontTools/merge/tables.py +12 -1
  25. fontTools/misc/bezierTools.c +14913 -17013
  26. fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
  27. fontTools/misc/bezierTools.py +4 -1
  28. fontTools/misc/configTools.py +3 -1
  29. fontTools/misc/enumTools.py +23 -0
  30. fontTools/misc/etree.py +4 -27
  31. fontTools/misc/filesystem/__init__.py +68 -0
  32. fontTools/misc/filesystem/_base.py +134 -0
  33. fontTools/misc/filesystem/_copy.py +45 -0
  34. fontTools/misc/filesystem/_errors.py +54 -0
  35. fontTools/misc/filesystem/_info.py +75 -0
  36. fontTools/misc/filesystem/_osfs.py +164 -0
  37. fontTools/misc/filesystem/_path.py +67 -0
  38. fontTools/misc/filesystem/_subfs.py +92 -0
  39. fontTools/misc/filesystem/_tempfs.py +34 -0
  40. fontTools/misc/filesystem/_tools.py +34 -0
  41. fontTools/misc/filesystem/_walk.py +55 -0
  42. fontTools/misc/filesystem/_zipfs.py +204 -0
  43. fontTools/misc/fixedTools.py +1 -1
  44. fontTools/misc/loggingTools.py +1 -1
  45. fontTools/misc/psCharStrings.py +17 -2
  46. fontTools/misc/sstruct.py +2 -6
  47. fontTools/misc/symfont.py +6 -8
  48. fontTools/misc/testTools.py +5 -1
  49. fontTools/misc/textTools.py +4 -2
  50. fontTools/misc/visitor.py +32 -16
  51. fontTools/misc/xmlWriter.py +44 -8
  52. fontTools/mtiLib/__init__.py +1 -3
  53. fontTools/otlLib/builder.py +402 -155
  54. fontTools/otlLib/optimize/gpos.py +49 -63
  55. fontTools/pens/filterPen.py +218 -26
  56. fontTools/pens/momentsPen.c +5514 -5584
  57. fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
  58. fontTools/pens/pointPen.py +61 -18
  59. fontTools/pens/roundingPen.py +2 -2
  60. fontTools/pens/t2CharStringPen.py +31 -11
  61. fontTools/qu2cu/qu2cu.c +6581 -6168
  62. fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
  63. fontTools/subset/__init__.py +283 -25
  64. fontTools/subset/svg.py +2 -3
  65. fontTools/ttLib/__init__.py +4 -0
  66. fontTools/ttLib/__main__.py +47 -8
  67. fontTools/ttLib/removeOverlaps.py +7 -5
  68. fontTools/ttLib/reorderGlyphs.py +8 -7
  69. fontTools/ttLib/sfnt.py +11 -9
  70. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  71. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  72. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  73. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  74. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  75. fontTools/ttLib/tables/T_S_I__5.py +18 -7
  76. fontTools/ttLib/tables/__init__.py +1 -0
  77. fontTools/ttLib/tables/_a_v_a_r.py +12 -3
  78. fontTools/ttLib/tables/_c_m_a_p.py +20 -7
  79. fontTools/ttLib/tables/_c_v_t.py +3 -2
  80. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  81. fontTools/ttLib/tables/_g_l_y_f.py +45 -21
  82. fontTools/ttLib/tables/_g_v_a_r.py +67 -19
  83. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  84. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  85. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  86. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  87. fontTools/ttLib/tables/_p_o_s_t.py +9 -7
  88. fontTools/ttLib/tables/otBase.py +5 -12
  89. fontTools/ttLib/tables/otConverters.py +5 -2
  90. fontTools/ttLib/tables/otData.py +1 -1
  91. fontTools/ttLib/tables/otTables.py +33 -30
  92. fontTools/ttLib/tables/otTraverse.py +2 -1
  93. fontTools/ttLib/tables/sbixStrike.py +3 -3
  94. fontTools/ttLib/ttFont.py +666 -120
  95. fontTools/ttLib/ttGlyphSet.py +0 -10
  96. fontTools/ttLib/woff2.py +10 -13
  97. fontTools/ttx.py +13 -1
  98. fontTools/ufoLib/__init__.py +300 -202
  99. fontTools/ufoLib/converters.py +103 -30
  100. fontTools/ufoLib/errors.py +8 -0
  101. fontTools/ufoLib/etree.py +1 -1
  102. fontTools/ufoLib/filenames.py +171 -106
  103. fontTools/ufoLib/glifLib.py +303 -205
  104. fontTools/ufoLib/kerning.py +98 -48
  105. fontTools/ufoLib/utils.py +46 -15
  106. fontTools/ufoLib/validators.py +121 -99
  107. fontTools/unicodedata/Blocks.py +35 -20
  108. fontTools/unicodedata/Mirrored.py +446 -0
  109. fontTools/unicodedata/ScriptExtensions.py +63 -37
  110. fontTools/unicodedata/Scripts.py +173 -152
  111. fontTools/unicodedata/__init__.py +10 -2
  112. fontTools/varLib/__init__.py +198 -109
  113. fontTools/varLib/avar/__init__.py +0 -0
  114. fontTools/varLib/avar/__main__.py +72 -0
  115. fontTools/varLib/avar/build.py +79 -0
  116. fontTools/varLib/avar/map.py +108 -0
  117. fontTools/varLib/avar/plan.py +1004 -0
  118. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  119. fontTools/varLib/avarPlanner.py +3 -999
  120. fontTools/varLib/featureVars.py +21 -7
  121. fontTools/varLib/hvar.py +113 -0
  122. fontTools/varLib/instancer/__init__.py +180 -65
  123. fontTools/varLib/interpolatableHelpers.py +3 -0
  124. fontTools/varLib/iup.c +7564 -6903
  125. fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
  126. fontTools/varLib/models.py +17 -2
  127. fontTools/varLib/mutator.py +11 -0
  128. fontTools/varLib/varStore.py +10 -38
  129. fontTools/voltLib/__main__.py +206 -0
  130. fontTools/voltLib/ast.py +4 -0
  131. fontTools/voltLib/parser.py +16 -8
  132. fontTools/voltLib/voltToFea.py +347 -166
  133. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
  134. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
  135. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
  136. fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
  137. {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
  138. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
  139. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
  140. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Mapping, Any
4
+ from collections.abc import Container
5
+
6
+ from fontTools.annotations import KerningNested
7
+
1
8
  """
2
- Conversion functions.
9
+ Functions for converting UFO1 or UFO2 files into UFO3 format.
10
+
11
+ Currently provides functionality for converting kerning rules
12
+ and kerning groups. Conversion is only supported _from_ UFO1
13
+ or UFO2, and _to_ UFO3.
3
14
  """
4
15
 
5
16
  # adapted from the UFO spec
6
17
 
7
18
 
8
- def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
19
+ def convertUFO1OrUFO2KerningToUFO3Kerning(
20
+ kerning: KerningNested, groups: dict[str, list[str]], glyphSet: Container[str] = ()
21
+ ) -> tuple[KerningNested, dict[str, list[str]], dict[str, dict[str, str]]]:
22
+ """Convert kerning data in UFO1 or UFO2 syntax into UFO3 syntax.
23
+
24
+ Args:
25
+ kerning:
26
+ A dictionary containing the kerning rules defined in
27
+ the UFO font, as used in :class:`.UFOReader` objects.
28
+ groups:
29
+ A dictionary containing the groups defined in the UFO
30
+ font, as used in :class:`.UFOReader` objects.
31
+ glyphSet:
32
+ Optional; a set of glyph objects to skip (default: None).
33
+
34
+ Returns:
35
+ 1. A dictionary representing the converted kerning data.
36
+ 2. A copy of the groups dictionary, with all groups renamed to UFO3 syntax.
37
+ 3. A dictionary containing the mapping of old group names to new group names.
38
+
39
+ """
9
40
  # gather known kerning groups based on the prefixes
10
41
  firstReferencedGroups, secondReferencedGroups = findKnownKerningGroups(groups)
11
42
  # Make lists of groups referenced in kerning pairs.
@@ -18,7 +49,7 @@ def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
18
49
  if not second.startswith("public.kern2."):
19
50
  secondReferencedGroups.add(second)
20
51
  # Create new names for these groups.
21
- firstRenamedGroups = {}
52
+ firstRenamedGroups: dict[str, str] = {}
22
53
  for first in firstReferencedGroups:
23
54
  # Make a list of existing group names.
24
55
  existingGroupNames = list(groups.keys()) + list(firstRenamedGroups.keys())
@@ -30,7 +61,7 @@ def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
30
61
  newName = makeUniqueGroupName(newName, existingGroupNames)
31
62
  # Store for use later.
32
63
  firstRenamedGroups[first] = newName
33
- secondRenamedGroups = {}
64
+ secondRenamedGroups: dict[str, str] = {}
34
65
  for second in secondReferencedGroups:
35
66
  # Make a list of existing group names.
36
67
  existingGroupNames = list(groups.keys()) + list(secondRenamedGroups.keys())
@@ -62,36 +93,55 @@ def convertUFO1OrUFO2KerningToUFO3Kerning(kerning, groups, glyphSet=()):
62
93
  return newKerning, groups, dict(side1=firstRenamedGroups, side2=secondRenamedGroups)
63
94
 
64
95
 
65
- def findKnownKerningGroups(groups):
66
- """
67
- This will find kerning groups with known prefixes.
68
- In some cases not all kerning groups will be referenced
69
- by the kerning pairs. The algorithm for locating groups
70
- in convertUFO1OrUFO2KerningToUFO3Kerning will miss these
71
- unreferenced groups. By scanning for known prefixes
96
+ def findKnownKerningGroups(groups: Mapping[str, Any]) -> tuple[set[str], set[str]]:
97
+ """Find all kerning groups in a UFO1 or UFO2 font that use known prefixes.
98
+
99
+ In some cases, not all kerning groups will be referenced
100
+ by the kerning pairs in a UFO. The algorithm for locating
101
+ groups in :func:`convertUFO1OrUFO2KerningToUFO3Kerning` will
102
+ miss these unreferenced groups. By scanning for known prefixes,
72
103
  this function will catch all of the prefixed groups.
73
104
 
74
- These are the prefixes and sides that are handled:
105
+ The prefixes and sides by this function are:
106
+
75
107
  @MMK_L_ - side 1
76
108
  @MMK_R_ - side 2
77
109
 
78
- >>> testGroups = {
79
- ... "@MMK_L_1" : None,
80
- ... "@MMK_L_2" : None,
81
- ... "@MMK_L_3" : None,
82
- ... "@MMK_R_1" : None,
83
- ... "@MMK_R_2" : None,
84
- ... "@MMK_R_3" : None,
85
- ... "@MMK_l_1" : None,
86
- ... "@MMK_r_1" : None,
87
- ... "@MMK_X_1" : None,
88
- ... "foo" : None,
89
- ... }
90
- >>> first, second = findKnownKerningGroups(testGroups)
91
- >>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
92
- True
93
- >>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
94
- True
110
+ as defined in the UFO1 specification.
111
+
112
+ Args:
113
+ groups:
114
+ A dictionary containing the groups defined in the UFO
115
+ font, as read by :class:`.UFOReader`.
116
+
117
+ Returns:
118
+ Two sets; the first containing the names of all
119
+ first-side kerning groups identified in the ``groups``
120
+ dictionary, and the second containing the names of all
121
+ second-side kerning groups identified.
122
+
123
+ "First-side" and "second-side" are with respect to the
124
+ writing direction of the script.
125
+
126
+ Example::
127
+
128
+ >>> testGroups = {
129
+ ... "@MMK_L_1" : None,
130
+ ... "@MMK_L_2" : None,
131
+ ... "@MMK_L_3" : None,
132
+ ... "@MMK_R_1" : None,
133
+ ... "@MMK_R_2" : None,
134
+ ... "@MMK_R_3" : None,
135
+ ... "@MMK_l_1" : None,
136
+ ... "@MMK_r_1" : None,
137
+ ... "@MMK_X_1" : None,
138
+ ... "foo" : None,
139
+ ... }
140
+ >>> first, second = findKnownKerningGroups(testGroups)
141
+ >>> sorted(first) == ['@MMK_L_1', '@MMK_L_2', '@MMK_L_3']
142
+ True
143
+ >>> sorted(second) == ['@MMK_R_1', '@MMK_R_2', '@MMK_R_3']
144
+ True
95
145
  """
96
146
  knownFirstGroupPrefixes = ["@MMK_L_"]
97
147
  knownSecondGroupPrefixes = ["@MMK_R_"]
@@ -109,7 +159,28 @@ def findKnownKerningGroups(groups):
109
159
  return firstGroups, secondGroups
110
160
 
111
161
 
112
- def makeUniqueGroupName(name, groupNames, counter=0):
162
+ def makeUniqueGroupName(name: str, groupNames: list[str], counter: int = 0) -> str:
163
+ """Make a kerning group name that will be unique within the set of group names.
164
+
165
+ If the requested kerning group name already exists within the set, this
166
+ will return a new name by adding an incremented counter to the end
167
+ of the requested name.
168
+
169
+ Args:
170
+ name:
171
+ The requested kerning group name.
172
+ groupNames:
173
+ A list of the existing kerning group names.
174
+ counter:
175
+ Optional; a counter of group names already seen (default: 0). If
176
+ :attr:`.counter` is not provided, the function will recurse,
177
+ incrementing the value of :attr:`.counter` until it finds the
178
+ first unused ``name+counter`` combination, and return that result.
179
+
180
+ Returns:
181
+ A unique kerning group name composed of the requested name suffixed
182
+ by the smallest available integer counter.
183
+ """
113
184
  # Add a number to the name if the counter is higher than zero.
114
185
  newName = name
115
186
  if counter > 0:
@@ -123,6 +194,8 @@ def makeUniqueGroupName(name, groupNames, counter=0):
123
194
 
124
195
  def test():
125
196
  """
197
+ Tests for :func:`.convertUFO1OrUFO2KerningToUFO3Kerning`.
198
+
126
199
  No known prefixes.
127
200
 
128
201
  >>> testKerning = {
@@ -10,6 +10,14 @@ class UnsupportedUFOFormat(UFOLibError):
10
10
 
11
11
 
12
12
  class GlifLibError(UFOLibError):
13
+ """An error raised by glifLib.
14
+
15
+ This class is a loose backport of PEP 678, adding a :attr:`.note`
16
+ attribute that can hold additional context for errors encountered.
17
+
18
+ It will be maintained until only Python 3.11-and-later are supported.
19
+ """
20
+
13
21
  def _add_note(self, note: str) -> None:
14
22
  # Loose backport of PEP 678 until we only support Python 3.11+, used for
15
23
  # adding additional context to errors.
fontTools/ufoLib/etree.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """DEPRECATED - This module is kept here only as a backward compatibility shim
2
- for the old ufoLib.etree module, which was moved to fontTools.misc.etree.
2
+ for the old ufoLib.etree module, which was moved to :mod:`fontTools.misc.etree`.
3
3
  Please use the latter instead.
4
4
  """
5
5
 
@@ -1,6 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
1
5
  """
2
- User name to file name conversion.
3
- This was taken from the UFO 3 spec.
6
+ Convert user-provided internal UFO names to spec-compliant filenames.
7
+
8
+ This module implements the algorithm for converting between a "user name" -
9
+ something that a user can choose arbitrarily inside a font editor - and a file
10
+ name suitable for use in a wide range of operating systems and filesystems.
11
+
12
+ The `UFO 3 specification <http://unifiedfontobject.org/versions/ufo3/conventions/>`_
13
+ provides an example of an algorithm for such conversion, which avoids illegal
14
+ characters, reserved file names, ambiguity between upper- and lower-case
15
+ characters, and clashes with existing files.
16
+
17
+ This code was originally copied from
18
+ `ufoLib <https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py>`_
19
+ by Tal Leming and is copyright (c) 2005-2016, The RoboFab Developers:
20
+
21
+ - Erik van Blokland
22
+ - Tal Leming
23
+ - Just van Rossum
4
24
  """
5
25
 
6
26
  # Restrictions are taken mostly from
@@ -11,7 +31,7 @@ This was taken from the UFO 3 spec.
11
31
  # inclusive.
12
32
  # 3. Various characters that (mostly) Windows and POSIX-y filesystems don't
13
33
  # allow, plus "(" and ")", as per the specification.
14
- illegalCharacters = {
34
+ illegalCharacters: set[str] = {
15
35
  "\x00",
16
36
  "\x01",
17
37
  "\x02",
@@ -60,7 +80,7 @@ illegalCharacters = {
60
80
  "|",
61
81
  "\x7f",
62
82
  }
63
- reservedFileNames = {
83
+ reservedFileNames: set[str] = {
64
84
  "aux",
65
85
  "clock$",
66
86
  "com1",
@@ -85,61 +105,79 @@ reservedFileNames = {
85
105
  "nul",
86
106
  "prn",
87
107
  }
88
- maxFileNameLength = 255
108
+ maxFileNameLength: int = 255
89
109
 
90
110
 
91
111
  class NameTranslationError(Exception):
92
112
  pass
93
113
 
94
114
 
95
- def userNameToFileName(userName: str, existing=(), prefix="", suffix=""):
96
- """
97
- `existing` should be a set-like object.
98
-
99
- >>> userNameToFileName("a") == "a"
100
- True
101
- >>> userNameToFileName("A") == "A_"
102
- True
103
- >>> userNameToFileName("AE") == "A_E_"
104
- True
105
- >>> userNameToFileName("Ae") == "A_e"
106
- True
107
- >>> userNameToFileName("ae") == "ae"
108
- True
109
- >>> userNameToFileName("aE") == "aE_"
110
- True
111
- >>> userNameToFileName("a.alt") == "a.alt"
112
- True
113
- >>> userNameToFileName("A.alt") == "A_.alt"
114
- True
115
- >>> userNameToFileName("A.Alt") == "A_.A_lt"
116
- True
117
- >>> userNameToFileName("A.aLt") == "A_.aL_t"
118
- True
119
- >>> userNameToFileName(u"A.alT") == "A_.alT_"
120
- True
121
- >>> userNameToFileName("T_H") == "T__H_"
122
- True
123
- >>> userNameToFileName("T_h") == "T__h"
124
- True
125
- >>> userNameToFileName("t_h") == "t_h"
126
- True
127
- >>> userNameToFileName("F_F_I") == "F__F__I_"
128
- True
129
- >>> userNameToFileName("f_f_i") == "f_f_i"
130
- True
131
- >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
132
- True
133
- >>> userNameToFileName(".notdef") == "_notdef"
134
- True
135
- >>> userNameToFileName("con") == "_con"
136
- True
137
- >>> userNameToFileName("CON") == "C_O_N_"
138
- True
139
- >>> userNameToFileName("con.alt") == "_con.alt"
140
- True
141
- >>> userNameToFileName("alt.con") == "alt._con"
142
- True
115
+ def userNameToFileName(
116
+ userName: str, existing: Iterable[str] = (), prefix: str = "", suffix: str = ""
117
+ ) -> str:
118
+ """Converts from a user name to a file name.
119
+
120
+ Takes care to avoid illegal characters, reserved file names, ambiguity between
121
+ upper- and lower-case characters, and clashes with existing files.
122
+
123
+ Args:
124
+ userName (str): The input file name.
125
+ existing: A case-insensitive list of all existing file names.
126
+ prefix: Prefix to be prepended to the file name.
127
+ suffix: Suffix to be appended to the file name.
128
+
129
+ Returns:
130
+ A suitable filename.
131
+
132
+ Raises:
133
+ NameTranslationError: If no suitable name could be generated.
134
+
135
+ Examples::
136
+
137
+ >>> userNameToFileName("a") == "a"
138
+ True
139
+ >>> userNameToFileName("A") == "A_"
140
+ True
141
+ >>> userNameToFileName("AE") == "A_E_"
142
+ True
143
+ >>> userNameToFileName("Ae") == "A_e"
144
+ True
145
+ >>> userNameToFileName("ae") == "ae"
146
+ True
147
+ >>> userNameToFileName("aE") == "aE_"
148
+ True
149
+ >>> userNameToFileName("a.alt") == "a.alt"
150
+ True
151
+ >>> userNameToFileName("A.alt") == "A_.alt"
152
+ True
153
+ >>> userNameToFileName("A.Alt") == "A_.A_lt"
154
+ True
155
+ >>> userNameToFileName("A.aLt") == "A_.aL_t"
156
+ True
157
+ >>> userNameToFileName(u"A.alT") == "A_.alT_"
158
+ True
159
+ >>> userNameToFileName("T_H") == "T__H_"
160
+ True
161
+ >>> userNameToFileName("T_h") == "T__h"
162
+ True
163
+ >>> userNameToFileName("t_h") == "t_h"
164
+ True
165
+ >>> userNameToFileName("F_F_I") == "F__F__I_"
166
+ True
167
+ >>> userNameToFileName("f_f_i") == "f_f_i"
168
+ True
169
+ >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash"
170
+ True
171
+ >>> userNameToFileName(".notdef") == "_notdef"
172
+ True
173
+ >>> userNameToFileName("con") == "_con"
174
+ True
175
+ >>> userNameToFileName("CON") == "C_O_N_"
176
+ True
177
+ >>> userNameToFileName("con.alt") == "_con.alt"
178
+ True
179
+ >>> userNameToFileName("alt.con") == "alt._con"
180
+ True
143
181
  """
144
182
  # the incoming name must be a string
145
183
  if not isinstance(userName, str):
@@ -180,34 +218,45 @@ def userNameToFileName(userName: str, existing=(), prefix="", suffix=""):
180
218
  return fullName
181
219
 
182
220
 
183
- def handleClash1(userName, existing=[], prefix="", suffix=""):
184
- """
185
- existing should be a case-insensitive list
186
- of all existing file names.
187
-
188
- >>> prefix = ("0" * 5) + "."
189
- >>> suffix = "." + ("0" * 10)
190
- >>> existing = ["a" * 5]
191
-
192
- >>> e = list(existing)
193
- >>> handleClash1(userName="A" * 5, existing=e,
194
- ... prefix=prefix, suffix=suffix) == (
195
- ... '00000.AAAAA000000000000001.0000000000')
196
- True
197
-
198
- >>> e = list(existing)
199
- >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
200
- >>> handleClash1(userName="A" * 5, existing=e,
201
- ... prefix=prefix, suffix=suffix) == (
202
- ... '00000.AAAAA000000000000002.0000000000')
203
- True
204
-
205
- >>> e = list(existing)
206
- >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
207
- >>> handleClash1(userName="A" * 5, existing=e,
208
- ... prefix=prefix, suffix=suffix) == (
209
- ... '00000.AAAAA000000000000001.0000000000')
210
- True
221
+ def handleClash1(
222
+ userName: str, existing: Iterable[str] = [], prefix: str = "", suffix: str = ""
223
+ ) -> str:
224
+ """A helper function that resolves collisions with existing names when choosing a filename.
225
+
226
+ This function attempts to append an unused integer counter to the filename.
227
+
228
+ Args:
229
+ userName (str): The input file name.
230
+ existing: A case-insensitive list of all existing file names.
231
+ prefix: Prefix to be prepended to the file name.
232
+ suffix: Suffix to be appended to the file name.
233
+
234
+ Returns:
235
+ A suitable filename.
236
+
237
+ >>> prefix = ("0" * 5) + "."
238
+ >>> suffix = "." + ("0" * 10)
239
+ >>> existing = ["a" * 5]
240
+
241
+ >>> e = list(existing)
242
+ >>> handleClash1(userName="A" * 5, existing=e,
243
+ ... prefix=prefix, suffix=suffix) == (
244
+ ... '00000.AAAAA000000000000001.0000000000')
245
+ True
246
+
247
+ >>> e = list(existing)
248
+ >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
249
+ >>> handleClash1(userName="A" * 5, existing=e,
250
+ ... prefix=prefix, suffix=suffix) == (
251
+ ... '00000.AAAAA000000000000002.0000000000')
252
+ True
253
+
254
+ >>> e = list(existing)
255
+ >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
256
+ >>> handleClash1(userName="A" * 5, existing=e,
257
+ ... prefix=prefix, suffix=suffix) == (
258
+ ... '00000.AAAAA000000000000001.0000000000')
259
+ True
211
260
  """
212
261
  # if the prefix length + user name length + suffix length + 15 is at
213
262
  # or past the maximum length, silce 15 characters off of the user name
@@ -237,31 +286,47 @@ def handleClash1(userName, existing=[], prefix="", suffix=""):
237
286
  return finalName
238
287
 
239
288
 
240
- def handleClash2(existing=[], prefix="", suffix=""):
241
- """
242
- existing should be a case-insensitive list
243
- of all existing file names.
244
-
245
- >>> prefix = ("0" * 5) + "."
246
- >>> suffix = "." + ("0" * 10)
247
- >>> existing = [prefix + str(i) + suffix for i in range(100)]
248
-
249
- >>> e = list(existing)
250
- >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
251
- ... '00000.100.0000000000')
252
- True
253
-
254
- >>> e = list(existing)
255
- >>> e.remove(prefix + "1" + suffix)
256
- >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
257
- ... '00000.1.0000000000')
258
- True
259
-
260
- >>> e = list(existing)
261
- >>> e.remove(prefix + "2" + suffix)
262
- >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
263
- ... '00000.2.0000000000')
264
- True
289
+ def handleClash2(
290
+ existing: Iterable[str] = [], prefix: str = "", suffix: str = ""
291
+ ) -> str:
292
+ """A helper function that resolves collisions with existing names when choosing a filename.
293
+
294
+ This function is a fallback to :func:`handleClash1`. It attempts to append an unused integer counter to the filename.
295
+
296
+ Args:
297
+ userName (str): The input file name.
298
+ existing: A case-insensitive list of all existing file names.
299
+ prefix: Prefix to be prepended to the file name.
300
+ suffix: Suffix to be appended to the file name.
301
+
302
+ Returns:
303
+ A suitable filename.
304
+
305
+ Raises:
306
+ NameTranslationError: If no suitable name could be generated.
307
+
308
+ Examples::
309
+
310
+ >>> prefix = ("0" * 5) + "."
311
+ >>> suffix = "." + ("0" * 10)
312
+ >>> existing = [prefix + str(i) + suffix for i in range(100)]
313
+
314
+ >>> e = list(existing)
315
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
316
+ ... '00000.100.0000000000')
317
+ True
318
+
319
+ >>> e = list(existing)
320
+ >>> e.remove(prefix + "1" + suffix)
321
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
322
+ ... '00000.1.0000000000')
323
+ True
324
+
325
+ >>> e = list(existing)
326
+ >>> e.remove(prefix + "2" + suffix)
327
+ >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == (
328
+ ... '00000.2.0000000000')
329
+ True
265
330
  """
266
331
  # calculate the longest possible string
267
332
  maxLength = maxFileNameLength - len(prefix) - len(suffix)