1 """A collection of string constants.
2
3 Public module variables:
4
5 whitespace -- a string containing all ASCII whitespace
6 ascii_lowercase -- a string containing all ASCII lowercase letters
7 ascii_uppercase -- a string containing all ASCII uppercase letters
8 ascii_letters -- a string containing all ASCII letters
9 digits -- a string containing all ASCII decimal digits
10 hexdigits -- a string containing all ASCII hexadecimal digits
11 octdigits -- a string containing all ASCII octal digits
12 punctuation -- a string containing all ASCII punctuation characters
13 printable -- a string containing all ASCII characters considered printable
14
15 """
16
17 __all__ = ["ascii_letters", "ascii_lowercase", "ascii_uppercase", "capwords",
18 "digits", "hexdigits", "octdigits", "printable", "punctuation",
19 "whitespace", "Formatter", "Template"]
20
21 import _string
22
23 # Some strings for ctype-style character classification
24 whitespace = ' \t\n\r\v\f'
25 ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
26 ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
27 ascii_letters = ascii_lowercase + ascii_uppercase
28 digits = '0123456789'
29 hexdigits = digits + 'abcdef' + 'ABCDEF'
30 octdigits = '01234567'
31 punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
32 printable = digits + ascii_letters + punctuation + whitespace
33
34 # Functions which aren't available as string methods.
35
36 # Capitalize the words in a string, e.g. " aBc dEf " -> "Abc Def".
37 def capwords(s, sep=None):
38 """capwords(s [,sep]) -> string
39
40 Split the argument into words using split, capitalize each
41 word using capitalize, and join the capitalized words using
42 join. If the optional second argument sep is absent or None,
43 runs of whitespace characters are replaced by a single space
44 and leading and trailing whitespace are removed, otherwise
45 sep is used to split and join the words.
46
47 """
48 return (sep or ' ').join(map(str.capitalize, s.split(sep)))
49
50
51 ####################################################################
52 import re as _re
53 from collections import ChainMap as _ChainMap
54
55 _sentinel_dict = {}
56
57 class ESC[4;38;5;81mTemplate:
58 """A string class for supporting $-substitutions."""
59
60 delimiter = '$'
61 # r'[a-z]' matches to non-ASCII letters when used with IGNORECASE, but
62 # without the ASCII flag. We can't add re.ASCII to flags because of
63 # backward compatibility. So we use the ?a local flag and [a-z] pattern.
64 # See https://bugs.python.org/issue31672
65 idpattern = r'(?a:[_a-z][_a-z0-9]*)'
66 braceidpattern = None
67 flags = _re.IGNORECASE
68
69 def __init_subclass__(cls):
70 super().__init_subclass__()
71 if 'pattern' in cls.__dict__:
72 pattern = cls.pattern
73 else:
74 delim = _re.escape(cls.delimiter)
75 id = cls.idpattern
76 bid = cls.braceidpattern or cls.idpattern
77 pattern = fr"""
78 {delim}(?:
79 (?P<escaped>{delim}) | # Escape sequence of two delimiters
80 (?P<named>{id}) | # delimiter and a Python identifier
81 {{(?P<braced>{bid})}} | # delimiter and a braced identifier
82 (?P<invalid>) # Other ill-formed delimiter exprs
83 )
84 """
85 cls.pattern = _re.compile(pattern, cls.flags | _re.VERBOSE)
86
87 def __init__(self, template):
88 self.template = template
89
90 # Search for $$, $identifier, ${identifier}, and any bare $'s
91
92 def _invalid(self, mo):
93 i = mo.start('invalid')
94 lines = self.template[:i].splitlines(keepends=True)
95 if not lines:
96 colno = 1
97 lineno = 1
98 else:
99 colno = i - len(''.join(lines[:-1]))
100 lineno = len(lines)
101 raise ValueError('Invalid placeholder in string: line %d, col %d' %
102 (lineno, colno))
103
104 def substitute(self, mapping=_sentinel_dict, /, **kws):
105 if mapping is _sentinel_dict:
106 mapping = kws
107 elif kws:
108 mapping = _ChainMap(kws, mapping)
109 # Helper function for .sub()
110 def convert(mo):
111 # Check the most common path first.
112 named = mo.group('named') or mo.group('braced')
113 if named is not None:
114 return str(mapping[named])
115 if mo.group('escaped') is not None:
116 return self.delimiter
117 if mo.group('invalid') is not None:
118 self._invalid(mo)
119 raise ValueError('Unrecognized named group in pattern',
120 self.pattern)
121 return self.pattern.sub(convert, self.template)
122
123 def safe_substitute(self, mapping=_sentinel_dict, /, **kws):
124 if mapping is _sentinel_dict:
125 mapping = kws
126 elif kws:
127 mapping = _ChainMap(kws, mapping)
128 # Helper function for .sub()
129 def convert(mo):
130 named = mo.group('named') or mo.group('braced')
131 if named is not None:
132 try:
133 return str(mapping[named])
134 except KeyError:
135 return mo.group()
136 if mo.group('escaped') is not None:
137 return self.delimiter
138 if mo.group('invalid') is not None:
139 return mo.group()
140 raise ValueError('Unrecognized named group in pattern',
141 self.pattern)
142 return self.pattern.sub(convert, self.template)
143
144 def is_valid(self):
145 for mo in self.pattern.finditer(self.template):
146 if mo.group('invalid') is not None:
147 return False
148 if (mo.group('named') is None
149 and mo.group('braced') is None
150 and mo.group('escaped') is None):
151 # If all the groups are None, there must be
152 # another group we're not expecting
153 raise ValueError('Unrecognized named group in pattern',
154 self.pattern)
155 return True
156
157 def get_identifiers(self):
158 ids = []
159 for mo in self.pattern.finditer(self.template):
160 named = mo.group('named') or mo.group('braced')
161 if named is not None and named not in ids:
162 # add a named group only the first time it appears
163 ids.append(named)
164 elif (named is None
165 and mo.group('invalid') is None
166 and mo.group('escaped') is None):
167 # If all the groups are None, there must be
168 # another group we're not expecting
169 raise ValueError('Unrecognized named group in pattern',
170 self.pattern)
171 return ids
172
173 # Initialize Template.pattern. __init_subclass__() is automatically called
174 # only for subclasses, not for the Template class itself.
175 Template.__init_subclass__()
176
177
178 ########################################################################
179 # the Formatter class
180 # see PEP 3101 for details and purpose of this class
181
182 # The hard parts are reused from the C implementation. They're exposed as "_"
183 # prefixed methods of str.
184
185 # The overall parser is implemented in _string.formatter_parser.
186 # The field name parser is implemented in _string.formatter_field_name_split
187
188 class ESC[4;38;5;81mFormatter:
189 def format(self, format_string, /, *args, **kwargs):
190 return self.vformat(format_string, args, kwargs)
191
192 def vformat(self, format_string, args, kwargs):
193 used_args = set()
194 result, _ = self._vformat(format_string, args, kwargs, used_args, 2)
195 self.check_unused_args(used_args, args, kwargs)
196 return result
197
198 def _vformat(self, format_string, args, kwargs, used_args, recursion_depth,
199 auto_arg_index=0):
200 if recursion_depth < 0:
201 raise ValueError('Max string recursion exceeded')
202 result = []
203 for literal_text, field_name, format_spec, conversion in \
204 self.parse(format_string):
205
206 # output the literal text
207 if literal_text:
208 result.append(literal_text)
209
210 # if there's a field, output it
211 if field_name is not None:
212 # this is some markup, find the object and do
213 # the formatting
214
215 # handle arg indexing when empty field_names are given.
216 if field_name == '':
217 if auto_arg_index is False:
218 raise ValueError('cannot switch from manual field '
219 'specification to automatic field '
220 'numbering')
221 field_name = str(auto_arg_index)
222 auto_arg_index += 1
223 elif field_name.isdigit():
224 if auto_arg_index:
225 raise ValueError('cannot switch from manual field '
226 'specification to automatic field '
227 'numbering')
228 # disable auto arg incrementing, if it gets
229 # used later on, then an exception will be raised
230 auto_arg_index = False
231
232 # given the field_name, find the object it references
233 # and the argument it came from
234 obj, arg_used = self.get_field(field_name, args, kwargs)
235 used_args.add(arg_used)
236
237 # do any conversion on the resulting object
238 obj = self.convert_field(obj, conversion)
239
240 # expand the format spec, if needed
241 format_spec, auto_arg_index = self._vformat(
242 format_spec, args, kwargs,
243 used_args, recursion_depth-1,
244 auto_arg_index=auto_arg_index)
245
246 # format the object and append to the result
247 result.append(self.format_field(obj, format_spec))
248
249 return ''.join(result), auto_arg_index
250
251
252 def get_value(self, key, args, kwargs):
253 if isinstance(key, int):
254 return args[key]
255 else:
256 return kwargs[key]
257
258
259 def check_unused_args(self, used_args, args, kwargs):
260 pass
261
262
263 def format_field(self, value, format_spec):
264 return format(value, format_spec)
265
266
267 def convert_field(self, value, conversion):
268 # do any conversion on the resulting object
269 if conversion is None:
270 return value
271 elif conversion == 's':
272 return str(value)
273 elif conversion == 'r':
274 return repr(value)
275 elif conversion == 'a':
276 return ascii(value)
277 raise ValueError("Unknown conversion specifier {0!s}".format(conversion))
278
279
280 # returns an iterable that contains tuples of the form:
281 # (literal_text, field_name, format_spec, conversion)
282 # literal_text can be zero length
283 # field_name can be None, in which case there's no
284 # object to format and output
285 # if field_name is not None, it is looked up, formatted
286 # with format_spec and conversion and then used
287 def parse(self, format_string):
288 return _string.formatter_parser(format_string)
289
290
291 # given a field_name, find the object it references.
292 # field_name: the field being looked up, e.g. "0.name"
293 # or "lookup[3]"
294 # used_args: a set of which args have been used
295 # args, kwargs: as passed in to vformat
296 def get_field(self, field_name, args, kwargs):
297 first, rest = _string.formatter_field_name_split(field_name)
298
299 obj = self.get_value(first, args, kwargs)
300
301 # loop through the rest of the field_name, doing
302 # getattr or getitem as needed
303 for is_attr, i in rest:
304 if is_attr:
305 obj = getattr(obj, i)
306 else:
307 obj = obj[i]
308
309 return obj, first