Improve unicode handling and increase the default textwrap width.
[update-copyright.git] / update_copyright / utils.py
1 # Copyright (C) 2012 W. Trevor King
2 #
3 # This file is part of update-copyright.
4 #
5 # update-copyright is free software: you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 #
10 # update-copyright is distributed in the hope that it will be useful,
11 # but 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 update-copyright.  If not, see
17 # <http://www.gnu.org/licenses/>.
18
19 import codecs as _codecs
20 import difflib as _difflib
21 import locale as _locale
22 import os as _os
23 import os.path as _os_path
24 import sys as _sys
25 import textwrap as _textwrap
26 import time as _time
27
28 from . import LOG as _LOG
29
30
31 ENCODING = _locale.getpreferredencoding() or _sys.getdefaultencoding()
32
33
34 def long_author_formatter(copyright_year_string, authors):
35     """
36     >>> print '\\n'.join(long_author_formatter(
37     ...     copyright_year_string='Copyright (C) 1990-2010',
38     ...     authors=['Jack', 'Jill', 'John']))
39     Copyright (C) 1990-2010 Jack
40                             Jill
41                             John
42     """
43     lines = ['%s %s' % (copyright_year_string, authors[0])]
44     for author in authors[1:]:
45         lines.append(' '*(len(copyright_year_string)+1) + author)
46     return lines
47
48 def short_author_formatter(copyright_year_string, authors):
49     """
50     >>> print '\\n'.join(short_author_formatter(
51     ...     copyright_year_string='Copyright (C) 1990-2010',
52     ...     authors=['Jack', 'Jill', 'John']*5))
53     Copyright (C) 1990-2010 Jack, Jill, John, Jack, Jill, John, Jack, Jill, John, Jack, Jill, John, Jack, Jill, John
54     """
55     blurb = '%s %s' % (copyright_year_string, ', '.join(authors))
56     return [blurb]
57
58 def copyright_string(original_year, final_year, authors, text, info={},
59                      author_format_fn=long_author_formatter,
60                      formatter_kwargs={}, prefix='', wrap=True,
61                      **wrap_kwargs):
62     """
63     >>> print(copyright_string(original_year=2005, final_year=2005,
64     ...                        authors=['A <a@a.com>', 'B <b@b.edu>'],
65     ...                        text=['BLURB',], prefix='# '
66     ...                        )) # doctest: +REPORT_UDIFF
67     # Copyright (C) 2005 A <a@a.com>
68     #                    B <b@b.edu>
69     #
70     # BLURB
71     >>> print(copyright_string(original_year=2005, final_year=2009,
72     ...                        authors=['A <a@a.com>', 'B <b@b.edu>'],
73     ...                        text=['BLURB',]
74     ...                        )) # doctest: +REPORT_UDIFF
75     Copyright (C) 2005-2009 A <a@a.com>
76                             B <b@b.edu>
77     <BLANKLINE>
78     BLURB
79     >>> print(copyright_string(original_year=2005, final_year=2005,
80     ...                        authors=['A <a@a.com>', 'B <b@b.edu>'],
81     ...                        text=['This file is part of %(program)s.',],
82     ...                        author_format_fn=short_author_formatter,
83     ...                        info={'program':'update-copyright'},
84     ...                        width=25,
85     ...                        )) # doctest: +REPORT_UDIFF
86     Copyright (C) 2005 A <a@a.com>, B <b@b.edu>
87     <BLANKLINE>
88     This file is part of
89     update-copyright.
90     >>> print(copyright_string(original_year=2005, final_year=2005,
91     ...                        authors=['A <a@a.com>', 'B <b@b.edu>'],
92     ...                        text=[('This file is part of %(program)s.  '*3
93     ...                               ).strip(),],
94     ...                        info={'program':'update-copyright'},
95     ...                        author_format_fn=short_author_formatter,
96     ...                        wrap=False,
97     ...                        )) # doctest: +REPORT_UDIFF
98     Copyright (C) 2005 A <a@a.com>, B <b@b.edu>
99     <BLANKLINE>
100     This file is part of update-copyright.  This file is part of update-copyright.  This file is part of update-copyright.
101     """
102     for key in ['initial_indent', 'subsequent_indent']:
103         if key not in wrap_kwargs:
104             wrap_kwargs[key] = prefix
105
106     if original_year == final_year:
107         date_range = '%s' % original_year
108     else:
109         date_range = '%s-%s' % (original_year, final_year)
110     copyright_year_string = 'Copyright (C) %s' % date_range
111
112     lines = author_format_fn(copyright_year_string, authors,
113                              **formatter_kwargs)
114     for i,line in enumerate(lines):
115         lines[i] = prefix + line
116
117     for i,paragraph in enumerate(text):
118         try:
119             text[i] = paragraph % info
120         except ValueError, e:
121             _LOG.error(
122                 "{}: can't format {} with {}".format(e, paragraph, info))
123             raise
124         except TypeError, e:
125             _LOG.error(
126                 ('{}: copright text must be a list of paragraph strings, '
127                  'not {}').format(e, repr(text)))
128             raise
129
130     if wrap == True:
131         text = [_textwrap.fill(p, **wrap_kwargs) for p in text]
132     else:
133         assert wrap_kwargs['subsequent_indent'] == '', \
134             wrap_kwargs['subsequent_indent']
135     sep = '\n%s\n' % prefix.rstrip()
136     return sep.join(['\n'.join(lines)] + text)
137
138 def tag_copyright(contents, tag=None):
139     """
140     >>> contents = '''Some file
141     ... bla bla
142     ... # Copyright (copyright begins)
143     ... # (copyright continues)
144     ... # bla bla bla
145     ... (copyright ends)
146     ... bla bla bla
147     ... '''
148     >>> print tag_copyright(contents, tag='-xyz-CR-zyx-')
149     Some file
150     bla bla
151     -xyz-CR-zyx-
152     (copyright ends)
153     bla bla bla
154     <BLANKLINE>
155     """
156     lines = []
157     incopy = False
158     for line in contents.splitlines():
159         if incopy == False and line.startswith('# Copyright'):
160             incopy = True
161             lines.append(tag)
162         elif incopy == True and not line.startswith('#'):
163             incopy = False
164         if incopy == False:
165             lines.append(line.rstrip('\n'))
166     return '\n'.join(lines)+'\n'
167
168 def update_copyright(contents, tag=None, **kwargs):
169     """
170     >>> contents = '''Some file
171     ... bla bla
172     ... # Copyright (copyright begins)
173     ... # (copyright continues)
174     ... # bla bla bla
175     ... (copyright ends)
176     ... bla bla bla
177     ... '''
178     >>> print update_copyright(contents, original_year=2008,
179     ...                        authors=['Jack', 'Jill'],
180     ...                        text=['BLURB',], prefix='# ', tag='--tag--'
181     ...     ) # doctest: +ELLIPSIS, +REPORT_UDIFF
182     Some file
183     bla bla
184     # Copyright (C) 2008-... Jack
185     #                         Jill
186     #
187     # BLURB
188     (copyright ends)
189     bla bla bla
190     <BLANKLINE>
191     """
192     current_year = _time.gmtime()[0]
193     string = copyright_string(final_year=current_year, **kwargs)
194     contents = tag_copyright(contents=contents, tag=tag)
195     return contents.replace(tag, string)
196
197 def get_contents(filename, unicode=False, encoding=None):
198     if _os_path.isfile(filename):
199         if unicode:
200             if encoding is None:
201                 encoding = ENCODING
202             f = _codecs.open(filename, 'r', encoding=encoding)
203         else:
204             f = open(filename, 'r')
205         contents = f.read()
206         f.close()
207         return contents
208     return None
209
210 def set_contents(filename, contents, original_contents=None, unicode=False,
211                  encoding=None, dry_run=False):
212     if original_contents is None:
213         original_contents = get_contents(
214             filename=filename, unicode=unicode, encoding=encoding)
215     _LOG.debug('check contents of {}'.format(filename))
216     if contents != original_contents:
217         if original_contents is None:
218             _LOG.info('creating {}'.format(filename))
219         else:
220             _LOG.info('updating {}'.format(filename))
221             _LOG.debug(u'\n'.join(
222                     _difflib.unified_diff(
223                         original_contents.splitlines(), contents.splitlines(),
224                         fromfile=_os_path.normpath(
225                             _os_path.join('a', filename)),
226                         tofile=_os_path.normpath(_os_path.join('b', filename)),
227                         n=3, lineterm='')))
228         if dry_run == False:
229             if unicode:
230                 if encoding is None:
231                     encoding = ENCODING
232                 f = _codecs.open(filename, 'w', encoding=encoding)
233             else:
234                 f = file(filename, 'w')
235             f.write(contents)
236             f.close()
237     _LOG.debug('no change in {}'.format(filename))
238
239 def list_files(root='.'):
240     for dirpath,dirnames,filenames in _os.walk(root):
241         for filename in filenames:
242             yield _os_path.normpath(_os_path.join(root, dirpath, filename))