blob: 13e50a4deefb3474833a2d1517aa091999f96d90 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Rewrite protobuf files to have monotonically increasing field numbers.
This is safe to do for recipe property protobuf files because they are only ever
used to de/serialize JSON-encoding protos, which are only sensitive to field
names, not field numbers.
"""
import argparse
import re
def renumber_fields(lines):
# Copy the lines for modification.
newlines = list(lines)
# levels is a stack that tracks the next field number to use within a given
# scope.
levels = []
for i, line in enumerate(lines):
stripped = line.split("//")[0].strip()
if not stripped:
continue
if stripped.endswith("{"):
if stripped.startswith("enum"):
# First field number for enums is 0.
levels.append(0)
elif stripped.startswith(("message", "oneof")):
# First field number for messages and oneofs is 1.
levels.append(1)
elif stripped.startswith("option"):
# option clauses shouldn't contain any numbered fields.
levels.append(None)
else:
raise ValueError("invalid line: %r" % line)
continue
if stripped.startswith("}"):
levels.pop()
continue
# A single line may contain multiple fields.
for match in re.finditer(
r"((?P<reserved>reserved\s+\w+(\s*(,|to\s)\s*\w+)*)|(?P<field>\w+ =) \d+(?P<option>\s*(\[.*\])?));",
stripped,
):
if match.group("reserved"):
# Reserved fields are not necessary in recipe property protos.
newlines[i] = line.replace(match.group(0), "")
continue
new = "%s %d%s;" % (
match.group("field"),
levels[-1],
match.group("option") or "",
)
newlines[i] = line.replace(match.group(0), new)
levels[-1] += 1
return newlines
def main():
parser = argparse.ArgumentParser(description="Renumber .proto file fields.")
parser.add_argument("filename", metavar="FILE", type=str, help="The file to modify")
parser.add_argument(
"--dry-run",
action="store_true",
help="Print result to stdout instead of writing to the file",
)
args = parser.parse_args()
with open(args.filename, encoding="utf-8") as f:
lines = f.readlines()
new_lines = renumber_fields(lines)
if args.dry_run:
for l in new_lines:
print(l, end="")
else:
with open(args.filename, "w", encoding="utf-8") as f:
f.writelines(new_lines)
if __name__ == "__main__":
main()