chiark / gitweb /
1bc47595b1f9dabb3bffbbf498b27cc839068e9a
[stgit] / stgit / run.py
1 # -*- coding: utf-8 -*-
2
3 __copyright__ = """
4 Copyright (C) 2007, Karl Hasselström <kha@treskal.com>
5
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 2 as
8 published by the Free Software Foundation.
9
10 This program 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
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 """
19
20 # popen2 and os.spawn* suck. We should really use subprocess instead,
21 # but that's only available in Python 2.4 and up, and we try our best
22 # to stay Python 2.3 compatible.
23 import popen2, os
24
25 class RunException(Exception):
26     """Thrown when something bad happened when we tried to run the
27     subprocess."""
28     pass
29
30 class Run:
31     exc = RunException
32     def __init__(self, *cmd):
33         self.__cmd = list(cmd)
34         for c in cmd:
35             if type(c) != str:
36                 raise Exception, 'Bad command: %r' % cmd
37         self.__good_retvals = [0]
38         self.__env = None
39         self.__indata = None
40     def __run_io(self, cmd):
41         """Run with captured IO. Note: arguments are parsed by the
42         shell. We single-quote them, so don't use anything with single
43         quotes in it."""
44         if self.__env == None:
45             ecmd = cmd
46         else:
47             ecmd = (['env'] + ['%s=%s' % (key, val)
48                                for key, val in self.__env.iteritems()]
49                     + cmd)
50         p = popen2.Popen3(' '.join(["'%s'" % c for c in ecmd]), True)
51         if self.__indata != None:
52             p.tochild.write(self.__indata)
53         p.tochild.close()
54         outdata = p.fromchild.read()
55         errdata = p.childerr.read()
56         self.exitcode = p.wait() >> 8
57         if errdata or self.exitcode not in self.__good_retvals:
58             raise self.exc('%s failed with code %d:\n%s'
59                            % (cmd[0], self.exitcode, errdata))
60         return outdata
61     def __run_noshell(self, cmd):
62         """Run without captured IO. Note: arguments are not parsed by
63         the shell."""
64         assert self.__env == None
65         assert self.__indata == None
66         self.exitcode = os.spawnvp(os.P_WAIT, cmd[0], cmd)
67         if not self.exitcode in self.__good_retvals:
68             raise self.exc('%s failed with code %d'
69                            % (cmd[0], self.exitcode))
70     def returns(self, retvals):
71         self.__good_retvals = retvals
72         return self
73     def env(self, env):
74         self.__env = env
75         return self
76     def raw_input(self, indata):
77         self.__indata = indata
78         return self
79     def input_lines(self, lines):
80         self.__indata = ''.join(['%s\n' % line for line in lines])
81         return self
82     def no_output(self):
83         outdata = self.__run_io(self.__cmd)
84         if outdata:
85             raise self.exc, '%s produced output' % self.__cmd[0]
86     def discard_output(self):
87         self.__run_io(self.__cmd)
88     def raw_output(self):
89         return self.__run_io(self.__cmd)
90     def output_lines(self):
91         outdata = self.__run_io(self.__cmd)
92         if outdata.endswith('\n'):
93             outdata = outdata[:-1]
94         if outdata:
95             return outdata.split('\n')
96         else:
97             return []
98     def output_one_line(self):
99         outlines = self.output_lines()
100         if len(outlines) == 1:
101             return outlines[0]
102         else:
103             raise self.exc('%s produced %d lines, expected 1'
104                            % (self.__cmd[0], len(outlines)))
105     def run(self):
106         """Just run, with no IO redirection."""
107         self.__run_noshell(self.__cmd)
108     def xargs(self, xargs):
109         """Just run, with no IO redirection. The extra arguments are
110         appended to the command line a few at a time; the command is
111         run as many times as needed to consume them all."""
112         step = 100
113         for i in xrange(0, len(xargs), step):
114             self.__run_noshell(self.__cmd + xargs[i:i+step])