fonttools 4.60.2__cp311-cp311-win32.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 (353) hide show
  1. fontTools/__init__.py +8 -0
  2. fontTools/__main__.py +35 -0
  3. fontTools/afmLib.py +439 -0
  4. fontTools/agl.py +5233 -0
  5. fontTools/annotations.py +30 -0
  6. fontTools/cffLib/CFF2ToCFF.py +258 -0
  7. fontTools/cffLib/CFFToCFF2.py +305 -0
  8. fontTools/cffLib/__init__.py +3694 -0
  9. fontTools/cffLib/specializer.py +927 -0
  10. fontTools/cffLib/transforms.py +495 -0
  11. fontTools/cffLib/width.py +210 -0
  12. fontTools/colorLib/__init__.py +0 -0
  13. fontTools/colorLib/builder.py +664 -0
  14. fontTools/colorLib/errors.py +2 -0
  15. fontTools/colorLib/geometry.py +143 -0
  16. fontTools/colorLib/table_builder.py +223 -0
  17. fontTools/colorLib/unbuilder.py +81 -0
  18. fontTools/config/__init__.py +90 -0
  19. fontTools/cu2qu/__init__.py +15 -0
  20. fontTools/cu2qu/__main__.py +6 -0
  21. fontTools/cu2qu/benchmark.py +54 -0
  22. fontTools/cu2qu/cli.py +198 -0
  23. fontTools/cu2qu/cu2qu.c +15817 -0
  24. fontTools/cu2qu/cu2qu.cp311-win32.pyd +0 -0
  25. fontTools/cu2qu/cu2qu.py +563 -0
  26. fontTools/cu2qu/errors.py +77 -0
  27. fontTools/cu2qu/ufo.py +363 -0
  28. fontTools/designspaceLib/__init__.py +3343 -0
  29. fontTools/designspaceLib/__main__.py +6 -0
  30. fontTools/designspaceLib/split.py +475 -0
  31. fontTools/designspaceLib/statNames.py +260 -0
  32. fontTools/designspaceLib/types.py +147 -0
  33. fontTools/encodings/MacRoman.py +258 -0
  34. fontTools/encodings/StandardEncoding.py +258 -0
  35. fontTools/encodings/__init__.py +1 -0
  36. fontTools/encodings/codecs.py +135 -0
  37. fontTools/feaLib/__init__.py +4 -0
  38. fontTools/feaLib/__main__.py +78 -0
  39. fontTools/feaLib/ast.py +2143 -0
  40. fontTools/feaLib/builder.py +1814 -0
  41. fontTools/feaLib/error.py +22 -0
  42. fontTools/feaLib/lexer.c +17029 -0
  43. fontTools/feaLib/lexer.cp311-win32.pyd +0 -0
  44. fontTools/feaLib/lexer.py +287 -0
  45. fontTools/feaLib/location.py +12 -0
  46. fontTools/feaLib/lookupDebugInfo.py +12 -0
  47. fontTools/feaLib/parser.py +2394 -0
  48. fontTools/feaLib/variableScalar.py +118 -0
  49. fontTools/fontBuilder.py +1014 -0
  50. fontTools/help.py +36 -0
  51. fontTools/merge/__init__.py +248 -0
  52. fontTools/merge/__main__.py +6 -0
  53. fontTools/merge/base.py +81 -0
  54. fontTools/merge/cmap.py +173 -0
  55. fontTools/merge/layout.py +526 -0
  56. fontTools/merge/options.py +85 -0
  57. fontTools/merge/tables.py +352 -0
  58. fontTools/merge/unicode.py +78 -0
  59. fontTools/merge/util.py +143 -0
  60. fontTools/misc/__init__.py +1 -0
  61. fontTools/misc/arrayTools.py +424 -0
  62. fontTools/misc/bezierTools.c +39731 -0
  63. fontTools/misc/bezierTools.cp311-win32.pyd +0 -0
  64. fontTools/misc/bezierTools.py +1500 -0
  65. fontTools/misc/classifyTools.py +170 -0
  66. fontTools/misc/cliTools.py +53 -0
  67. fontTools/misc/configTools.py +349 -0
  68. fontTools/misc/cython.py +27 -0
  69. fontTools/misc/dictTools.py +83 -0
  70. fontTools/misc/eexec.py +119 -0
  71. fontTools/misc/encodingTools.py +72 -0
  72. fontTools/misc/enumTools.py +23 -0
  73. fontTools/misc/etree.py +456 -0
  74. fontTools/misc/filenames.py +245 -0
  75. fontTools/misc/filesystem/__init__.py +68 -0
  76. fontTools/misc/filesystem/_base.py +134 -0
  77. fontTools/misc/filesystem/_copy.py +45 -0
  78. fontTools/misc/filesystem/_errors.py +54 -0
  79. fontTools/misc/filesystem/_info.py +75 -0
  80. fontTools/misc/filesystem/_osfs.py +164 -0
  81. fontTools/misc/filesystem/_path.py +67 -0
  82. fontTools/misc/filesystem/_subfs.py +92 -0
  83. fontTools/misc/filesystem/_tempfs.py +34 -0
  84. fontTools/misc/filesystem/_tools.py +34 -0
  85. fontTools/misc/filesystem/_walk.py +55 -0
  86. fontTools/misc/filesystem/_zipfs.py +204 -0
  87. fontTools/misc/fixedTools.py +253 -0
  88. fontTools/misc/intTools.py +25 -0
  89. fontTools/misc/iterTools.py +12 -0
  90. fontTools/misc/lazyTools.py +42 -0
  91. fontTools/misc/loggingTools.py +543 -0
  92. fontTools/misc/macCreatorType.py +56 -0
  93. fontTools/misc/macRes.py +261 -0
  94. fontTools/misc/plistlib/__init__.py +681 -0
  95. fontTools/misc/plistlib/py.typed +0 -0
  96. fontTools/misc/psCharStrings.py +1511 -0
  97. fontTools/misc/psLib.py +398 -0
  98. fontTools/misc/psOperators.py +572 -0
  99. fontTools/misc/py23.py +96 -0
  100. fontTools/misc/roundTools.py +110 -0
  101. fontTools/misc/sstruct.py +227 -0
  102. fontTools/misc/symfont.py +242 -0
  103. fontTools/misc/testTools.py +233 -0
  104. fontTools/misc/textTools.py +156 -0
  105. fontTools/misc/timeTools.py +88 -0
  106. fontTools/misc/transform.py +516 -0
  107. fontTools/misc/treeTools.py +45 -0
  108. fontTools/misc/vector.py +147 -0
  109. fontTools/misc/visitor.py +158 -0
  110. fontTools/misc/xmlReader.py +188 -0
  111. fontTools/misc/xmlWriter.py +231 -0
  112. fontTools/mtiLib/__init__.py +1400 -0
  113. fontTools/mtiLib/__main__.py +5 -0
  114. fontTools/otlLib/__init__.py +1 -0
  115. fontTools/otlLib/builder.py +3465 -0
  116. fontTools/otlLib/error.py +11 -0
  117. fontTools/otlLib/maxContextCalc.py +96 -0
  118. fontTools/otlLib/optimize/__init__.py +53 -0
  119. fontTools/otlLib/optimize/__main__.py +6 -0
  120. fontTools/otlLib/optimize/gpos.py +439 -0
  121. fontTools/pens/__init__.py +1 -0
  122. fontTools/pens/areaPen.py +52 -0
  123. fontTools/pens/basePen.py +475 -0
  124. fontTools/pens/boundsPen.py +98 -0
  125. fontTools/pens/cairoPen.py +26 -0
  126. fontTools/pens/cocoaPen.py +26 -0
  127. fontTools/pens/cu2quPen.py +325 -0
  128. fontTools/pens/explicitClosingLinePen.py +101 -0
  129. fontTools/pens/filterPen.py +433 -0
  130. fontTools/pens/freetypePen.py +462 -0
  131. fontTools/pens/hashPointPen.py +89 -0
  132. fontTools/pens/momentsPen.c +13378 -0
  133. fontTools/pens/momentsPen.cp311-win32.pyd +0 -0
  134. fontTools/pens/momentsPen.py +879 -0
  135. fontTools/pens/perimeterPen.py +69 -0
  136. fontTools/pens/pointInsidePen.py +192 -0
  137. fontTools/pens/pointPen.py +643 -0
  138. fontTools/pens/qtPen.py +29 -0
  139. fontTools/pens/qu2cuPen.py +105 -0
  140. fontTools/pens/quartzPen.py +43 -0
  141. fontTools/pens/recordingPen.py +335 -0
  142. fontTools/pens/reportLabPen.py +79 -0
  143. fontTools/pens/reverseContourPen.py +96 -0
  144. fontTools/pens/roundingPen.py +130 -0
  145. fontTools/pens/statisticsPen.py +312 -0
  146. fontTools/pens/svgPathPen.py +310 -0
  147. fontTools/pens/t2CharStringPen.py +88 -0
  148. fontTools/pens/teePen.py +55 -0
  149. fontTools/pens/transformPen.py +115 -0
  150. fontTools/pens/ttGlyphPen.py +335 -0
  151. fontTools/pens/wxPen.py +29 -0
  152. fontTools/qu2cu/__init__.py +15 -0
  153. fontTools/qu2cu/__main__.py +7 -0
  154. fontTools/qu2cu/benchmark.py +56 -0
  155. fontTools/qu2cu/cli.py +125 -0
  156. fontTools/qu2cu/qu2cu.c +16682 -0
  157. fontTools/qu2cu/qu2cu.cp311-win32.pyd +0 -0
  158. fontTools/qu2cu/qu2cu.py +405 -0
  159. fontTools/subset/__init__.py +4096 -0
  160. fontTools/subset/__main__.py +6 -0
  161. fontTools/subset/cff.py +184 -0
  162. fontTools/subset/svg.py +253 -0
  163. fontTools/subset/util.py +25 -0
  164. fontTools/svgLib/__init__.py +3 -0
  165. fontTools/svgLib/path/__init__.py +65 -0
  166. fontTools/svgLib/path/arc.py +154 -0
  167. fontTools/svgLib/path/parser.py +322 -0
  168. fontTools/svgLib/path/shapes.py +183 -0
  169. fontTools/t1Lib/__init__.py +648 -0
  170. fontTools/tfmLib.py +460 -0
  171. fontTools/ttLib/__init__.py +30 -0
  172. fontTools/ttLib/__main__.py +148 -0
  173. fontTools/ttLib/macUtils.py +54 -0
  174. fontTools/ttLib/removeOverlaps.py +395 -0
  175. fontTools/ttLib/reorderGlyphs.py +285 -0
  176. fontTools/ttLib/scaleUpem.py +436 -0
  177. fontTools/ttLib/sfnt.py +661 -0
  178. fontTools/ttLib/standardGlyphOrder.py +271 -0
  179. fontTools/ttLib/tables/B_A_S_E_.py +14 -0
  180. fontTools/ttLib/tables/BitmapGlyphMetrics.py +64 -0
  181. fontTools/ttLib/tables/C_B_D_T_.py +113 -0
  182. fontTools/ttLib/tables/C_B_L_C_.py +19 -0
  183. fontTools/ttLib/tables/C_F_F_.py +61 -0
  184. fontTools/ttLib/tables/C_F_F__2.py +26 -0
  185. fontTools/ttLib/tables/C_O_L_R_.py +165 -0
  186. fontTools/ttLib/tables/C_P_A_L_.py +305 -0
  187. fontTools/ttLib/tables/D_S_I_G_.py +158 -0
  188. fontTools/ttLib/tables/D__e_b_g.py +35 -0
  189. fontTools/ttLib/tables/DefaultTable.py +49 -0
  190. fontTools/ttLib/tables/E_B_D_T_.py +835 -0
  191. fontTools/ttLib/tables/E_B_L_C_.py +718 -0
  192. fontTools/ttLib/tables/F_F_T_M_.py +52 -0
  193. fontTools/ttLib/tables/F__e_a_t.py +149 -0
  194. fontTools/ttLib/tables/G_D_E_F_.py +13 -0
  195. fontTools/ttLib/tables/G_M_A_P_.py +148 -0
  196. fontTools/ttLib/tables/G_P_K_G_.py +133 -0
  197. fontTools/ttLib/tables/G_P_O_S_.py +14 -0
  198. fontTools/ttLib/tables/G_S_U_B_.py +13 -0
  199. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  200. fontTools/ttLib/tables/G__l_a_t.py +235 -0
  201. fontTools/ttLib/tables/G__l_o_c.py +85 -0
  202. fontTools/ttLib/tables/H_V_A_R_.py +13 -0
  203. fontTools/ttLib/tables/J_S_T_F_.py +13 -0
  204. fontTools/ttLib/tables/L_T_S_H_.py +58 -0
  205. fontTools/ttLib/tables/M_A_T_H_.py +13 -0
  206. fontTools/ttLib/tables/M_E_T_A_.py +352 -0
  207. fontTools/ttLib/tables/M_V_A_R_.py +13 -0
  208. fontTools/ttLib/tables/O_S_2f_2.py +752 -0
  209. fontTools/ttLib/tables/S_I_N_G_.py +99 -0
  210. fontTools/ttLib/tables/S_T_A_T_.py +15 -0
  211. fontTools/ttLib/tables/S_V_G_.py +223 -0
  212. fontTools/ttLib/tables/S__i_l_f.py +1040 -0
  213. fontTools/ttLib/tables/S__i_l_l.py +92 -0
  214. fontTools/ttLib/tables/T_S_I_B_.py +13 -0
  215. fontTools/ttLib/tables/T_S_I_C_.py +14 -0
  216. fontTools/ttLib/tables/T_S_I_D_.py +13 -0
  217. fontTools/ttLib/tables/T_S_I_J_.py +13 -0
  218. fontTools/ttLib/tables/T_S_I_P_.py +13 -0
  219. fontTools/ttLib/tables/T_S_I_S_.py +13 -0
  220. fontTools/ttLib/tables/T_S_I_V_.py +26 -0
  221. fontTools/ttLib/tables/T_S_I__0.py +70 -0
  222. fontTools/ttLib/tables/T_S_I__1.py +163 -0
  223. fontTools/ttLib/tables/T_S_I__2.py +17 -0
  224. fontTools/ttLib/tables/T_S_I__3.py +22 -0
  225. fontTools/ttLib/tables/T_S_I__5.py +60 -0
  226. fontTools/ttLib/tables/T_T_F_A_.py +14 -0
  227. fontTools/ttLib/tables/TupleVariation.py +884 -0
  228. fontTools/ttLib/tables/V_A_R_C_.py +12 -0
  229. fontTools/ttLib/tables/V_D_M_X_.py +249 -0
  230. fontTools/ttLib/tables/V_O_R_G_.py +165 -0
  231. fontTools/ttLib/tables/V_V_A_R_.py +13 -0
  232. fontTools/ttLib/tables/__init__.py +98 -0
  233. fontTools/ttLib/tables/_a_n_k_r.py +15 -0
  234. fontTools/ttLib/tables/_a_v_a_r.py +193 -0
  235. fontTools/ttLib/tables/_b_s_l_n.py +15 -0
  236. fontTools/ttLib/tables/_c_i_d_g.py +24 -0
  237. fontTools/ttLib/tables/_c_m_a_p.py +1591 -0
  238. fontTools/ttLib/tables/_c_v_a_r.py +94 -0
  239. fontTools/ttLib/tables/_c_v_t.py +56 -0
  240. fontTools/ttLib/tables/_f_e_a_t.py +15 -0
  241. fontTools/ttLib/tables/_f_p_g_m.py +62 -0
  242. fontTools/ttLib/tables/_f_v_a_r.py +261 -0
  243. fontTools/ttLib/tables/_g_a_s_p.py +63 -0
  244. fontTools/ttLib/tables/_g_c_i_d.py +13 -0
  245. fontTools/ttLib/tables/_g_l_y_f.py +2311 -0
  246. fontTools/ttLib/tables/_g_v_a_r.py +340 -0
  247. fontTools/ttLib/tables/_h_d_m_x.py +127 -0
  248. fontTools/ttLib/tables/_h_e_a_d.py +130 -0
  249. fontTools/ttLib/tables/_h_h_e_a.py +147 -0
  250. fontTools/ttLib/tables/_h_m_t_x.py +164 -0
  251. fontTools/ttLib/tables/_k_e_r_n.py +289 -0
  252. fontTools/ttLib/tables/_l_c_a_r.py +13 -0
  253. fontTools/ttLib/tables/_l_o_c_a.py +70 -0
  254. fontTools/ttLib/tables/_l_t_a_g.py +72 -0
  255. fontTools/ttLib/tables/_m_a_x_p.py +147 -0
  256. fontTools/ttLib/tables/_m_e_t_a.py +112 -0
  257. fontTools/ttLib/tables/_m_o_r_t.py +14 -0
  258. fontTools/ttLib/tables/_m_o_r_x.py +15 -0
  259. fontTools/ttLib/tables/_n_a_m_e.py +1242 -0
  260. fontTools/ttLib/tables/_o_p_b_d.py +14 -0
  261. fontTools/ttLib/tables/_p_o_s_t.py +319 -0
  262. fontTools/ttLib/tables/_p_r_e_p.py +16 -0
  263. fontTools/ttLib/tables/_p_r_o_p.py +12 -0
  264. fontTools/ttLib/tables/_s_b_i_x.py +129 -0
  265. fontTools/ttLib/tables/_t_r_a_k.py +332 -0
  266. fontTools/ttLib/tables/_v_h_e_a.py +139 -0
  267. fontTools/ttLib/tables/_v_m_t_x.py +19 -0
  268. fontTools/ttLib/tables/asciiTable.py +20 -0
  269. fontTools/ttLib/tables/grUtils.py +92 -0
  270. fontTools/ttLib/tables/otBase.py +1458 -0
  271. fontTools/ttLib/tables/otConverters.py +2068 -0
  272. fontTools/ttLib/tables/otData.py +6400 -0
  273. fontTools/ttLib/tables/otTables.py +2703 -0
  274. fontTools/ttLib/tables/otTraverse.py +163 -0
  275. fontTools/ttLib/tables/sbixGlyph.py +149 -0
  276. fontTools/ttLib/tables/sbixStrike.py +177 -0
  277. fontTools/ttLib/tables/table_API_readme.txt +91 -0
  278. fontTools/ttLib/tables/ttProgram.py +594 -0
  279. fontTools/ttLib/ttCollection.py +125 -0
  280. fontTools/ttLib/ttFont.py +1148 -0
  281. fontTools/ttLib/ttGlyphSet.py +490 -0
  282. fontTools/ttLib/ttVisitor.py +32 -0
  283. fontTools/ttLib/woff2.py +1680 -0
  284. fontTools/ttx.py +479 -0
  285. fontTools/ufoLib/__init__.py +2575 -0
  286. fontTools/ufoLib/converters.py +407 -0
  287. fontTools/ufoLib/errors.py +30 -0
  288. fontTools/ufoLib/etree.py +6 -0
  289. fontTools/ufoLib/filenames.py +356 -0
  290. fontTools/ufoLib/glifLib.py +2120 -0
  291. fontTools/ufoLib/kerning.py +141 -0
  292. fontTools/ufoLib/plistlib.py +47 -0
  293. fontTools/ufoLib/pointPen.py +6 -0
  294. fontTools/ufoLib/utils.py +107 -0
  295. fontTools/ufoLib/validators.py +1208 -0
  296. fontTools/unicode.py +50 -0
  297. fontTools/unicodedata/Blocks.py +817 -0
  298. fontTools/unicodedata/Mirrored.py +446 -0
  299. fontTools/unicodedata/OTTags.py +50 -0
  300. fontTools/unicodedata/ScriptExtensions.py +832 -0
  301. fontTools/unicodedata/Scripts.py +3639 -0
  302. fontTools/unicodedata/__init__.py +306 -0
  303. fontTools/varLib/__init__.py +1600 -0
  304. fontTools/varLib/__main__.py +6 -0
  305. fontTools/varLib/avar/__init__.py +0 -0
  306. fontTools/varLib/avar/__main__.py +72 -0
  307. fontTools/varLib/avar/build.py +79 -0
  308. fontTools/varLib/avar/map.py +108 -0
  309. fontTools/varLib/avar/plan.py +1004 -0
  310. fontTools/varLib/avar/unbuild.py +271 -0
  311. fontTools/varLib/avarPlanner.py +8 -0
  312. fontTools/varLib/builder.py +215 -0
  313. fontTools/varLib/cff.py +631 -0
  314. fontTools/varLib/errors.py +219 -0
  315. fontTools/varLib/featureVars.py +703 -0
  316. fontTools/varLib/hvar.py +113 -0
  317. fontTools/varLib/instancer/__init__.py +2052 -0
  318. fontTools/varLib/instancer/__main__.py +5 -0
  319. fontTools/varLib/instancer/featureVars.py +190 -0
  320. fontTools/varLib/instancer/names.py +388 -0
  321. fontTools/varLib/instancer/solver.py +309 -0
  322. fontTools/varLib/interpolatable.py +1209 -0
  323. fontTools/varLib/interpolatableHelpers.py +399 -0
  324. fontTools/varLib/interpolatablePlot.py +1269 -0
  325. fontTools/varLib/interpolatableTestContourOrder.py +82 -0
  326. fontTools/varLib/interpolatableTestStartingPoint.py +107 -0
  327. fontTools/varLib/interpolate_layout.py +124 -0
  328. fontTools/varLib/iup.c +19815 -0
  329. fontTools/varLib/iup.cp311-win32.pyd +0 -0
  330. fontTools/varLib/iup.py +490 -0
  331. fontTools/varLib/merger.py +1717 -0
  332. fontTools/varLib/models.py +642 -0
  333. fontTools/varLib/multiVarStore.py +253 -0
  334. fontTools/varLib/mutator.py +529 -0
  335. fontTools/varLib/mvar.py +40 -0
  336. fontTools/varLib/plot.py +238 -0
  337. fontTools/varLib/stat.py +149 -0
  338. fontTools/varLib/varStore.py +739 -0
  339. fontTools/voltLib/__init__.py +5 -0
  340. fontTools/voltLib/__main__.py +206 -0
  341. fontTools/voltLib/ast.py +452 -0
  342. fontTools/voltLib/error.py +12 -0
  343. fontTools/voltLib/lexer.py +99 -0
  344. fontTools/voltLib/parser.py +664 -0
  345. fontTools/voltLib/voltToFea.py +911 -0
  346. fonttools-4.60.2.data/data/share/man/man1/ttx.1 +225 -0
  347. fonttools-4.60.2.dist-info/METADATA +2250 -0
  348. fonttools-4.60.2.dist-info/RECORD +353 -0
  349. fonttools-4.60.2.dist-info/WHEEL +5 -0
  350. fonttools-4.60.2.dist-info/entry_points.txt +5 -0
  351. fonttools-4.60.2.dist-info/licenses/LICENSE +21 -0
  352. fonttools-4.60.2.dist-info/licenses/LICENSE.external +388 -0
  353. fonttools-4.60.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,356 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ """
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
24
+ """
25
+
26
+ # Restrictions are taken mostly from
27
+ # https://docs.microsoft.com/en-gb/windows/win32/fileio/naming-a-file#naming-conventions.
28
+ #
29
+ # 1. Integer value zero, sometimes referred to as the ASCII NUL character.
30
+ # 2. Characters whose integer representations are in the range 1 to 31,
31
+ # inclusive.
32
+ # 3. Various characters that (mostly) Windows and POSIX-y filesystems don't
33
+ # allow, plus "(" and ")", as per the specification.
34
+ illegalCharacters: set[str] = {
35
+ "\x00",
36
+ "\x01",
37
+ "\x02",
38
+ "\x03",
39
+ "\x04",
40
+ "\x05",
41
+ "\x06",
42
+ "\x07",
43
+ "\x08",
44
+ "\t",
45
+ "\n",
46
+ "\x0b",
47
+ "\x0c",
48
+ "\r",
49
+ "\x0e",
50
+ "\x0f",
51
+ "\x10",
52
+ "\x11",
53
+ "\x12",
54
+ "\x13",
55
+ "\x14",
56
+ "\x15",
57
+ "\x16",
58
+ "\x17",
59
+ "\x18",
60
+ "\x19",
61
+ "\x1a",
62
+ "\x1b",
63
+ "\x1c",
64
+ "\x1d",
65
+ "\x1e",
66
+ "\x1f",
67
+ '"',
68
+ "*",
69
+ "+",
70
+ "/",
71
+ ":",
72
+ "<",
73
+ ">",
74
+ "?",
75
+ "[",
76
+ "\\",
77
+ "]",
78
+ "(",
79
+ ")",
80
+ "|",
81
+ "\x7f",
82
+ }
83
+ reservedFileNames: set[str] = {
84
+ "aux",
85
+ "clock$",
86
+ "com1",
87
+ "com2",
88
+ "com3",
89
+ "com4",
90
+ "com5",
91
+ "com6",
92
+ "com7",
93
+ "com8",
94
+ "com9",
95
+ "con",
96
+ "lpt1",
97
+ "lpt2",
98
+ "lpt3",
99
+ "lpt4",
100
+ "lpt5",
101
+ "lpt6",
102
+ "lpt7",
103
+ "lpt8",
104
+ "lpt9",
105
+ "nul",
106
+ "prn",
107
+ }
108
+ maxFileNameLength: int = 255
109
+
110
+
111
+ class NameTranslationError(Exception):
112
+ pass
113
+
114
+
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
181
+ """
182
+ # the incoming name must be a string
183
+ if not isinstance(userName, str):
184
+ raise ValueError("The value for userName must be a string.")
185
+ # establish the prefix and suffix lengths
186
+ prefixLength = len(prefix)
187
+ suffixLength = len(suffix)
188
+ # replace an initial period with an _
189
+ # if no prefix is to be added
190
+ if not prefix and userName[0] == ".":
191
+ userName = "_" + userName[1:]
192
+ # filter the user name
193
+ filteredUserName = []
194
+ for character in userName:
195
+ # replace illegal characters with _
196
+ if character in illegalCharacters:
197
+ character = "_"
198
+ # add _ to all non-lower characters
199
+ elif character != character.lower():
200
+ character += "_"
201
+ filteredUserName.append(character)
202
+ userName = "".join(filteredUserName)
203
+ # clip to 255
204
+ sliceLength = maxFileNameLength - prefixLength - suffixLength
205
+ userName = userName[:sliceLength]
206
+ # test for illegal files names
207
+ parts = []
208
+ for part in userName.split("."):
209
+ if part.lower() in reservedFileNames:
210
+ part = "_" + part
211
+ parts.append(part)
212
+ userName = ".".join(parts)
213
+ # test for clash
214
+ fullName = prefix + userName + suffix
215
+ if fullName.lower() in existing:
216
+ fullName = handleClash1(userName, existing, prefix, suffix)
217
+ # finished
218
+ return fullName
219
+
220
+
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
260
+ """
261
+ # if the prefix length + user name length + suffix length + 15 is at
262
+ # or past the maximum length, silce 15 characters off of the user name
263
+ prefixLength = len(prefix)
264
+ suffixLength = len(suffix)
265
+ if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength:
266
+ l = prefixLength + len(userName) + suffixLength + 15
267
+ sliceLength = maxFileNameLength - l
268
+ userName = userName[:sliceLength]
269
+ finalName = None
270
+ # try to add numbers to create a unique name
271
+ counter = 1
272
+ while finalName is None:
273
+ name = userName + str(counter).zfill(15)
274
+ fullName = prefix + name + suffix
275
+ if fullName.lower() not in existing:
276
+ finalName = fullName
277
+ break
278
+ else:
279
+ counter += 1
280
+ if counter >= 999999999999999:
281
+ break
282
+ # if there is a clash, go to the next fallback
283
+ if finalName is None:
284
+ finalName = handleClash2(existing, prefix, suffix)
285
+ # finished
286
+ return finalName
287
+
288
+
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
330
+ """
331
+ # calculate the longest possible string
332
+ maxLength = maxFileNameLength - len(prefix) - len(suffix)
333
+ maxValue = int("9" * maxLength)
334
+ # try to find a number
335
+ finalName = None
336
+ counter = 1
337
+ while finalName is None:
338
+ fullName = prefix + str(counter) + suffix
339
+ if fullName.lower() not in existing:
340
+ finalName = fullName
341
+ break
342
+ else:
343
+ counter += 1
344
+ if counter >= maxValue:
345
+ break
346
+ # raise an error if nothing has been found
347
+ if finalName is None:
348
+ raise NameTranslationError("No unique name could be found.")
349
+ # finished
350
+ return finalName
351
+
352
+
353
+ if __name__ == "__main__":
354
+ import doctest
355
+
356
+ doctest.testmod()