source: tailor/vcpx/shwrap.py @ 940

Revision 940, 6.2 KB checked in by lele@…, 8 years ago (diff)

Revamped the logging subsystem

Line 
1# -*- mode: python; coding: iso-8859-1 -*-
2# :Progetto: vcpx -- Tiny wrapper around external command
3# :Creato:   sab 10 apr 2004 16:43:48 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8__docformat__ = 'reStructuredText'
9
10try:
11    # Python 2.4
12    from subprocess import Popen, PIPE, STDOUT
13except ImportError:
14    # Older snakes
15    from _process import Popen, PIPE, STDOUT
16import logging
17
18class ReopenableNamedTemporaryFile:
19    """
20    This uses tempfile.mkstemp() to generate a secure temp file.  It
21    then closes the file, leaving a zero-length file as a placeholder.
22    You can get the filename with ReopenableNamedTemporaryFile.name.
23    When the ReopenableNamedTemporaryFile instance is garbage
24    collected or its shutdown() method is called, it deletes the file.
25
26    Copied from Zooko's pyutil.fileutil, http://zooko.com/repos/pyutil
27    """
28    def __init__(self, suffix=None, prefix=None, dir=None, text=None):
29        from tempfile import mkstemp
30        from os import close
31
32        fd, self.name = mkstemp(suffix, prefix, dir, text)
33        close(fd)
34
35    def __del__(self):
36        self.shutdown()
37
38    def shutdown(self):
39        from os import remove
40
41        remove(self.name)
42
43
44class ExternalCommand:
45    """Wrap a single command to be executed by the shell."""
46
47    VERBOSE = True
48    """Print the executed command on stderr, at each run."""
49
50    DEBUG = False
51    """Print the output of the command, when not PIPEd to the caller."""
52
53    DRY_RUN = False
54    """Don't really execute the command."""
55
56    FORCE_ENCODING = None
57    """Force the output encoding to some other charset instead of user prefs."""
58
59    def __init__(self, command=None, cwd=None):
60        """Initialize a ExternalCommand instance, specifying the command
61           to be executed and eventually the working directory."""
62
63        self.command = command
64        """The command to be executed."""
65
66        self.cwd = cwd
67        """The working directory, go there before execution."""
68
69        self.exit_status = None
70        """Once the command has been executed, this is its exit status."""
71
72        self._last_command = None
73        """Last executed command."""
74
75        self.log = logging.getLogger('tailor.shell')
76
77    def __str__(self):
78        result = []
79        if self.cwd:
80            result.append(self.cwd)
81            result.append(' ')
82        result.append('$')
83        needquote = False
84        for arg in self._last_command or self.command:
85            bs_buf = []
86
87            # Add a space to separate this argument from the others
88            result.append(' ')
89
90            needquote = (" " in arg) or ("\t" in arg)
91            if needquote:
92                result.append('"')
93
94            for c in arg:
95                if c == '\\':
96                    # Don't know if we need to double yet.
97                    bs_buf.append(c)
98                elif c == '"':
99                    # Double backspaces.
100                    result.append('\\' * len(bs_buf)*2)
101                    bs_buf = []
102                    result.append('\\"')
103                else:
104                    # Normal char
105                    if bs_buf:
106                        result.extend(bs_buf)
107                        bs_buf = []
108                    result.append(c)
109
110            # Add remaining backspaces, if any.
111            if bs_buf:
112                result.extend(bs_buf)
113
114            if needquote:
115                result.extend(bs_buf)
116                result.append('"')
117
118        return ''.join(result)
119
120    def execute(self, *args, **kwargs):
121        """Execute the command."""
122
123        from sys import stderr
124        from locale import getpreferredencoding
125        import os
126        from cStringIO import StringIO
127
128        self.exit_status = None
129
130        self._last_command = [chunk % kwargs for chunk in self.command]
131        if len(args) == 1 and type(args[0]) == type([]):
132            self._last_command.extend(args[0])
133        else:
134            self._last_command.extend(args)
135
136        self.log.info(self)
137
138        if self.DRY_RUN:
139            return
140
141        if not kwargs.has_key('cwd') and self.cwd:
142            kwargs['cwd'] = self.cwd
143
144        if not kwargs.has_key('env'):
145            env = kwargs['env'] = {}
146            env.update(os.environ)
147
148            for v in ['LANG', 'TZ', 'PATH']:
149                if kwargs.has_key(v):
150                    env[v] = kwargs[v]
151
152        input = kwargs.get('input')
153        output = kwargs.get('stdout')
154        error = kwargs.get('stderr')
155
156        # When not in debug, redirect stderr and stdout to /dev/null
157        # when the caller didn't ask for them.
158        if not self.DEBUG:
159            devnull = getattr(os, 'devnull', '/dev/null')
160            if output is None:
161                output = open(devnull, 'w')
162            if error is None:
163                error = open(devnull, 'w')
164        try:
165            process = Popen(self._last_command,
166                            stdin=input and PIPE or None,
167                            stdout=output,
168                            stderr=error,
169                            env=kwargs.get('env'),
170                            cwd=kwargs.get('cwd'),
171                            universal_newlines=True)
172        except OSError, e:
173            from errno import ENOENT
174
175            if e.errno == ENOENT:
176                raise OSError("'%s' does not exist!" % self._last_command[0])
177            else:
178                raise
179
180        if input and isinstance(input, unicode):
181            input = input.encode(self.FORCE_ENCODING or getpreferredencoding())
182
183        out, err = process.communicate(input=input)
184
185        self.exit_status = process.returncode
186        if not self.exit_status:
187            self.log.info("[Ok]")
188        else:
189            self.log.warning("[Status %s]", self.exit_status)
190
191        # For debug purposes, copy the output to our stderr when hidden above
192        if self.DEBUG:
193            if out and output == PIPE:
194                stderr.write('Output stream:\n')
195                stderr.write(out)
196            if err and error == PIPE:
197                stderr.write('Error stream:\n')
198                stderr.write(err)
199
200        if out is not None:
201            out = StringIO(out)
202        if err is not None:
203            err = StringIO(err)
204
205        return out, err
Note: See TracBrowser for help on using the repository browser.