Skip to content

Commit b056ff0

Browse files
Revert "Simplify format to just pass conformance tests"
This reverts commit 3071c6d.
1 parent 3071c6d commit b056ff0

File tree

1 file changed

+143
-2
lines changed

1 file changed

+143
-2
lines changed

protovalidate/internal/string_format.py

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,159 @@
1515
import celpy # type: ignore
1616
from celpy import celtypes # type: ignore
1717

18+
QUOTE_TRANS = str.maketrans(
19+
{
20+
"\a": r"\a",
21+
"\b": r"\b",
22+
"\f": r"\f",
23+
"\n": r"\n",
24+
"\r": r"\r",
25+
"\t": r"\t",
26+
"\v": r"\v",
27+
"\\": r"\\",
28+
'"': r"\"",
29+
}
30+
)
31+
32+
33+
def quote(s: str) -> str:
34+
return '"' + s.translate(QUOTE_TRANS) + '"'
35+
1836

1937
class StringFormat:
2038
"""An implementation of string.format() in CEL."""
2139

2240
def __init__(self, locale: str):
2341
self.locale = locale
2442

25-
def format(self, fmt: celtypes.Value, _: celtypes.Value) -> celpy.Result:
43+
def format(self, fmt: celtypes.Value, args: celtypes.Value) -> celpy.Result:
2644
if not isinstance(fmt, celtypes.StringType):
2745
return celpy.CELEvalError("format() requires a string as the first argument")
28-
return celtypes.StringType(fmt)
46+
if not isinstance(args, celtypes.ListType):
47+
return celpy.CELEvalError("format() requires a list as the second argument")
48+
# printf style formatting
49+
i = 0
50+
j = 0
51+
result = ""
52+
while i < len(fmt):
53+
if fmt[i] != "%":
54+
result += fmt[i]
55+
i += 1
56+
continue
57+
58+
if i + 1 < len(fmt) and fmt[i + 1] == "%":
59+
result += "%"
60+
i += 2
61+
continue
62+
if j >= len(args):
63+
return celpy.CELEvalError("format() not enough arguments for format string")
64+
arg = args[j]
65+
j += 1
66+
i += 1
67+
if i >= len(fmt):
68+
return celpy.CELEvalError("format() incomplete format specifier")
69+
precision = 6
70+
if fmt[i] == ".":
71+
i += 1
72+
precision = 0
73+
while i < len(fmt) and fmt[i].isdigit():
74+
precision = precision * 10 + int(fmt[i])
75+
i += 1
76+
if i >= len(fmt):
77+
return celpy.CELEvalError("format() incomplete format specifier")
78+
if fmt[i] == "f":
79+
result += self.format_float(arg, precision)
80+
if fmt[i] == "e":
81+
result += self.format_exponential(arg, precision)
82+
elif fmt[i] == "d":
83+
result += self.format_int(arg)
84+
elif fmt[i] == "s":
85+
result += self.format_string(arg)
86+
elif fmt[i] == "x":
87+
result += self.format_hex(arg)
88+
elif fmt[i] == "X":
89+
result += self.format_hex(arg).upper()
90+
elif fmt[i] == "o":
91+
result += self.format_oct(arg)
92+
elif fmt[i] == "b":
93+
result += self.format_bin(arg)
94+
else:
95+
return celpy.CELEvalError("format() unknown format specifier: " + fmt[i])
96+
i += 1
97+
if j < len(args):
98+
return celpy.CELEvalError("format() too many arguments for format string")
99+
return celtypes.StringType(result)
100+
101+
def format_float(self, arg: celtypes.Value, precision: int) -> celpy.Result:
102+
if isinstance(arg, celtypes.DoubleType):
103+
return celtypes.StringType(f"{arg:.{precision}f}")
104+
return self.format_int(arg)
105+
106+
def format_exponential(self, arg: celtypes.Value, precision: int) -> celpy.Result:
107+
if isinstance(arg, celtypes.DoubleType):
108+
return celtypes.StringType(f"{arg:.{precision}e}")
109+
return self.format_int(arg)
110+
111+
def format_int(self, arg: celtypes.Value) -> celpy.Result:
112+
if isinstance(arg, celtypes.IntType):
113+
return celtypes.StringType(arg)
114+
if isinstance(arg, celtypes.UintType):
115+
return celtypes.StringType(arg)
116+
return celpy.CELEvalError("format_int() requires an integer argument")
117+
118+
def format_hex(self, arg: celtypes.Value) -> celpy.Result:
119+
if isinstance(arg, celtypes.IntType):
120+
return celtypes.StringType(f"{arg:x}")
121+
if isinstance(arg, celtypes.UintType):
122+
return celtypes.StringType(f"{arg:x}")
123+
if isinstance(arg, celtypes.BytesType):
124+
return celtypes.StringType(arg.hex())
125+
if isinstance(arg, celtypes.StringType):
126+
return celtypes.StringType(arg.encode("utf-8").hex())
127+
return celpy.CELEvalError("format_hex() requires an integer, string, or binary argument")
128+
129+
def format_oct(self, arg: celtypes.Value) -> celpy.Result:
130+
if isinstance(arg, celtypes.IntType):
131+
return celtypes.StringType(f"{arg:o}")
132+
if isinstance(arg, celtypes.UintType):
133+
return celtypes.StringType(f"{arg:o}")
134+
return celpy.CELEvalError("format_oct() requires an integer argument")
135+
136+
def format_bin(self, arg: celtypes.Value) -> celpy.Result:
137+
if isinstance(arg, celtypes.IntType):
138+
return celtypes.StringType(f"{arg:b}")
139+
if isinstance(arg, celtypes.UintType):
140+
return celtypes.StringType(f"{arg:b}")
141+
if isinstance(arg, celtypes.BoolType):
142+
return celtypes.StringType(f"{arg:b}")
143+
return celpy.CELEvalError("format_bin() requires an integer argument")
144+
145+
def format_string(self, arg: celtypes.Value) -> celpy.Result:
146+
if isinstance(arg, celtypes.StringType):
147+
return arg
148+
if isinstance(arg, celtypes.BytesType):
149+
return celtypes.StringType(arg.hex())
150+
if isinstance(arg, celtypes.ListType):
151+
return self.format_list(arg)
152+
return celtypes.StringType(arg)
153+
154+
def format_value(self, arg: celtypes.Value) -> celpy.Result:
155+
if isinstance(arg, (celtypes.StringType, str)):
156+
return celtypes.StringType(quote(arg))
157+
if isinstance(arg, celtypes.UintType):
158+
return celtypes.StringType(arg)
159+
return self.format_string(arg)
160+
161+
def format_list(self, arg: celtypes.ListType) -> celpy.Result:
162+
result = "["
163+
for i in range(len(arg)):
164+
if i > 0:
165+
result += ", "
166+
result += self.format_value(arg[i])
167+
result += "]"
168+
return celtypes.StringType(result)
29169

30170

31171
_default_format = StringFormat("en_US")
32172
format = _default_format.format # noqa: A001
173+
format_value = _default_format.format_value

0 commit comments

Comments
 (0)