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