1- #!/usr/bin/env python
2- # -*- python -*-
1+ #!/usr/bin/env python3.7
2+ # -*- mode: python; coding: utf-8 -*-
33# Copyright The IETF Trust 2019, All Rights Reserved
44"""
55NAME
@@ -10,11 +10,17 @@ SYNOPSIS
1010
1111DESCRIPTION
1212 Given a list of files or filename wildcard patterns, check all for
13- an IETF Trust copyright notice with the current year.
13+ an IETF Trust copyright notice with the current year. Optionally
14+ generate a diff on standard out which can be used by 'patch'.
1415
15- %(options)s
16+ An invocation similar to the following can be particularly useful with
17+ a set of changed version-controlled files, as it will fix up the
18+ Copyright statements of any python files with pending changes:
19+
20+ $ check-copyright -p $(svn st | cut -c 9- | grep '\.py$' ) | patch -p0
1621
17- FILES
22+
23+ %(options)s
1824
1925AUTHOR
2026 Written by Henrik Levkowetz, <henrik@tools.ietf.org>
@@ -43,10 +49,12 @@ import pytz
4349import tzparse
4450import debug
4551
46- version = "0.10 "
52+ version = "1.0.0 "
4753program = os .path .basename (sys .argv [0 ])
4854progdir = os .path .dirname (sys .argv [0 ])
4955
56+ debug .debug = True
57+
5058# ----------------------------------------------------------------------
5159# Parse options
5260
@@ -63,8 +71,8 @@ if len(sys.argv) < 1:
6371 sys .exit (1 )
6472
6573try :
66- opts , files = getopt .gnu_getopt (sys .argv [1 :], "hvV " , ["help" , "version" , "verbose" ,])
67- except Exception , e :
74+ opts , files = getopt .gnu_getopt (sys .argv [1 :], "hC:pvV " , ["help" , "copyright=" , "patch " , "version" , "verbose" ,])
75+ except Exception as e :
6876 print ( "%s: %s" % (program , e ))
6977 sys .exit (1 )
7078
@@ -73,16 +81,22 @@ except Exception, e:
7381
7482# set default values, if any
7583opt_verbose = 0
84+ opt_patch = False
85+ opt_copyright = "Copyright The IETF Trust {years}, All Rights Reserved"
7686
7787# handle individual options
7888for opt , value in opts :
7989 if opt in ["-h" , "--help" ]: # Output this help, then exit
8090 print ( __doc__ % locals () )
8191 sys .exit (1 )
92+ elif opt in ["-p" , "--patch" ]: # Generate patch output rather than error messages
93+ opt_patch = True
94+ elif opt in ["-C" , "--copyright" ]: # Copyright line pattern using {years} for years
95+ opt_copyright = value
8296 elif opt in ["-V" , "--version" ]: # Output version information, then exit
8397 print ( program , version )
8498 sys .exit (0 )
85- elif opt in ["-v" , "--verbose" ]: # Output version information, then exit
99+ elif opt in ["-v" , "--verbose" ]: # Be more verbose
86100 opt_verbose += 1
87101
88102# ----------------------------------------------------------------------
@@ -107,7 +121,7 @@ def pipe(cmd, inp=None):
107121 args = shlex .split (cmd )
108122 bufsize = 4096
109123 stdin = PIPE if inp else None
110- pipe = Popen (args , stdin = stdin , stdout = PIPE , stderr = PIPE , bufsize = bufsize )
124+ pipe = Popen (args , stdin = stdin , stdout = PIPE , stderr = PIPE , bufsize = bufsize , encoding = 'utf-8' , universal_newlines = True )
111125 out , err = pipe .communicate (inp )
112126 code = pipe .returncode
113127 if code != 0 :
@@ -156,9 +170,6 @@ import json
156170
157171cwd = os .getcwd ()
158172
159- if cwd .split (os .path .sep )[- 1 ] != 'trunk' :
160- die ("Expected to run this operation in trunk, but the current\n directory is '%s'" % cwd )
161-
162173# Get current initinfo from cache and svn
163174cachefn = os .path .join (os .environ .get ('HOME' , '.' ), '.initinfo' )
164175
@@ -177,24 +188,67 @@ write_cache = False
177188loginfo_format = r'^r[0-9]+ \| [^@]+@[^@]+ \| \d\d\d\d-\d\d-\d\d '
178189
179190year = time .strftime ('%Y' )
191+ copyright_re = "(?i)" + opt_copyright .format (years = r"(\d+-)?\d+" )
180192for path in files :
181- note ("Checking path %s" % path )
182- if not path in initinfo :
183- initinfo .update (get_first_commit (path ))
184- write_cache = True
185- date = initinfo [path ]['date' ]
186- init = date [:4 ]
187- copyright = "(?i)Copyright The IETF Trust (%s-)?%s, All Rights Reserved" % (init , year )
188- with open (path ) as file :
189- chunk = file .read (4000 )
190- if os .path .basename (path ) == '__init__.py' and len (chunk )== 0 :
193+ try :
194+ if not os .path .exists (path ):
195+ note ("File does not exist: %s" % path )
191196 continue
192- if not re .search (copyright , chunk ):
193- sys .stdout .write ("%s(1): Error: Missing or bad copyright. " % path )
194- if year == init :
195- print (" Expected: Copyright The IETF Trust %s, All Rights Reserved" % year )
196- else :
197- print (" Expected: Copyright The IETF Trust %s-%s, All Rights Reserved" % (init , year ))
197+ note ("Checking path %s" % path )
198+ if not path in initinfo :
199+ initinfo .update (get_first_commit (path ))
200+ write_cache = True
201+ date = initinfo [path ]['date' ]
202+ init = date [:4 ]
203+
204+ copyright_year_re = "(?i)" + opt_copyright .format (years = r"({init}-)?{year}" .format (init = init , year = year ))
205+ with open (path ) as file :
206+ try :
207+ chunk = file .read (4000 )
208+ except UnicodeDecodeError as e :
209+ sys .stderr .write (f'Error when reading { file .name } : { e } \n ' )
210+ raise
211+ if os .path .basename (path ) == '__init__.py' and len (chunk )== 0 :
212+ continue
213+ if not re .search (copyright_year_re , chunk ):
214+ if year == init :
215+ copyright = opt_copyright .format (years = year )
216+ else :
217+ copyright = opt_copyright .format (years = f"{ init } -{ year } " )
218+ if opt_patch :
219+ print (f"--- { file .name } \t (original)" )
220+ print (f"+++ { file .name } \t (modified)" )
221+ if not re .search (copyright_re , chunk ):
222+ # Simple case, just insert copyright at the top
223+ print ( "@@ -1,3 +1,4 @@" )
224+ print (f"+# { copyright } " )
225+ for i , line in list (enumerate (chunk .splitlines ()))[:3 ]:
226+ print (f" { line } " )
227+ else :
228+ # Find old copyright, then emit preceding lines,
229+ # change, and following lines.
230+ pos = None
231+ for i , line in enumerate (chunk .splitlines (), start = 1 ):
232+ if re .search (copyright_re , line ):
233+ pos = i
234+ break
235+ if not pos :
236+ raise RuntimeError ("Unexpected state: Expected a copyright line, but found none" )
237+ print (f"@@ -1,{ pos + 3 } +1,{ pos + 3 } @@" )
238+ for i , line in list (enumerate (chunk .splitlines (), start = 1 ))[:pos + 3 ]:
239+ if i == pos :
240+ print (f"-{ line } " )
241+ print (f"+# { copyright } " )
242+ else :
243+ print (f" { line } " )
244+ else :
245+ sys .stderr .write (f"{ path } (1): Error: Missing or bad copyright. Expected: { copyright } " )
246+ except Exception :
247+ if write_cache :
248+ cache = initinfo
249+ with open (cachefn , "w" ) as file :
250+ json .dump (cache , file , indent = 2 , sort_keys = True )
251+ raise
198252
199253if write_cache :
200254 cache = initinfo
0 commit comments