024568eae143851d294b4a47f65eb1e90638d04f
[update-copyright.git] / update_copyright / vcs / 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 """Useful utilities for backend classes."""
20
21 import email.utils as _email_utils
22 import os.path as _os_path
23 import subprocess as _subprocess
24 import sys as _sys
25
26 from .. import LOG as LOG
27 from ..utils import ENCODING as _ENCODING
28
29
30 _MSWINDOWS = _sys.platform == 'win32'
31 _POSIX = not _MSWINDOWS
32
33
34 def invoke(args, stdin=None, stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
35            cwd=None, expect=(0,), unicode_output=False, encoding=None):
36     """Invoke an external program and return the results
37
38     ``expect`` should be a tuple of allowed exit codes.
39
40     When ``unicode_output`` is ``True``, convert stdout and stdin
41     strings to unicode before returing them.
42     """
43     LOG.debug('{}$ {}'.format(cwd, args))
44     try :
45         if _POSIX:
46             q = _subprocess.Popen(args, stdin=_subprocess.PIPE,
47                                   stdout=stdout, stderr=stderr,
48                                   close_fds=True, cwd=cwd)
49         else:
50             assert _MSWINDOWS == True, 'invalid platform'
51             # win32 don't have os.execvp() so run the command in a shell
52             q = _subprocess.Popen(args, stdin=_subprocess.PIPE,
53                                   stdout=stdout, stderr=stderr, shell=True,
54                                   cwd=cwd)
55     except OSError, e:
56         raise ValueError([args, e])
57     stdout,stderr = q.communicate(input=stdin)
58     status = q.wait()
59     if unicode_output == True:
60         if encoding is None:
61             encoding = _ENCODING
62         if stdout is not None:
63             stdout = unicode(stdout, encoding)
64         if stderr is not None:
65             stderr = unicode(stderr, encoding)
66     if status not in expect:
67         raise ValueError([args, status, stdout, stderr])
68     return status, stdout, stderr
69
70 def splitpath(path):
71     """Recursively split a path into elements.
72
73     Examples
74     --------
75
76     >>> import os.path
77     >>> splitpath(os.path.join('a', 'b', 'c'))
78     ('a', 'b', 'c')
79     >>> splitpath(os.path.join('.', 'a', 'b', 'c'))
80     ('a', 'b', 'c')
81     """
82     path = _os_path.normpath(path)
83     elements = []
84     while True:
85         dirname,basename = _os_path.split(path)
86         elements.insert(0,basename)
87         if dirname in ['/', '', '.']:
88             break
89         path = dirname
90     return tuple(elements)
91
92 def strip_email(*args):
93     """Remove email addresses from a series of names.
94
95     Examples
96     --------
97
98     >>> strip_email('J Doe')
99     ['J Doe']
100     >>> strip_email('J Doe <jdoe@a.com>')
101     ['J Doe']
102     >>> strip_email('J Doe <jdoe@a.com>', 'JJJ Smith <jjjs@a.com>')
103     ['J Doe', 'JJJ Smith']
104     """
105     args = list(args)
106     for i,arg in enumerate(args):
107         if arg == None:
108             continue
109         author,addr = _email_utils.parseaddr(arg)
110         if author == '':
111             author = arg
112         args[i] = author
113     return args
114
115 def reverse_aliases(aliases):
116     """Reverse an `aliases` dict.
117
118     Input:   key: canonical name,  value: list of aliases
119     Output:  key: alias,           value: canonical name
120
121     Examples
122     --------
123
124     >>> aliases = {
125     ...     'J Doe <jdoe@a.com>':['Johnny <jdoe@b.edu>', 'J'],
126     ...     'JJJ Smith <jjjs@a.com>':['Jingly <jjjs@b.edu>'],
127     ...     None:['Anonymous <a@a.com>'],
128     ...     }
129     >>> r = reverse_aliases(aliases)
130     >>> for item in sorted(r.items()):
131     ...     print item
132     ('Anonymous <a@a.com>', None)
133     ('J', 'J Doe <jdoe@a.com>')
134     ('Jingly <jjjs@b.edu>', 'JJJ Smith <jjjs@a.com>')
135     ('Johnny <jdoe@b.edu>', 'J Doe <jdoe@a.com>')
136     """
137     output = {}
138     for canonical_name,_aliases in aliases.items():
139         for alias in _aliases:
140             output[alias] = canonical_name
141     return output
142
143 def replace_aliases(authors, with_email=True, aliases=None):
144     """Consolidate and sort `authors`.
145
146     Make the replacements listed in the `aliases` dict (key: canonical
147     name, value: list of aliases).  If `aliases` is ``None``, default
148     to ``ALIASES``.
149
150     >>> aliases = {
151     ...     'J Doe <jdoe@a.com>':['Johnny <jdoe@b.edu>'],
152     ...     'JJJ Smith <jjjs@a.com>':['Jingly <jjjs@b.edu>'],
153     ...     None:['Anonymous <a@a.com>'],
154     ...     }
155     >>> authors = [
156     ...     'JJJ Smith <jjjs@a.com>', 'Johnny <jdoe@b.edu>',
157     ...     'Jingly <jjjs@b.edu>', 'J Doe <jdoe@a.com>', 'Anonymous <a@a.com>']
158     >>> replace_aliases(authors, with_email=True, aliases=aliases)
159     ['J Doe <jdoe@a.com>', 'JJJ Smith <jjjs@a.com>']
160     >>> replace_aliases(authors, with_email=False, aliases=aliases)
161     ['J Doe', 'JJJ Smith']
162     """
163     if aliases == None:
164         aliases = ALIASES
165     rev_aliases = reverse_aliases(aliases)
166     for i,author in enumerate(authors):
167         if author in rev_aliases:
168             authors[i] = rev_aliases[author]
169     authors = sorted(list(set(authors)))
170     if None in authors:
171         authors.remove(None)
172     if with_email == False:
173         authors = strip_email(*authors)
174     return authors