@@ -6,10 +6,13 @@ derives the authorship and copyright years information from the git history
6
6
of the project; hence, this script must be run within a git repository.
7
7
"""
8
8
9
+ from __future__ import annotations
10
+
9
11
import os
10
12
import sys
11
13
assert sys .version_info >= (3 , 6 ), 'Script requires Python 3.6+'
12
14
import tempfile
15
+ import typing as t
13
16
from argparse import ArgumentParser , Namespace
14
17
from configparser import ConfigParser
15
18
from operator import attrgetter
@@ -18,14 +21,13 @@ from datetime import datetime
18
21
from subprocess import Popen , PIPE , DEVNULL
19
22
from pathlib import Path
20
23
from fnmatch import fnmatch
21
- from typing import NamedTuple , Iterator , List , Tuple , Set , Union , Optional
22
24
23
25
24
26
SPDX_PREFIX = 'SPDX-License-Identifier:'
25
27
COPYRIGHT_PREFIX = 'Copyright (c)'
26
28
27
29
28
- def main (args : List [str ] = None ):
30
+ def main (args : t . List [str ] = None ):
29
31
if args is None :
30
32
args = sys .argv [1 :]
31
33
config = get_config (args )
@@ -41,7 +43,7 @@ def main(args: List[str] = None):
41
43
target .write (chunk )
42
44
43
45
44
- def get_config (args : List [str ]) -> Namespace :
46
+ def get_config (args : t . List [str ]) -> Namespace :
45
47
config = ConfigParser (
46
48
defaults = {
47
49
'include' : '**/*' ,
@@ -52,10 +54,10 @@ def get_config(args: List[str]) -> Namespace:
52
54
'spdx_prefix' : SPDX_PREFIX ,
53
55
'copy_prefix' : COPYRIGHT_PREFIX ,
54
56
},
55
- delimiters = ('=' ,), default_section = 'settings' ,
57
+ delimiters = ('=' ,), default_section = 'copyrights: settings' ,
56
58
empty_lines_in_values = False , interpolation = None ,
57
59
converters = {'list' : lambda s : s .strip ().splitlines () })
58
- config .read ('copyrights .cfg' )
60
+ config .read ('setup .cfg' )
59
61
sect = config [config .default_section ]
60
62
61
63
parser = ArgumentParser (description = __doc__ )
@@ -113,10 +115,10 @@ def get_config(args: List[str]) -> Namespace:
113
115
return ns
114
116
115
117
116
- class Copyright (NamedTuple ):
118
+ class Copyright (t . NamedTuple ):
117
119
author : str
118
120
email : str
119
- years : Set [int ]
121
+ years : t . Set [int ]
120
122
121
123
def __str__ (self ):
122
124
if len (self .years ) > 1 :
@@ -126,8 +128,8 @@ class Copyright(NamedTuple):
126
128
return f'{ years } { self .author } <{ self .email } >'
127
129
128
130
129
- def get_copyrights (include : Set [str ], exclude : Set [str ])\
130
- -> Iterator [Tuple [Path , List [Copyright ]]]:
131
+ def get_copyrights (include : t . Set [str ], exclude : t . Set [str ])\
132
+ -> t . Iterator [t . Tuple [Path , t . Container [Copyright ]]]:
131
133
sorted_blame = sorted (
132
134
get_contributions (include , exclude ),
133
135
key = lambda c : (c .path , c .author , c .email )
@@ -147,15 +149,15 @@ def get_copyrights(include: Set[str], exclude: Set[str])\
147
149
yield path , copyrights
148
150
149
151
150
- class Contribution (NamedTuple ):
152
+ class Contribution (t . NamedTuple ):
151
153
author : str
152
154
email : str
153
155
year : int
154
156
path : Path
155
157
156
158
157
- def get_contributions (include : Set [str ], exclude : Set [str ])\
158
- -> Iterator [Contribution ]:
159
+ def get_contributions (include : t . Set [str ], exclude : t . Set [str ])\
160
+ -> t . Iterator [Contribution ]:
159
161
for path in get_source_paths (include , exclude ):
160
162
blame = Popen (
161
163
['git' , 'blame' , '--line-porcelain' , 'HEAD' , '--' , str (path )],
@@ -186,7 +188,8 @@ def get_contributions(include: Set[str], exclude: Set[str])\
186
188
assert blame .returncode == 0
187
189
188
190
189
- def get_source_paths (include : Set [str ], exclude : Set [str ]) -> Iterator [Path ]:
191
+ def get_source_paths (include : t .Set [str ], exclude : t .Set [str ])\
192
+ -> t .Iterator [Path ]:
190
193
ls_tree = Popen (
191
194
['git' , 'ls-tree' , '-r' , '--name-only' , 'HEAD' ],
192
195
stdout = PIPE , stderr = DEVNULL , universal_newlines = True )
@@ -203,9 +206,9 @@ def get_source_paths(include: Set[str], exclude: Set[str]) -> Iterator[Path]:
203
206
assert ls_tree .returncode == 0
204
207
205
208
206
- class License (NamedTuple ):
207
- ident : Optional [str ]
208
- text : List [str ]
209
+ class License (t . NamedTuple ):
210
+ ident : t . Optional [str ]
211
+ text : t . List [str ]
209
212
210
213
211
214
def get_license (path : Path , * , spdx_prefix : str = SPDX_PREFIX ) -> License :
@@ -253,20 +256,26 @@ class CopyWriter:
253
256
'.sql' : '--' ,
254
257
}
255
258
256
- def __init__ (self , license = 'LICENSE.txt' , preamble = (),
257
- spdx_prefix = SPDX_PREFIX , copy_prefix = COPYRIGHT_PREFIX ):
259
+ def __init__ (self , license : Path = Path ('LICENSE.txt' ),
260
+ preamble : t .List [str ]= None ,
261
+ spdx_prefix : str = SPDX_PREFIX ,
262
+ copy_prefix : str = COPYRIGHT_PREFIX ):
263
+ if preamble is None :
264
+ preamble = []
258
265
self .license = get_license (license , spdx_prefix = spdx_prefix )
259
266
self .preamble = preamble
260
267
self .spdx_prefix = spdx_prefix
261
268
self .copy_prefix = copy_prefix
262
269
263
270
@classmethod
264
- def from_config (cls , config ) :
271
+ def from_config (cls , config : Namespace ) -> CopyWriter :
265
272
return cls (
266
273
config .license , config .preamble ,
267
274
config .spdx_prefix , config .copy_prefix )
268
275
269
- def transform (self , source , copyrights , * , comment_prefix = None ):
276
+ def transform (self , source : t .TextIO ,
277
+ copyrights : t .List [Copyright ], * ,
278
+ comment_prefix : str = None ) -> t .Iterator [str ]:
270
279
if comment_prefix is None :
271
280
comment_prefix = self .COMMENTS [Path (source .name ).suffix ]
272
281
license_start = self .license .text [0 ]
@@ -279,7 +288,7 @@ class CopyWriter:
279
288
yield line
280
289
empty = False
281
290
elif linenum < 3 and (
282
- 'set fileencoding=' in line or '-*- coding:' in line ):
291
+ 'fileencoding=' in line or '-*- coding:' in line ):
283
292
yield line
284
293
empty = False
285
294
elif line .rstrip () == comment_prefix :
@@ -313,7 +322,8 @@ class CopyWriter:
313
322
elif state == 'body' :
314
323
yield line
315
324
316
- def _generate_header (self , copyrights , comment_prefix , empty ):
325
+ def _generate_header (self , copyrights : t .Iterable [Copyright ],
326
+ comment_prefix : str , empty : bool ) -> t .Iterator [str ]:
317
327
if not empty :
318
328
yield comment_prefix + '\n '
319
329
for line in self .preamble :
@@ -356,7 +366,7 @@ class AtomicReplaceFile:
356
366
If ``None`` (the default), the temporary file will be opened in binary
357
367
mode. Otherwise, this specifies the encoding to use with text mode.
358
368
"""
359
- def __init__ (self , path : Union [str , Path ], encoding : str = None ):
369
+ def __init__ (self , path : t . Union [str , Path ], encoding : str = None ):
360
370
if isinstance (path , str ):
361
371
path = Path (path )
362
372
self ._path = path
0 commit comments