From b7e9fd14b03b94c3b66c3f6de6393b44f2d497bc Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Thu, 4 Jul 2019 13:00:55 +0000 Subject: [PATCH] Updated check-copyright to python3.7 and added a --patch switch - Legacy-Id: 16372 --- bin/check-copyright | 105 ++++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/bin/check-copyright b/bin/check-copyright index 2ae4ae208..ce21a3881 100755 --- a/bin/check-copyright +++ b/bin/check-copyright @@ -1,5 +1,5 @@ -#!/usr/bin/env python -# -*- python -*- +#!/usr/bin/env python3.7 +# -*- mode: python; coding: utf-8 -*- # Copyright The IETF Trust 2019, All Rights Reserved """ NAME @@ -10,12 +10,18 @@ SYNOPSIS DESCRIPTION Given a list of files or filename wildcard patterns, check all for - an IETF Trust copyright notice with the current year. + an IETF Trust copyright notice with the current year. Optionally + generate a diff on standard out which can be used by 'patch'. + + An invocation similar to the following can be particularly useful with + a set of changed version-controlled files, as it will fix up the + Copyright statements of any python files with pending changes: + + $ check-copyright -p $(svn st | cut -c 9- | grep '\.py$' ) | patch -p0 + %(options)s -FILES - AUTHOR Written by Henrik Levkowetz, @@ -47,6 +53,8 @@ version = "0.10" program = os.path.basename(sys.argv[0]) progdir = os.path.dirname(sys.argv[0]) +debug.debug = True + # ---------------------------------------------------------------------- # Parse options @@ -63,8 +71,8 @@ if len(sys.argv) < 1: sys.exit(1) try: - opts, files = getopt.gnu_getopt(sys.argv[1:], "hvV", ["help", "version", "verbose",]) -except Exception, e: + opts, files = getopt.gnu_getopt(sys.argv[1:], "hpvV", ["help", "patch", "version", "verbose",]) +except Exception as e: print( "%s: %s" % (program, e)) sys.exit(1) @@ -73,12 +81,15 @@ except Exception, e: # set default values, if any opt_verbose = 0 +opt_patch = False # handle individual options for opt, value in opts: if opt in ["-h", "--help"]: # Output this help, then exit print( __doc__ % locals() ) sys.exit(1) + elif opt in ["-p", "--patch"]: # Generate patch output rather than error messages + opt_patch = True elif opt in ["-V", "--version"]: # Output version information, then exit print( program, version ) sys.exit(0) @@ -107,7 +118,7 @@ def pipe(cmd, inp=None): args = shlex.split(cmd) bufsize = 4096 stdin = PIPE if inp else None - pipe = Popen(args, stdin=stdin, stdout=PIPE, stderr=PIPE, bufsize=bufsize) + pipe = Popen(args, stdin=stdin, stdout=PIPE, stderr=PIPE, bufsize=bufsize, encoding='utf-8', universal_newlines=True) out, err = pipe.communicate(inp) code = pipe.returncode if code != 0: @@ -156,9 +167,6 @@ import json cwd = os.getcwd() -if cwd.split(os.path.sep)[-1] != 'trunk': - die("Expected to run this operation in trunk, but the current\ndirectory is '%s'" % cwd) - # Get current initinfo from cache and svn cachefn = os.path.join(os.environ.get('HOME', '.'), '.initinfo') @@ -178,23 +186,66 @@ loginfo_format = r'^r[0-9]+ \| [^@]+@[^@]+ \| \d\d\d\d-\d\d-\d\d ' year = time.strftime('%Y') for path in files: - note("Checking path %s" % path) - if not path in initinfo: - initinfo.update(get_first_commit(path)) - write_cache = True - date = initinfo[path]['date'] - init = date[:4] - copyright = "(?i)Copyright The IETF Trust (%s-)?%s, All Rights Reserved" % (init, year) - with open(path) as file: - chunk = file.read(4000) - if os.path.basename(path) == '__init__.py' and len(chunk)==0: + try: + if not os.path.exists(path): + note("File does not exist: %s" % path) continue - if not re.search(copyright, chunk): - sys.stdout.write("%s(1): Error: Missing or bad copyright. " % path) - if year == init: - print(" Expected: Copyright The IETF Trust %s, All Rights Reserved" % year) - else: - print(" Expected: Copyright The IETF Trust %s-%s, All Rights Reserved" % (init, year)) + note("Checking path %s" % path) + if not path in initinfo: + initinfo.update(get_first_commit(path)) + write_cache = True + date = initinfo[path]['date'] + init = date[:4] + + copyright_re = r"(?i)Copyright The IETF Trust (\d+-)?\d+, All Rights Reserved" + copyright_year_re = r"(?i)Copyright The IETF Trust (%s-)?%s, All Rights Reserved" % (init, year) + with open(path) as file: + try: + chunk = file.read(4000) + except UnicodeDecodeError as e: + sys.stderr.write(f'Error when reading {file.name}: {e}\n') + raise + if os.path.basename(path) == '__init__.py' and len(chunk)==0: + continue + if not re.search(copyright_year_re, chunk): + if year == init: + copyright = f"Copyright The IETF Trust {year}, All Rights Reserved" + else: + copyright = f"Copyright The IETF Trust {init}-{year}, All Rights Reserved" + if opt_patch: + print(f"--- {file.name}\t(original)") + print(f"+++ {file.name}\t(modified)") + if not re.search(copyright_re, chunk): + # Simple case, just insert copyright at the top + print( "@@ -1,3 +1,4 @@") + print(f"+# {copyright}") + for i, line in list(enumerate(chunk.splitlines()))[:3]: + print(f" {line}") + else: + # Find old copyright, then emit preceding lines, + # change, and following lines. + pos = None + for i, line in enumerate(chunk.splitlines(), start=1): + if re.search(copyright_re, line): + pos = i + break + if not pos: + raise RuntimeError("Unexpected state: Expected a copyright line, but found none") + print(f"@@ -1,{pos+3} +1,{pos+3} @@") + for i, line in list(enumerate(chunk.splitlines(), start=1))[:pos+3]: + if i == pos: + print(f"-{line}") + print(f"+# {copyright}") + else: + print(f" {line}") + else: + sys.stderr.write(f"{path}(1): Error: Missing or bad copyright. Expected: {copyright}") + except Exception: + if write_cache: + cache = initinfo + with open(cachefn, "w") as file: + json.dump(cache, file, indent=2, sort_keys=True) + raise if write_cache: cache = initinfo