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