#!/usr/bin/env python3 # Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) from _builtins import _str_from_str, _str_guard, _str_len _empty_iterator = iter(()) class _SubscriptParser: def __init__(self, field_name, idx): self.field_name = field_name self.idx = idx def __iter__(self): return self def __next__(self): field_name = self.field_name if not field_name: raise StopIteration if field_name[0] != "[" and field_name[0] != ".": raise ValueError("Only '.' or '[' may follow ']' in format field specifier") if field_name[0] == ".": right_idx = find_arg_end(field_name[1:]) subscript = ( field_name[self.idx + 1 :] if right_idx == -1 else field_name[self.idx + 1 : right_idx + 1] ) if subscript == "": raise ValueError("Empty attribute in format string") self.field_name = "" if right_idx == -1 else field_name[right_idx + 1 :] self.idx = 0 return (True, subscript) right_idx = field_name.find("]") if right_idx == -1: raise ValueError("Missing ']' in format string") subscript = field_name[self.idx + 1 : right_idx] if subscript == "": raise ValueError("Empty attribute in format string") self.field_name = field_name[right_idx + 1 :] self.idx = 0 return (False, int(subscript) if subscript.isdigit() else subscript) def find_arg_end(field_name): """ Returns the index of the end of an attribute name or element index. field_name: the field being looked up, e.g. "0.name" or "lookup[3]" """ dot_idx = field_name.find(".") lbrack_idx = field_name.find("[") if dot_idx == -1: return lbrack_idx if lbrack_idx == -1: return dot_idx return min(dot_idx, lbrack_idx) def formatter_field_name_split(field_name): _str_guard(field_name) idx = find_arg_end(field_name) if idx != -1: field = field_name[:idx] if field.isdigit(): field = int(field) elif not field: # In order to accept "[0]" as an implicit "0[0]", return 0 instead # of the empty string here. See https://bugs.python.org/issue39985 # for more detail. field = 0 return (field, _SubscriptParser(field_name[idx:], 0)) if field_name.isdigit(): field_name = int(field_name) else: field_name = _str_from_str(str, field_name) return (field_name, _empty_iterator) def formatter_parser(string): # noqa: C901 _str_guard(string) idx = -1 fragment_begin = 0 it = str.__iter__(string) end_error = None try: while True: idx += 1 ch = it.__next__() if ch == "}": end_error = "Single '}' encountered in format string" idx += 1 next_ch = it.__next__() if next_ch != "}": raise ValueError(end_error) yield (string[fragment_begin:idx], None, None, None) end_error = None fragment_begin = idx + 1 continue if ch != "{": continue end_error = "Single '{' encountered in format string" idx += 1 next_ch = it.__next__() if next_ch == "{": yield (string[fragment_begin:idx], None, None, None) end_error = None fragment_begin = idx + 1 continue fragment = string[fragment_begin : idx - 1] ch = next_ch id_begin = idx end_error = "expected '}' before end of string" while ch != "}" and ch != "!" and ch != ":": if ch == "{": raise ValueError("unexpected '{' in field name") if ch == "[": while True: idx += 1 ch = it.__next__() if ch == "]": break idx += 1 ch = it.__next__() ident = string[id_begin:idx] if ch == "!": end_error = "end of string while looking for conversion specifier" idx += 1 ch = it.__next__() end_error = "unmatched '{' in format spec" conversion = ch idx += 1 ch = it.__next__() if ch != ":" and ch != "}": raise ValueError("expected ':' after conversion specifier") else: conversion = None spec = "" if ch == ":": spec_begin = idx + 1 end_error = "unmatched '{' in format spec" curly_count = 1 while True: idx += 1 ch = it.__next__() if ch == "{": curly_count += 1 continue if ch == "}": curly_count -= 1 if curly_count == 0: spec = string[spec_begin:idx] break yield (fragment, ident, spec, conversion) fragment_begin = idx + 1 end_error = None except StopIteration: if end_error is not None: raise ValueError(end_error) # Make sure everyone called `idx += 1` before `it.__next__()`. assert idx == _str_len(string) if idx - fragment_begin > 0: yield (string[fragment_begin:idx], None, None, None)