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>
@@ -47,6 +53,8 @@ version = "0.10"
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 :], "hpvV " , ["help" , "patch " , "version" , "verbose" ,])
75+ except Exception as e :
6876 print ( "%s: %s" % (program , e ))
6977 sys .exit (1 )
7078
@@ -73,12 +81,15 @@ except Exception, e:
7381
7482# set default values, if any
7583opt_verbose = 0
84+ opt_patch = False
7685
7786# handle individual options
7887for opt , value in opts :
7988 if opt in ["-h" , "--help" ]: # Output this help, then exit
8089 print ( __doc__ % locals () )
8190 sys .exit (1 )
91+ elif opt in ["-p" , "--patch" ]: # Generate patch output rather than error messages
92+ opt_patch = True
8293 elif opt in ["-V" , "--version" ]: # Output version information, then exit
8394 print ( program , version )
8495 sys .exit (0 )
@@ -107,7 +118,7 @@ def pipe(cmd, inp=None):
107118 args = shlex .split (cmd )
108119 bufsize = 4096
109120 stdin = PIPE if inp else None
110- pipe = Popen (args , stdin = stdin , stdout = PIPE , stderr = PIPE , bufsize = bufsize )
121+ pipe = Popen (args , stdin = stdin , stdout = PIPE , stderr = PIPE , bufsize = bufsize , encoding = 'utf-8' , universal_newlines = True )
111122 out , err = pipe .communicate (inp )
112123 code = pipe .returncode
113124 if code != 0 :
@@ -156,9 +167,6 @@ import json
156167
157168cwd = os .getcwd ()
158169
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-
162170# Get current initinfo from cache and svn
163171cachefn = os .path .join (os .environ .get ('HOME' , '.' ), '.initinfo' )
164172
@@ -178,23 +186,66 @@ loginfo_format = r'^r[0-9]+ \| [^@]+@[^@]+ \| \d\d\d\d-\d\d-\d\d '
178186
179187year = time .strftime ('%Y' )
180188for 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 :
189+ try :
190+ if not os .path .exists (path ):
191+ note ("File does not exist: %s" % path )
191192 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 ))
193+ note ("Checking path %s" % path )
194+ if not path in initinfo :
195+ initinfo .update (get_first_commit (path ))
196+ write_cache = True
197+ date = initinfo [path ]['date' ]
198+ init = date [:4 ]
199+
200+ copyright_re = r"(?i)Copyright The IETF Trust (\d+-)?\d+, All Rights Reserved"
201+ copyright_year_re = r"(?i)Copyright The IETF Trust (%s-)?%s, All Rights Reserved" % (init , year )
202+ with open (path ) as file :
203+ try :
204+ chunk = file .read (4000 )
205+ except UnicodeDecodeError as e :
206+ sys .stderr .write (f'Error when reading { file .name } : { e } \n ' )
207+ raise
208+ if os .path .basename (path ) == '__init__.py' and len (chunk )== 0 :
209+ continue
210+ if not re .search (copyright_year_re , chunk ):
211+ if year == init :
212+ copyright = f"Copyright The IETF Trust { year } , All Rights Reserved"
213+ else :
214+ copyright = f"Copyright The IETF Trust { init } -{ year } , All Rights Reserved"
215+ if opt_patch :
216+ print (f"--- { file .name } \t (original)" )
217+ print (f"+++ { file .name } \t (modified)" )
218+ if not re .search (copyright_re , chunk ):
219+ # Simple case, just insert copyright at the top
220+ print ( "@@ -1,3 +1,4 @@" )
221+ print (f"+# { copyright } " )
222+ for i , line in list (enumerate (chunk .splitlines ()))[:3 ]:
223+ print (f" { line } " )
224+ else :
225+ # Find old copyright, then emit preceding lines,
226+ # change, and following lines.
227+ pos = None
228+ for i , line in enumerate (chunk .splitlines (), start = 1 ):
229+ if re .search (copyright_re , line ):
230+ pos = i
231+ break
232+ if not pos :
233+ raise RuntimeError ("Unexpected state: Expected a copyright line, but found none" )
234+ print (f"@@ -1,{ pos + 3 } +1,{ pos + 3 } @@" )
235+ for i , line in list (enumerate (chunk .splitlines (), start = 1 ))[:pos + 3 ]:
236+ if i == pos :
237+ print (f"-{ line } " )
238+ print (f"+# { copyright } " )
239+ else :
240+ print (f" { line } " )
241+ else :
242+ sys .stderr .write (f"{ path } (1): Error: Missing or bad copyright. Expected: { copyright } " )
243+ except Exception :
244+ if write_cache :
245+ cache = initinfo
246+ with open (cachefn , "w" ) as file :
247+ json .dump (cache , file , indent = 2 , sort_keys = True )
248+ raise
198249
199250if write_cache :
200251 cache = initinfo
0 commit comments