Salome HOME
At least 1 FILE is required
[tools/configuration.git] / copyright / insert_copyright
1 #!/usr/bin/env python
2 #  -*- coding: utf8 -*-
3
4 # Copyright (C) 2017  CEA/DEN, EDF R&D
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License Version 3 as
8 # published by the Free Software Foundation.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, you may download a copy of license
17 # from https://www.gnu.org/licenses/gpl-3.0.
18
19 """
20 Command line tool to insert copyright notice to a file.
21 Usage: type "insert_copyright --help" to learn how to use tool.
22 """
23
24 import argparse
25 import os
26 import re
27 import sys
28 import time
29
30 # pragma pylint: disable=redefined-builtin
31
32 # -----------------------------------------------------------------------------
33 _COMMENTS = {
34     'cpp': '//',
35     'shell': '#',
36     'python': '#',
37     'auto': None,
38     }
39
40 _OWNERS = {
41     'cea': 'CEA/DEN',
42     'edf': 'EDF R&D',
43     'occ': 'OPEN CASCADE'
44     }
45
46
47 # -----------------------------------------------------------------------------
48 def error_exit(msg):
49     """
50     Print error message to stderr and exit.
51
52     Arguments:
53         msg (str): Error message.
54     """
55     sys.stderr.write("ERROR: {}\n".format(msg))
56     sys.exit(-1)
57
58
59 # -----------------------------------------------------------------------------
60 def warning(msg):
61     """
62     Print wating message to stderr.
63
64     Arguments:
65         msg (str): Warning message.
66     """
67     sys.stderr.write("WARNING: {}\n".format(msg))
68
69
70 # -----------------------------------------------------------------------------
71 def formats():
72     """
73     Get supported formats of comments.
74
75     Returns:
76         list[str]: List of formats.
77     """
78     return list(_COMMENTS)
79
80
81 # -----------------------------------------------------------------------------
82 def search_line(lines, rex, depth=1):
83     """
84     Search regexp in given lines.
85
86     Arguments:
87         lines (list[str]): List of strings.
88         regex (str): Regular expression.
89         depth (Optional[int]): Depth of search. Defaults to 1 line.
90
91     Returns:
92         int: Index of first matched line.
93     """
94     for i in range(depth if depth >= 0 else len(lines)):
95         if i < len(lines):
96             if re.search(rex, lines[i]):
97                 return i
98     return -1
99
100
101 # -----------------------------------------------------------------------------
102 def get_owner(owner):
103     """
104     Get owner's title
105
106     Arguments:
107         owner (str): Owner's name of alias or list of owners separated
108             by comma.
109
110     Returns:
111         str: Owner's title.
112     """
113     if owner.lower() in 'all':
114         return get_owner('cea,edf,occ')
115
116     owners = [i.strip() for i in owner.split(',')]
117     result = []
118     for i in owners:
119         i = _OWNERS.get(i.lower(), i)
120         if i not in result:
121             result.append(i)
122     return ', '.join(result)
123
124
125 # -----------------------------------------------------------------------------
126 def get_comment(file_format):
127     """
128     Get comment for given format.
129
130     Arguments:
131         format (str): Format of comments.
132
133     Returns:
134         str: Comment signature for given format; *None* for unsupported
135         format.
136     """
137     return _COMMENTS.get(file_format) if file_format else None
138
139
140 # -----------------------------------------------------------------------------
141 def get_copyright(comment, owner, year):
142     """
143     Generate copyright from template.
144
145     Arguments:
146         comment (str): Comment signature.
147         owner (str): Copyright owner.
148         year (str): Copyright year(s).
149
150     Returns:
151         list[str]: List of strings with copyright data.
152     """
153     template = os.path.join(os.path.dirname(sys.argv[0]), 'copyright.template')
154     the_copyright = []
155     try:
156         with open(template) as fid:
157             the_copyright = fid.readlines()
158     except IOError:
159         error_exit("cannot find copyright template")
160     the_copyright = [i.replace('@year@', year) for i in the_copyright]
161     the_copyright = [i.replace('@owner@', owner) for i in the_copyright]
162     the_copyright = [comment + ' ' + i if i.strip() else comment + '\n'
163                      for i in the_copyright]
164     return the_copyright
165
166
167 # -----------------------------------------------------------------------------
168 def get_module_owner(module):
169     """
170     Get owner of given module.
171
172     Arguments:
173         module (str): Module name.
174
175     Returns:
176         str: Module's owner.
177     """
178     modules_info = os.path.join(os.path.dirname(sys.argv[0]), 'modules.info')
179     owner = None
180     try:
181         with open(modules_info) as fid:
182             lines = fid.readlines()
183             index = search_line(lines, r'^{}:'.format(module), -1)
184             if index >= 0:
185                 return get_owner(lines[index].split(":")[1].strip())
186     except IOError:
187         warning("cannot find modules info file")
188     return owner
189
190
191 # -----------------------------------------------------------------------------
192 def autodetect_owner(filename):
193     """
194     Auto-detect owner from file path.
195
196     Arguments:
197         filename (str): File path.
198
199     Returns:
200         str: Owner; *None* if owner isn't detected.
201     """
202     filename = os.path.realpath(filename)
203     if os.path.exists(filename):
204         directory = os.path.dirname(filename)
205         while directory != '/':
206             config_file = os.path.join(directory, '.git', 'config')
207             if os.path.exists(config_file):
208                 from ConfigParser import ConfigParser
209                 from StringIO import StringIO
210                 with open(config_file) as fid:
211                     gitcfg = fid.readlines()
212                     cfg = ConfigParser()
213                     data = StringIO(''.join([l.lstrip() for l in gitcfg]))
214                     cfg.readfp(data)
215                     url = cfg.get('remote "origin"', 'url')
216                     module = os.path.split(url)[-1]
217                     if module.endswith('.git'):
218                         module = module[:-4]
219                     return get_module_owner(module)
220                 break
221             directory = os.path.dirname(directory)
222     return None
223
224
225 # -----------------------------------------------------------------------------
226 def autodetect_format(filename):
227     """
228     Auto-detect format from filename.
229
230     Arguments:
231         filename (str): File path.
232
233     Returns:
234         str: Format of comments; *None* if format isn't detected.
235     """
236     extensions = {
237         'cpp': ('c', 'cpp', 'cxx', 'cc', 'c++',
238                 'h', 'hxx', 'hpp', 'hh', 'h++',
239                 'idl', 'i'),
240         'shell': ('sh', 'bash', 'csh', 'cmake', 'txt', 'cfg', 'ini', 'm4'),
241         'python': ('py',),
242         }
243     if filename and os.path.isfile(filename):
244         extension = os.path.splitext(filename)[1][1:].lower()
245         if extension in ('in',):
246             name = os.path.splitext(filename)[0]
247             extension = os.path.splitext(name)[1][1:].lower()
248         for file_format in extensions:
249             if extension in extensions[file_format]:
250                 return format
251     return None
252
253
254 # -----------------------------------------------------------------------------
255 def insert_copyright(filename, owner, year, file_format):
256     """
257     Insert copyright note to a file.
258
259     Arguments:
260         filename (str): File path.
261         owner (str): Copyright owner.
262         year (str): Copyright year(s).
263         file_format (str): Format of comments.
264     """
265     try:
266         with open(filename) as fid:
267             lines = fid.readlines()
268     except IOError:
269         warning("cannot read file: {}".format(filename))
270         return
271
272     if file_format in ('auto',):
273         file_format = autodetect_format(filename)
274
275     if owner.lower() in ('auto',):
276         owner = autodetect_owner(filename) or get_owner('all')
277     else:
278         owner = get_owner(owner)
279
280     comment = get_comment(file_format)
281     if comment is None:
282         warning("cannot detect format")
283         return
284
285     shell_row = search_line(lines, r'^#!') \
286         if file_format in ('sh', 'bash', 'csh', 'py', 'python') else -1
287     coding_row = search_line(lines, r'coding:', 3) \
288         if file_format in ('py', 'python') else -1
289     insert_point = max(0, shell_row + 1, coding_row + 1)
290
291     the_copyright = get_copyright(comment, owner, year)
292     if the_copyright:
293         lines = lines[:insert_point] + the_copyright + ['\n'] \
294             + lines[insert_point:]
295         try:
296             with open(filename, 'w') as fid:
297                 for line in lines:
298                     fid.write(line)
299         except IOError:
300             warning("cannot write file: {}".format(filename))
301             return
302
303
304 # -----------------------------------------------------------------------------
305 def main():
306     """Main function."""
307
308     # Parse command line.
309     description = "Command line tool to insert copyright notice to a file."
310     parser = argparse.ArgumentParser(description=description)
311
312     help_string = "copyright owner; if not specified, tool tries to " \
313         "autodetect an owner from the file path; if auto-detection fails, " \
314         "an owner is set to '{owner}'"
315     owner = 'auto'
316     parser.add_argument("-o", "--owner", action="store",
317                         dest="owner", default=owner,
318                         help=help_string.format(owner=get_owner('all')))
319     help_string = "copyright year(s); default: current year ({year})"
320     year = str(time.localtime().tm_year)
321     parser.add_argument("-y", "--year", action="store",
322                         dest="year", default=year,
323                         help=help_string.format(year=year))
324     help_string = "format of comments ({choices}); default: {file_format}"
325     file_format = 'auto'
326     parser.add_argument("-f", "--format", action="store", choices=formats(),
327                         dest="format", default=file_format,
328                         help=help_string.format(file_format=file_format,
329                                                 choices="|".join(formats())))
330     parser.add_argument('files', nargs='+', metavar='FILE')
331
332     args = parser.parse_args(sys.argv[1:])
333
334     owner = args.owner
335     year = args.year
336     file_format = args.format
337     files = args.files
338
339     for filename in files:
340         insert_copyright(filename, owner, year, file_format)
341
342     return 0
343
344 # -----------------------------------------------------------------------------
345 if __name__ == "__main__":
346     sys.exit(main())