annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/encodings/punycode.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
jpayne@68 1 """ Codec for the Punicode encoding, as specified in RFC 3492
jpayne@68 2
jpayne@68 3 Written by Martin v. Löwis.
jpayne@68 4 """
jpayne@68 5
jpayne@68 6 import codecs
jpayne@68 7
jpayne@68 8 ##################### Encoding #####################################
jpayne@68 9
jpayne@68 10 def segregate(str):
jpayne@68 11 """3.1 Basic code point segregation"""
jpayne@68 12 base = bytearray()
jpayne@68 13 extended = set()
jpayne@68 14 for c in str:
jpayne@68 15 if ord(c) < 128:
jpayne@68 16 base.append(ord(c))
jpayne@68 17 else:
jpayne@68 18 extended.add(c)
jpayne@68 19 extended = sorted(extended)
jpayne@68 20 return bytes(base), extended
jpayne@68 21
jpayne@68 22 def selective_len(str, max):
jpayne@68 23 """Return the length of str, considering only characters below max."""
jpayne@68 24 res = 0
jpayne@68 25 for c in str:
jpayne@68 26 if ord(c) < max:
jpayne@68 27 res += 1
jpayne@68 28 return res
jpayne@68 29
jpayne@68 30 def selective_find(str, char, index, pos):
jpayne@68 31 """Return a pair (index, pos), indicating the next occurrence of
jpayne@68 32 char in str. index is the position of the character considering
jpayne@68 33 only ordinals up to and including char, and pos is the position in
jpayne@68 34 the full string. index/pos is the starting position in the full
jpayne@68 35 string."""
jpayne@68 36
jpayne@68 37 l = len(str)
jpayne@68 38 while 1:
jpayne@68 39 pos += 1
jpayne@68 40 if pos == l:
jpayne@68 41 return (-1, -1)
jpayne@68 42 c = str[pos]
jpayne@68 43 if c == char:
jpayne@68 44 return index+1, pos
jpayne@68 45 elif c < char:
jpayne@68 46 index += 1
jpayne@68 47
jpayne@68 48 def insertion_unsort(str, extended):
jpayne@68 49 """3.2 Insertion unsort coding"""
jpayne@68 50 oldchar = 0x80
jpayne@68 51 result = []
jpayne@68 52 oldindex = -1
jpayne@68 53 for c in extended:
jpayne@68 54 index = pos = -1
jpayne@68 55 char = ord(c)
jpayne@68 56 curlen = selective_len(str, char)
jpayne@68 57 delta = (curlen+1) * (char - oldchar)
jpayne@68 58 while 1:
jpayne@68 59 index,pos = selective_find(str,c,index,pos)
jpayne@68 60 if index == -1:
jpayne@68 61 break
jpayne@68 62 delta += index - oldindex
jpayne@68 63 result.append(delta-1)
jpayne@68 64 oldindex = index
jpayne@68 65 delta = 0
jpayne@68 66 oldchar = char
jpayne@68 67
jpayne@68 68 return result
jpayne@68 69
jpayne@68 70 def T(j, bias):
jpayne@68 71 # Punycode parameters: tmin = 1, tmax = 26, base = 36
jpayne@68 72 res = 36 * (j + 1) - bias
jpayne@68 73 if res < 1: return 1
jpayne@68 74 if res > 26: return 26
jpayne@68 75 return res
jpayne@68 76
jpayne@68 77 digits = b"abcdefghijklmnopqrstuvwxyz0123456789"
jpayne@68 78 def generate_generalized_integer(N, bias):
jpayne@68 79 """3.3 Generalized variable-length integers"""
jpayne@68 80 result = bytearray()
jpayne@68 81 j = 0
jpayne@68 82 while 1:
jpayne@68 83 t = T(j, bias)
jpayne@68 84 if N < t:
jpayne@68 85 result.append(digits[N])
jpayne@68 86 return bytes(result)
jpayne@68 87 result.append(digits[t + ((N - t) % (36 - t))])
jpayne@68 88 N = (N - t) // (36 - t)
jpayne@68 89 j += 1
jpayne@68 90
jpayne@68 91 def adapt(delta, first, numchars):
jpayne@68 92 if first:
jpayne@68 93 delta //= 700
jpayne@68 94 else:
jpayne@68 95 delta //= 2
jpayne@68 96 delta += delta // numchars
jpayne@68 97 # ((base - tmin) * tmax) // 2 == 455
jpayne@68 98 divisions = 0
jpayne@68 99 while delta > 455:
jpayne@68 100 delta = delta // 35 # base - tmin
jpayne@68 101 divisions += 36
jpayne@68 102 bias = divisions + (36 * delta // (delta + 38))
jpayne@68 103 return bias
jpayne@68 104
jpayne@68 105
jpayne@68 106 def generate_integers(baselen, deltas):
jpayne@68 107 """3.4 Bias adaptation"""
jpayne@68 108 # Punycode parameters: initial bias = 72, damp = 700, skew = 38
jpayne@68 109 result = bytearray()
jpayne@68 110 bias = 72
jpayne@68 111 for points, delta in enumerate(deltas):
jpayne@68 112 s = generate_generalized_integer(delta, bias)
jpayne@68 113 result.extend(s)
jpayne@68 114 bias = adapt(delta, points==0, baselen+points+1)
jpayne@68 115 return bytes(result)
jpayne@68 116
jpayne@68 117 def punycode_encode(text):
jpayne@68 118 base, extended = segregate(text)
jpayne@68 119 deltas = insertion_unsort(text, extended)
jpayne@68 120 extended = generate_integers(len(base), deltas)
jpayne@68 121 if base:
jpayne@68 122 return base + b"-" + extended
jpayne@68 123 return extended
jpayne@68 124
jpayne@68 125 ##################### Decoding #####################################
jpayne@68 126
jpayne@68 127 def decode_generalized_number(extended, extpos, bias, errors):
jpayne@68 128 """3.3 Generalized variable-length integers"""
jpayne@68 129 result = 0
jpayne@68 130 w = 1
jpayne@68 131 j = 0
jpayne@68 132 while 1:
jpayne@68 133 try:
jpayne@68 134 char = ord(extended[extpos])
jpayne@68 135 except IndexError:
jpayne@68 136 if errors == "strict":
jpayne@68 137 raise UnicodeError("incomplete punicode string")
jpayne@68 138 return extpos + 1, None
jpayne@68 139 extpos += 1
jpayne@68 140 if 0x41 <= char <= 0x5A: # A-Z
jpayne@68 141 digit = char - 0x41
jpayne@68 142 elif 0x30 <= char <= 0x39:
jpayne@68 143 digit = char - 22 # 0x30-26
jpayne@68 144 elif errors == "strict":
jpayne@68 145 raise UnicodeError("Invalid extended code point '%s'"
jpayne@68 146 % extended[extpos])
jpayne@68 147 else:
jpayne@68 148 return extpos, None
jpayne@68 149 t = T(j, bias)
jpayne@68 150 result += digit * w
jpayne@68 151 if digit < t:
jpayne@68 152 return extpos, result
jpayne@68 153 w = w * (36 - t)
jpayne@68 154 j += 1
jpayne@68 155
jpayne@68 156
jpayne@68 157 def insertion_sort(base, extended, errors):
jpayne@68 158 """3.2 Insertion unsort coding"""
jpayne@68 159 char = 0x80
jpayne@68 160 pos = -1
jpayne@68 161 bias = 72
jpayne@68 162 extpos = 0
jpayne@68 163 while extpos < len(extended):
jpayne@68 164 newpos, delta = decode_generalized_number(extended, extpos,
jpayne@68 165 bias, errors)
jpayne@68 166 if delta is None:
jpayne@68 167 # There was an error in decoding. We can't continue because
jpayne@68 168 # synchronization is lost.
jpayne@68 169 return base
jpayne@68 170 pos += delta+1
jpayne@68 171 char += pos // (len(base) + 1)
jpayne@68 172 if char > 0x10FFFF:
jpayne@68 173 if errors == "strict":
jpayne@68 174 raise UnicodeError("Invalid character U+%x" % char)
jpayne@68 175 char = ord('?')
jpayne@68 176 pos = pos % (len(base) + 1)
jpayne@68 177 base = base[:pos] + chr(char) + base[pos:]
jpayne@68 178 bias = adapt(delta, (extpos == 0), len(base))
jpayne@68 179 extpos = newpos
jpayne@68 180 return base
jpayne@68 181
jpayne@68 182 def punycode_decode(text, errors):
jpayne@68 183 if isinstance(text, str):
jpayne@68 184 text = text.encode("ascii")
jpayne@68 185 if isinstance(text, memoryview):
jpayne@68 186 text = bytes(text)
jpayne@68 187 pos = text.rfind(b"-")
jpayne@68 188 if pos == -1:
jpayne@68 189 base = ""
jpayne@68 190 extended = str(text, "ascii").upper()
jpayne@68 191 else:
jpayne@68 192 base = str(text[:pos], "ascii", errors)
jpayne@68 193 extended = str(text[pos+1:], "ascii").upper()
jpayne@68 194 return insertion_sort(base, extended, errors)
jpayne@68 195
jpayne@68 196 ### Codec APIs
jpayne@68 197
jpayne@68 198 class Codec(codecs.Codec):
jpayne@68 199
jpayne@68 200 def encode(self, input, errors='strict'):
jpayne@68 201 res = punycode_encode(input)
jpayne@68 202 return res, len(input)
jpayne@68 203
jpayne@68 204 def decode(self, input, errors='strict'):
jpayne@68 205 if errors not in ('strict', 'replace', 'ignore'):
jpayne@68 206 raise UnicodeError("Unsupported error handling "+errors)
jpayne@68 207 res = punycode_decode(input, errors)
jpayne@68 208 return res, len(input)
jpayne@68 209
jpayne@68 210 class IncrementalEncoder(codecs.IncrementalEncoder):
jpayne@68 211 def encode(self, input, final=False):
jpayne@68 212 return punycode_encode(input)
jpayne@68 213
jpayne@68 214 class IncrementalDecoder(codecs.IncrementalDecoder):
jpayne@68 215 def decode(self, input, final=False):
jpayne@68 216 if self.errors not in ('strict', 'replace', 'ignore'):
jpayne@68 217 raise UnicodeError("Unsupported error handling "+self.errors)
jpayne@68 218 return punycode_decode(input, self.errors)
jpayne@68 219
jpayne@68 220 class StreamWriter(Codec,codecs.StreamWriter):
jpayne@68 221 pass
jpayne@68 222
jpayne@68 223 class StreamReader(Codec,codecs.StreamReader):
jpayne@68 224 pass
jpayne@68 225
jpayne@68 226 ### encodings module API
jpayne@68 227
jpayne@68 228 def getregentry():
jpayne@68 229 return codecs.CodecInfo(
jpayne@68 230 name='punycode',
jpayne@68 231 encode=Codec().encode,
jpayne@68 232 decode=Codec().decode,
jpayne@68 233 incrementalencoder=IncrementalEncoder,
jpayne@68 234 incrementaldecoder=IncrementalDecoder,
jpayne@68 235 streamwriter=StreamWriter,
jpayne@68 236 streamreader=StreamReader,
jpayne@68 237 )