4 # Copyright (C) 2017 CEA/DEN, EDF R&D
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.
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.
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.
20 Command line tool to insert copyright notice to a file.
21 Usage: type "insert_copyright --help" to learn how to use tool.
30 # pragma pylint: disable=redefined-builtin
32 # -----------------------------------------------------------------------------
47 # -----------------------------------------------------------------------------
50 Print error message to stderr and exit.
53 msg (str): Error message.
55 sys.stderr.write("ERROR: {}\n".format(msg))
59 # -----------------------------------------------------------------------------
62 Print wating message to stderr.
65 msg (str): Warning message.
67 sys.stderr.write("WARNING: {}\n".format(msg))
70 # -----------------------------------------------------------------------------
73 Get supported formats of comments.
76 list[str]: List of formats.
78 return list(_COMMENTS)
81 # -----------------------------------------------------------------------------
82 def search_line(lines, rex, depth=1):
84 Search regexp in given lines.
87 lines (list[str]): List of strings.
88 regex (str): Regular expression.
89 depth (Optional[int]): Depth of search. Defaults to 1 line.
92 int: Index of first matched line.
94 for i in range(depth if depth >= 0 else len(lines)):
96 if re.search(rex, lines[i]):
101 # -----------------------------------------------------------------------------
102 def get_owner(owner):
107 owner (str): Owner's name of alias or list of owners separated
113 if owner.lower() in 'all':
114 return get_owner('cea,edf,occ')
116 owners = [i.strip() for i in owner.split(',')]
119 i = _OWNERS.get(i.lower(), i)
122 return ', '.join(result)
125 # -----------------------------------------------------------------------------
126 def get_comment(file_format):
128 Get comment for given format.
131 format (str): Format of comments.
134 str: Comment signature for given format; *None* for unsupported
137 return _COMMENTS.get(file_format) if file_format else None
140 # -----------------------------------------------------------------------------
141 def get_copyright(comment, owner, year):
143 Generate copyright from template.
146 comment (str): Comment signature.
147 owner (str): Copyright owner.
148 year (str): Copyright year(s).
151 list[str]: List of strings with copyright data.
153 template = os.path.join(os.path.dirname(sys.argv[0]), 'copyright.template')
156 with open(template) as fid:
157 the_copyright = fid.readlines()
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]
167 # -----------------------------------------------------------------------------
168 def get_module_owner(module):
170 Get owner of given module.
173 module (str): Module name.
178 modules_info = os.path.join(os.path.dirname(sys.argv[0]), 'modules.info')
181 with open(modules_info) as fid:
182 lines = fid.readlines()
183 index = search_line(lines, r'^{}:'.format(module), -1)
185 return get_owner(lines[index].split(":")[1].strip())
187 warning("cannot find modules info file")
191 # -----------------------------------------------------------------------------
192 def autodetect_owner(filename):
194 Auto-detect owner from file path.
197 filename (str): File path.
200 str: Owner; *None* if owner isn't detected.
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):
209 from ConfigParser import ConfigParser
211 from configparser import ConfigParser
213 from StringIO import StringIO
215 from io import StringIO
216 with open(config_file) as fid:
217 gitcfg = fid.readlines()
219 data = StringIO(''.join([l.lstrip() for l in gitcfg]))
221 url = cfg.get('remote "origin"', 'url')
222 module = os.path.split(url)[-1]
223 if module.endswith('.git'):
225 return get_module_owner(module)
227 directory = os.path.dirname(directory)
231 # -----------------------------------------------------------------------------
232 def autodetect_format(filename):
234 Auto-detect format from filename.
237 filename (str): File path.
240 str: Format of comments; *None* if format isn't detected.
243 'cpp': ('c', 'cpp', 'cxx', 'cc', 'c++',
244 'h', 'hxx', 'hpp', 'hh', 'h++',
246 'shell': ('sh', 'bash', 'csh', 'cmake', 'txt', 'cfg', 'ini', 'm4'),
249 rev_extensions = {e: k for k, exts in extensions.items() for e in exts}
250 if filename and os.path.isfile(filename):
251 extension = os.path.splitext(filename)[1][1:].lower()
252 if extension in ('in',):
253 name = os.path.splitext(filename)[0]
254 extension = os.path.splitext(name)[1][1:].lower()
255 if extension in rev_extensions:
256 return rev_extensions[extension]
260 m = magic.open(magic.MAGIC_MIME_TYPE)
263 'cpp': ('text/x-c', 'text/x-c++'),
264 'shell': ('text/x-shellscript',),
265 'python': ('text/x-python',),
267 rev_file_formats = {f: k for k, ff in file_formats.items() for f in ff}
268 file_format = m.file(filename)
269 if file_format in rev_file_formats:
270 return rev_file_formats[file_format]
277 # -----------------------------------------------------------------------------
278 def insert_copyright(filename, owner, year, file_format):
280 Insert copyright note to a file.
283 filename (str): File path.
284 owner (str): Copyright owner.
285 year (str): Copyright year(s).
286 file_format (str): Format of comments.
289 with open(filename) as fid:
290 lines = fid.readlines()
292 warning("cannot read file: {}".format(filename))
295 if file_format in ('auto',):
296 file_format = autodetect_format(filename)
298 if owner.lower() in ('auto',):
299 owner = autodetect_owner(filename) or get_owner('all')
301 owner = get_owner(owner)
303 comment = get_comment(file_format)
305 warning("cannot detect format")
308 shell_row = search_line(lines, r'^#!') \
309 if file_format in ('sh', 'bash', 'csh', 'py', 'python') else -1
310 coding_row = search_line(lines, r'coding:', 3) \
311 if file_format in ('py', 'python') else -1
312 insert_point = max(0, shell_row + 1, coding_row + 1)
314 the_copyright = get_copyright(comment, owner, year)
316 lines = lines[:insert_point] + the_copyright + ['\n'] \
317 + lines[insert_point:]
319 with open(filename, 'w') as fid:
323 warning("cannot write file: {}".format(filename))
327 # -----------------------------------------------------------------------------
331 # Parse command line.
332 description = "Command line tool to insert copyright notice to a file."
333 parser = argparse.ArgumentParser(description=description)
335 help_string = "copyright owner; if not specified, tool tries to " \
336 "autodetect an owner from the file path; if auto-detection fails, " \
337 "an owner is set to '{owner}'"
339 parser.add_argument("-o", "--owner", action="store",
340 dest="owner", default=owner,
341 help=help_string.format(owner=get_owner('all')))
342 help_string = "copyright year(s); default: current year ({year})"
343 year = str(time.localtime().tm_year)
344 parser.add_argument("-y", "--year", action="store",
345 dest="year", default=year,
346 help=help_string.format(year=year))
347 help_string = "format of comments ({choices}); default: {file_format}"
349 parser.add_argument("-f", "--format", action="store", choices=formats(),
350 dest="format", default=file_format,
351 help=help_string.format(file_format=file_format,
352 choices="|".join(formats())))
353 parser.add_argument('files', nargs='+', metavar='FILE')
355 args = parser.parse_args(sys.argv[1:])
359 file_format = args.format
362 for filename in files:
363 insert_copyright(filename, owner, year, file_format)
367 # -----------------------------------------------------------------------------
368 if __name__ == "__main__":