1#!/usr/bin/env python3 2# Copyright 2024 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15"""Substitute a template file with variables from bazel workspace status.""" 16import argparse 17import string 18import sys 19from typing import TextIO 20 21 22def _load_status_file(file: TextIO) -> dict[str, str]: 23 """Load a bazel status file. 24 25 E.g. bazel-out/stable-status.txt and volatile-status.txt. 26 27 Args: 28 file: The open status file handle to be read. 29 30 Returns: 31 A dictionary of key => value pairs (both strings) read from the file. 32 If the value is missing, and empty string is provided. 33 """ 34 result = {} 35 for line in file: 36 line = line.strip() 37 parts = line.split(maxsplit=1) 38 key = parts[0] 39 value = parts[1] if len(parts) >= 2 else "" 40 result[key] = value 41 return result 42 43 44def _parse_args() -> argparse.Namespace: 45 parser = argparse.ArgumentParser() 46 parser.add_argument( 47 "--status-file", 48 action="append", 49 dest="status_files", 50 type=argparse.FileType("r"), 51 help="A bazel status file, e.g. bazel-out/stable-status.txt", 52 ) 53 parser.add_argument( 54 "template", 55 type=argparse.FileType("r"), 56 help="The input template file whose $(VARIABLES) are to be expanded.", 57 ) 58 parser.add_argument( 59 "out", 60 type=argparse.FileType("w"), 61 nargs="?", 62 default=sys.stdout, 63 help="The output file to which the expanded template is written.", 64 ) 65 66 args = parser.parse_args() 67 68 if len(args.status_files) < 1: 69 parser.error("At least one --status-file is required") 70 71 return args 72 73 74def main(): 75 args = _parse_args() 76 77 # Load all of the status files, merging them into a single dictionary. 78 replacements: dict[str, str] = {} 79 for status_file in args.status_files: 80 with status_file: 81 replacements.update(_load_status_file(status_file)) 82 83 # Load the template input file. 84 with args.template as file: 85 template = string.Template(args.template.read()) 86 87 # Expand the variables in the template. 88 try: 89 result = template.substitute(replacements) 90 except KeyError as err: 91 raise SystemExit(f"Invalid key found in input file: {err}") 92 93 # Write the result to the output file. 94 args.out.write(result) 95 96 97if __name__ == "__main__": 98 main() 99