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