3 """Misc. useful functionality used by the rest of this package.
5 This module provides common functionality used by the other modules in
15 # Whether or not to show debug messages
18 def notify(msg
, *args
):
19 """Print a message to stderr."""
20 print >> sys
.stderr
, msg
% args
22 def debug (msg
, *args
):
23 """Print a debug message to stderr when DEBUG is enabled."""
25 print >> sys
.stderr
, msg
% args
27 def error (msg
, *args
):
28 """Print an error message to stderr."""
29 print >> sys
.stderr
, "ERROR:", msg
% args
32 """Print a warning message to stderr."""
33 print >> sys
.stderr
, "warning:", msg
% args
36 """Print as error message to stderr and exit the program."""
41 class ProgressIndicator(object):
43 """Simple progress indicator.
45 Displayed as a spinning character by default, but can be customized
46 by passing custom messages that overrides the spinning character.
50 States
= ("|", "/", "-", "\\")
52 def __init__ (self
, prefix
= "", f
= sys
.stdout
):
53 """Create a new ProgressIndicator, bound to the given file object."""
54 self
.n
= 0 # Simple progress counter
55 self
.f
= f
# Progress is written to this file object
56 self
.prev_len
= 0 # Length of previous msg (to be overwritten)
57 self
.prefix
= prefix
# Prefix prepended to each progress message
58 self
.prefix_lens
= [] # Stack of prefix string lengths
60 def pushprefix (self
, prefix
):
61 """Append the given prefix onto the prefix stack."""
62 self
.prefix_lens
.append(len(self
.prefix
))
66 """Remove the last prefix from the prefix stack."""
67 prev_len
= self
.prefix_lens
.pop()
68 self
.prefix
= self
.prefix
[:prev_len
]
70 def __call__ (self
, msg
= None, lf
= False):
71 """Indicate progress, possibly with a custom message."""
73 msg
= self
.States
[self
.n
% len(self
.States
)]
74 msg
= self
.prefix
+ msg
75 print >> self
.f
, "\r%-*s" % (self
.prev_len
, msg
),
76 self
.prev_len
= len(msg
.expandtabs())
82 def finish (self
, msg
= "done", noprefix
= False):
83 """Finalize progress indication with the given message."""
89 def start_command (args
, cwd
= None, shell
= False, add_env
= None,
90 stdin
= subprocess
.PIPE
, stdout
= subprocess
.PIPE
,
91 stderr
= subprocess
.PIPE
):
92 """Start the given command, and return a subprocess object.
94 This provides a simpler interface to the subprocess module.
98 if add_env
is not None:
99 env
= os
.environ
.copy()
101 return subprocess
.Popen(args
, bufsize
= 1, stdin
= stdin
, stdout
= stdout
,
102 stderr
= stderr
, cwd
= cwd
, shell
= shell
,
103 env
= env
, universal_newlines
= True)
106 def run_command (args
, cwd
= None, shell
= False, add_env
= None,
108 """Run the given command to completion, and return its results.
110 This provides a simpler interface to the subprocess module.
112 The results are formatted as a 3-tuple: (exit_code, output, errors)
114 If flag_error is enabled, Error messages will be produced if the
115 subprocess terminated with a non-zero exit code and/or stderr
118 The other arguments are passed on to start_command().
121 process
= start_command(args
, cwd
, shell
, add_env
)
122 (output
, errors
) = process
.communicate()
123 exit_code
= process
.returncode
124 if flag_error
and errors
:
125 error("'%s' returned errors:\n---\n%s---", " ".join(args
), errors
)
126 if flag_error
and exit_code
:
127 error("'%s' returned exit code %i", " ".join(args
), exit_code
)
128 return (exit_code
, output
, errors
)
131 def file_reader_method (missing_ok
= False):
132 """Decorator for simplifying reading of files.
134 If missing_ok is True, a failure to open a file for reading will
135 not raise the usual IOError, but instead the wrapped method will be
136 called with f == None. The method must in this case properly
141 """Teach given method to handle both filenames and file objects.
143 The given method must take a file object as its second argument
144 (the first argument being 'self', of course). This decorator
145 will take a filename given as the second argument and promote
149 def _wrapped_method (self
, filename
, *args
, **kwargs
):
150 if isinstance(filename
, file):
154 f
= open(filename
, 'r')
161 return method(self
, f
, *args
, **kwargs
)
163 if not isinstance(filename
, file) and f
:
165 return _wrapped_method
169 def file_writer_method (method
):
170 """Decorator for simplifying writing of files.
172 Enables the given method to handle both filenames and file objects.
174 The given method must take a file object as its second argument
175 (the first argument being 'self', of course). This decorator will
176 take a filename given as the second argument and promote it to a
180 def _new_method (self
, filename
, *args
, **kwargs
):
181 if isinstance(filename
, file):
184 # Make sure the containing directory exists
185 parent_dir
= os
.path
.dirname(filename
)
186 if not os
.path
.isdir(parent_dir
):
187 os
.makedirs(parent_dir
)
188 f
= open(filename
, 'w')
190 return method(self
, f
, *args
, **kwargs
)
192 if not isinstance(filename
, file):