#!/usr/bin/env python3 # Copyright 2020 The IREE Authors # # Licensed under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception """Prepends a license header to files that don't already have one. By default, only operates on known filetypes but behavior can be overridden with flags. Ignores files already containing a license as determined by the presence of a block that looks like "Copyright SOME_YEAR" """ import argparse import datetime import os import re import sys COPYRIGHT_PATTERN = re.compile(r"Copyright\s+\d+") LICENSE_HEADER_FORMATTER = """{shebang}{start_comment} Copyright {year} {holder} {middle_comment} Licensed under the Apache License v2.0 with LLVM Exceptions. {middle_comment} See https://llvm.org/LICENSE.txt for license information. {middle_comment} SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception{end_comment} """ class CommentSyntax(object): def __init__(self, start_comment, middle_comment=None, end_comment=""): self.start_comment = start_comment self.middle_comment = middle_comment if middle_comment else start_comment self.end_comment = end_comment def comment_arg_parser(v): """Can be used to parse a comment syntax triple.""" if v is None: return None if not isinstance(v, str): raise argparse.ArgumentTypeError("String expected") return CommentSyntax(*v.split(",")) def create_multikey(d): # pylint: disable=g-complex-comprehension return {k: v for keys, v in d.items() for k in keys} filename_to_comment = create_multikey( { ("BUILD", "CMakeLists.txt"): CommentSyntax("#"), } ) ext_to_comment = create_multikey( { (".bzl", ".cfg", ".cmake", ".overlay", ".py", ".sh", ".yml"): CommentSyntax( "#" ), (".cc", ".cpp", ".comp", ".fbs", ".h", ".hpp", ".inc", ".td"): CommentSyntax( "//" ), (".def",): CommentSyntax(";;"), } ) def get_comment_syntax(args): """Deterime the comment syntax to use.""" if args.comment: return args.comment basename = os.path.basename(args.filename) from_filename = filename_to_comment.get(basename) if from_filename: return from_filename _, ext = os.path.splitext(args.filename) return ext_to_comment.get(ext, args.default_comment) def parse_arguments(): """Parses command line arguments.""" current_year = datetime.date.today().year parser = argparse.ArgumentParser() input_group = parser.add_mutually_exclusive_group() input_group.add_argument( "infile", nargs="?", type=argparse.FileType("r", encoding="UTF-8"), help="Input file to format. Default: stdin", default=sys.stdin, ) parser.add_argument( "--filename", "--assume-filename", type=str, default=None, help=( "Filename to use for determining comment syntax. Default: actual name" "of input file." ), ) parser.add_argument( "--year", "-y", help="Year to add copyright. Default: the current year ({})".format( current_year ), default=current_year, ) parser.add_argument( "--holder", help="Copyright holder. Default: The IREE Authors", default="The IREE Authors", ) parser.add_argument( "--quiet", help=( "Don't raise a runtime error on encountering an unhandled filetype." "Useful for running across many files at once. Default: False" ), action="store_true", default=False, ) output_group = parser.add_mutually_exclusive_group() output_group.add_argument( "-o", "--outfile", "--output", help="File to send output. Default: stdout", type=argparse.FileType("w", encoding="UTF-8"), default=sys.stdout, ) output_group.add_argument( "--in_place", "-i", action="store_true", help="Run formatting in place. Default: False", default=False, ) comment_group = parser.add_mutually_exclusive_group() comment_group.add_argument( "--comment", "-c", type=comment_arg_parser, help="Override comment syntax.", default=None, ) comment_group.add_argument( "--default_comment", type=comment_arg_parser, help="Fallback comment syntax if filename is unknown. Default: None", default=None, ) args = parser.parse_args() if args.in_place and args.infile == sys.stdin: raise parser.error("Cannot format stdin in place") if not args.filename and args.infile != sys.stdin: args.filename = args.infile.name return args def main(args): first_line = args.infile.readline() already_has_license = False shebang = "" content_lines = [] if first_line.startswith("#!"): shebang = first_line else: content_lines = [first_line] content_lines.extend(args.infile.readlines()) for line in content_lines: if COPYRIGHT_PATTERN.search(line): already_has_license = True break if already_has_license: header = shebang else: comment_syntax = get_comment_syntax(args) if not comment_syntax: if args.quiet: header = shebang else: raise ValueError( "Could not determine comment syntax for " + args.filename ) else: header = LICENSE_HEADER_FORMATTER.format( # Add a blank line between shebang and license. shebang=(shebang + "\n" if shebang else ""), start_comment=comment_syntax.start_comment, middle_comment=comment_syntax.middle_comment, # Add a blank line before the end comment. end_comment=( "\n" + comment_syntax.end_comment if comment_syntax.end_comment else "" ), year=args.year, holder=args.holder, ) # Have to open for write after we're done reading. if args.in_place: args.outfile = open(args.filename, "w", encoding="UTF-8") args.outfile.write(header) args.outfile.writelines(content_lines) if __name__ == "__main__": main(parse_arguments())