Last active
January 30, 2021 14:08
-
-
Save justvanrossum/c1055da1041f8976a31a93ea838cc05e to your computer and use it in GitHub Desktop.
Test implementation for new proposed reversible glyph name to file name scheme
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# test implementation for https://github.com/unified-font-object/ufo-spec/issues/164 | |
from urllib.parse import unquote | |
import string | |
separatorChar = "^" | |
# TODO: [insert references] | |
reservedCharacters = set(" \" % * + / : < > ? [ \\ ] | ".split()) | |
reservedCharacters.update(chr(i) for i in range(32)) | |
# reservedCharacters.add(" ") # should we escape space chars or not? | |
reservedCharacters.add(chr(0x7F)) | |
reservedCharacters.add(separatorChar) | |
assert all(len(c) == 1 for c in reservedCharacters) | |
# TODO: [insert references] | |
reservedFileNames = set(""" | |
CON | |
PRN | |
AUX | |
CLOCK$ | |
NUL | |
COM1 | |
LPT1 | |
LPT2 | |
LPT3 | |
COM2 | |
COM3 | |
COM4 | |
""".lower().split()) | |
base32chars = string.digits + string.ascii_uppercase[:22] | |
assert len(set(base32chars)) == 32 | |
def userNameToFileName(userName, prefix="", suffix=""): | |
codeDigits = [] | |
for i in range(0, len(userName), 5): | |
digit = 0 | |
bit = 1 | |
for c in userName[i:i + 5]: | |
if c.isupper(): | |
digit |= bit | |
bit <<= 1 | |
codeDigits.append(digit) | |
# strip trailing zeros | |
while codeDigits and codeDigits[-1] == 0: | |
codeDigits.pop() | |
name = "".join(f"%{ord(c):02X}" if c in reservedCharacters else c for c in userName) | |
if name[0] == ".": | |
name = "%2E" + name[1:] | |
if not codeDigits and name.lower() in reservedFileNames: | |
codeDigits = [0] | |
if codeDigits: | |
disambiguationCode = separatorChar + "".join(base32chars[d] for d in codeDigits) | |
else: | |
disambiguationCode = "" | |
return prefix + name + disambiguationCode + suffix | |
def fileNameToUserName(fileName, prefix="", suffix="", stripSuffix=True): | |
if prefix: | |
assert fileName.startswith(prefix) | |
fileName = fileName[len(prefix):] | |
if stripSuffix or suffix: | |
if suffix: | |
assert fileName.endswith(suffix) | |
fileName = fileName[:-len(suffix)] | |
else: | |
fileName = fileName.rsplit(".", 1)[0] | |
name = fileName.split(separatorChar, 1)[0] | |
return unquote(name, encoding="ascii", errors="strict") | |
_testCases = [ | |
("Aring", "Aring^1.glif"), | |
("aring", "aring.glif"), | |
("ABCDEGF", "ABCDEGF^V3.glif"), | |
("f_i", "f_i.glif"), | |
("F_I", "F_I^5.glif"), | |
(".notdef", "%2Enotdef.glif"), | |
(".null", "%2Enull.glif"), | |
("CON", "CON^7.glif"), | |
("con", "con^0.glif"), | |
("aux", "aux^0.glif"), | |
("con.alt", "con.alt.glif"), | |
("A:", "A%3A^1.glif"), | |
("A^321", "A%5E321^1.glif"), | |
("a\\", "a%5C.glif"), | |
("a\t", "a%09.glif"), | |
# ("a ", "a%20.glif"), # escape space? | |
("a ", "a .glif"), # or not? | |
("a\"", "a%22.glif"), | |
("aaaaaaaaaA", "aaaaaaaaaA^0G.glif"), | |
("AAAAAAAAAA", "AAAAAAAAAA^VV.glif"), | |
] | |
# doctest for now, should be pytest | |
def _test_userNameToFileName(): | |
""" | |
>>> for userName, expectedFileName in _testCases: | |
... fileName = userNameToFileName(userName, suffix=".glif") | |
... assert expectedFileName == fileName, (userName, fileName, expectedFileName) | |
... | |
""" | |
def _test_fileNameToUserName(): | |
""" | |
>>> for expectedUserName, fileName in _testCases: | |
... userName = fileNameToUserName(fileName) | |
... assert expectedUserName == userName, (fileName, userName, expectedUserName) | |
... | |
""" | |
def _test_unique(): | |
""" | |
>>> base = "abcdefg" | |
>>> foldedResults = set() | |
>>> for i in range(2 ** len(base)): | |
... name = "" | |
... for j in range(len(base)): | |
... name += base[j].upper() if i & 0x01 else base[j] | |
... i >>= 1 | |
... assert name.lower() == base, name | |
... foldedResults.add(userNameToFileName(name).lower()) | |
... | |
>>> len(foldedResults) == 2 ** len(base) | |
True | |
""" | |
# TODO: test error cases | |
if __name__ == "__main__": | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment