this repo has no description
at trunk 177 lines 5.9 kB view raw
1#!/usr/bin/env python3 2# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 3 4from _builtins import _str_from_str, _str_guard, _str_len 5 6 7_empty_iterator = iter(()) 8 9 10class _SubscriptParser: 11 def __init__(self, field_name, idx): 12 self.field_name = field_name 13 self.idx = idx 14 15 def __iter__(self): 16 return self 17 18 def __next__(self): 19 field_name = self.field_name 20 if not field_name: 21 raise StopIteration 22 if field_name[0] != "[" and field_name[0] != ".": 23 raise ValueError("Only '.' or '[' may follow ']' in format field specifier") 24 if field_name[0] == ".": 25 right_idx = find_arg_end(field_name[1:]) 26 subscript = ( 27 field_name[self.idx + 1 :] 28 if right_idx == -1 29 else field_name[self.idx + 1 : right_idx + 1] 30 ) 31 if subscript == "": 32 raise ValueError("Empty attribute in format string") 33 self.field_name = "" if right_idx == -1 else field_name[right_idx + 1 :] 34 self.idx = 0 35 return (True, subscript) 36 right_idx = field_name.find("]") 37 if right_idx == -1: 38 raise ValueError("Missing ']' in format string") 39 subscript = field_name[self.idx + 1 : right_idx] 40 if subscript == "": 41 raise ValueError("Empty attribute in format string") 42 self.field_name = field_name[right_idx + 1 :] 43 self.idx = 0 44 return (False, int(subscript) if subscript.isdigit() else subscript) 45 46 47def find_arg_end(field_name): 48 """ 49 Returns the index of the end of an attribute name or element index. 50 field_name: the field being looked up, e.g. "0.name" 51 or "lookup[3]" 52 """ 53 dot_idx = field_name.find(".") 54 lbrack_idx = field_name.find("[") 55 if dot_idx == -1: 56 return lbrack_idx 57 if lbrack_idx == -1: 58 return dot_idx 59 return min(dot_idx, lbrack_idx) 60 61 62def formatter_field_name_split(field_name): 63 _str_guard(field_name) 64 65 idx = find_arg_end(field_name) 66 if idx != -1: 67 field = field_name[:idx] 68 if field.isdigit(): 69 field = int(field) 70 elif not field: 71 # In order to accept "[0]" as an implicit "0[0]", return 0 instead 72 # of the empty string here. See https://bugs.python.org/issue39985 73 # for more detail. 74 field = 0 75 return (field, _SubscriptParser(field_name[idx:], 0)) 76 77 if field_name.isdigit(): 78 field_name = int(field_name) 79 else: 80 field_name = _str_from_str(str, field_name) 81 return (field_name, _empty_iterator) 82 83 84def formatter_parser(string): # noqa: C901 85 _str_guard(string) 86 idx = -1 87 fragment_begin = 0 88 it = str.__iter__(string) 89 end_error = None 90 try: 91 while True: 92 idx += 1 93 ch = it.__next__() 94 if ch == "}": 95 end_error = "Single '}' encountered in format string" 96 idx += 1 97 next_ch = it.__next__() 98 99 if next_ch != "}": 100 raise ValueError(end_error) 101 yield (string[fragment_begin:idx], None, None, None) 102 end_error = None 103 fragment_begin = idx + 1 104 continue 105 if ch != "{": 106 continue 107 108 end_error = "Single '{' encountered in format string" 109 idx += 1 110 next_ch = it.__next__() 111 112 if next_ch == "{": 113 yield (string[fragment_begin:idx], None, None, None) 114 end_error = None 115 fragment_begin = idx + 1 116 continue 117 118 fragment = string[fragment_begin : idx - 1] 119 120 ch = next_ch 121 id_begin = idx 122 end_error = "expected '}' before end of string" 123 while ch != "}" and ch != "!" and ch != ":": 124 if ch == "{": 125 raise ValueError("unexpected '{' in field name") 126 if ch == "[": 127 while True: 128 idx += 1 129 ch = it.__next__() 130 if ch == "]": 131 break 132 idx += 1 133 ch = it.__next__() 134 ident = string[id_begin:idx] 135 136 if ch == "!": 137 end_error = "end of string while looking for conversion specifier" 138 idx += 1 139 ch = it.__next__() 140 end_error = "unmatched '{' in format spec" 141 conversion = ch 142 143 idx += 1 144 ch = it.__next__() 145 if ch != ":" and ch != "}": 146 raise ValueError("expected ':' after conversion specifier") 147 else: 148 conversion = None 149 150 spec = "" 151 if ch == ":": 152 spec_begin = idx + 1 153 end_error = "unmatched '{' in format spec" 154 curly_count = 1 155 while True: 156 idx += 1 157 ch = it.__next__() 158 if ch == "{": 159 curly_count += 1 160 continue 161 if ch == "}": 162 curly_count -= 1 163 if curly_count == 0: 164 spec = string[spec_begin:idx] 165 break 166 167 yield (fragment, ident, spec, conversion) 168 fragment_begin = idx + 1 169 end_error = None 170 except StopIteration: 171 if end_error is not None: 172 raise ValueError(end_error) 173 # Make sure everyone called `idx += 1` before `it.__next__()`. 174 assert idx == _str_len(string) 175 176 if idx - fragment_begin > 0: 177 yield (string[fragment_begin:idx], None, None, None)