1
2
3
4
5
6
7
8
9 """
10 Miscellaneous utility functions that are used by multiple modules.
11
12 @group Python source types: is_module_file, is_package_dir, is_pyname,
13 py_src_filename
14 @group Text processing: wordwrap, decode_with_backslashreplace,
15 plaintext_to_html
16 """
17 __docformat__ = 'epytext en'
18
19 import os, os.path, re, sys
20
21
22
23
24
25 PY_SRC_EXTENSIONS = ['.py', '.pyw']
26 PY_BIN_EXTENSIONS = ['.pyc', '.so', '.pyd']
27
37
42
44 """
45 Return true if the given directory is a valid package directory
46 (i.e., it names a directory that contains a valid __init__ file,
47 and its name is a valid identifier).
48 """
49
50 if not isinstance(dirname, basestring):
51 return False
52 if not os.path.isdir(dirname):
53 return False
54 dirname = os.path.abspath(dirname)
55
56
57 (parent, dir) = os.path.split(dirname)
58 if dir == '': (parent, dir) = os.path.split(parent)
59
60
61
62
63
64
65 for name in os.listdir(dirname):
66 filename = os.path.join(dirname, name)
67 if name.startswith('__init__.') and is_module_file(filename):
68 return True
69 else:
70 return False
71
73 return re.match(r"\w+(\.\w+)*$", name)
74
76 basefile, extension = os.path.splitext(filename)
77 if extension in PY_SRC_EXTENSIONS:
78 return filename
79 else:
80 for ext in PY_SRC_EXTENSIONS:
81 if os.path.isfile('%s%s' % (basefile, ext)):
82 return '%s%s' % (basefile, ext)
83 else:
84 raise ValueError('Could not find a corresponding '
85 'Python source file for %r.' % filename)
86
91
92
93
94
95
97 r"""
98 Convert the given 8-bit string into unicode, treating any
99 character c such that ord(c)<128 as an ascii character, and
100 converting any c such that ord(c)>128 into a backslashed escape
101 sequence.
102
103 >>> decode_with_backslashreplace('abc\xff\xe8')
104 u'abc\\xff\\xe8'
105 """
106
107
108 assert isinstance(s, str)
109 return (s
110 .decode('latin1')
111 .encode('ascii', 'backslashreplace')
112 .decode('ascii'))
113
114 -def wordwrap(str, indent=0, right=75, startindex=0, splitchars=''):
115 """
116 Word-wrap the given string. I.e., add newlines to the string such
117 that any lines that are longer than C{right} are broken into
118 shorter lines (at the first whitespace sequence that occurs before
119 index C{right}). If the given string contains newlines, they will
120 I{not} be removed. Any lines that begin with whitespace will not
121 be wordwrapped.
122
123 @param indent: If specified, then indent each line by this number
124 of spaces.
125 @type indent: C{int}
126 @param right: The right margin for word wrapping. Lines that are
127 longer than C{right} will be broken at the first whitespace
128 sequence before the right margin.
129 @type right: C{int}
130 @param startindex: If specified, then assume that the first line
131 is already preceeded by C{startindex} characters.
132 @type startindex: C{int}
133 @param splitchars: A list of non-whitespace characters which can
134 be used to split a line. (E.g., use '/\\' to allow path names
135 to be split over multiple lines.)
136 @rtype: C{str}
137 """
138 if splitchars:
139 chunks = re.split(r'( +|\n|[^ \n%s]*[%s])' %
140 (re.escape(splitchars), re.escape(splitchars)),
141 str.expandtabs())
142 else:
143 chunks = re.split(r'( +|\n)', str.expandtabs())
144 result = [' '*(indent-startindex)]
145 charindex = max(indent, startindex)
146 for chunknum, chunk in enumerate(chunks):
147 if (charindex+len(chunk) > right and charindex > 0) or chunk == '\n':
148 result.append('\n' + ' '*indent)
149 charindex = indent
150 if chunk[:1] not in ('\n', ' '):
151 result.append(chunk)
152 charindex += len(chunk)
153 else:
154 result.append(chunk)
155 charindex += len(chunk)
156 return ''.join(result).rstrip()+'\n'
157
159 """
160 @return: An HTML string that encodes the given plaintext string.
161 In particular, special characters (such as C{'<'} and C{'&'})
162 are escaped.
163 @rtype: C{string}
164 """
165 s = s.replace('&', '&').replace('"', '"')
166 s = s.replace('<', '<').replace('>', '>')
167 return s
168
169 -def plaintext_to_latex(str, nbsp=0, breakany=0):
170 """
171 @return: A LaTeX string that encodes the given plaintext string.
172 In particular, special characters (such as C{'$'} and C{'_'})
173 are escaped, and tabs are expanded.
174 @rtype: C{string}
175 @param breakany: Insert hyphenation marks, so that LaTeX can
176 break the resulting string at any point. This is useful for
177 small boxes (e.g., the type box in the variable list table).
178 @param nbsp: Replace every space with a non-breaking space
179 (C{'~'}).
180 """
181
182 if breakany: str = re.sub('(.)', '\\1\1', str)
183
184
185 str = str.replace('\\', '\0')
186
187
188 str = str.expandtabs()
189
190
191 str = re.sub(r'([#$&%_\${}])', r'\\\1', str)
192
193
194 str = str.replace('|', '{\\textbar}')
195 str = str.replace('<', '{\\textless}')
196 str = str.replace('>', '{\\textgreater}')
197 str = str.replace('^', '{\\textasciicircum}')
198 str = str.replace('~', '{\\textasciitilde}')
199 str = str.replace('\0', r'{\textbackslash}')
200
201
202 if nbsp: str = str.replace(' ', '~')
203
204
205 if breakany: str = str.replace('\1', r'\-')
206
207 return str
208
211 OSError.__init__(self, '%s failed' % cmd[0])
212 self.out = out
213 self.err = err
214
216 """
217 Execute the command C{cmd} in a subprocess.
218
219 @param cmd: The command to execute, specified as a list
220 of string.
221 @param data: A string containing data to send to the
222 subprocess.
223 @return: A tuple C{(out, err)}.
224 @raise OSError: If there is any problem executing the
225 command, or if its exitval is not 0.
226 """
227 if isinstance(cmd, basestring):
228 cmd = cmd.split()
229
230
231 try:
232 from subprocess import Popen, PIPE
233 pipe = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
234 out, err = pipe.communicate(data)
235 if hasattr(pipe, 'returncode'):
236 if pipe.returncode == 0:
237 return out, err
238 else:
239 raise RunSubprocessError(cmd, out, err)
240 else:
241
242
243 if err == '':
244 return out, err
245 else:
246 raise RunSubprocessError(cmd, out, err)
247 except ImportError:
248 pass
249
250
251
252 import popen2
253 if hasattr(popen2, 'Popen3'):
254 pipe = popen2.Popen3(' '.join(cmd), True)
255 to_child = pipe.tochild
256 from_child = pipe.fromchild
257 child_err = pipe.childerr
258 if data:
259 to_child.write(data)
260 to_child.close()
261 out = err = ''
262 while pipe.poll() is None:
263 out += from_child.read()
264 err += child_err.read()
265 out += from_child.read()
266 err += child_err.read()
267 if pipe.wait() == 0:
268 return out, err
269 else:
270 raise RunSubprocessError(cmd, out, err)
271
272
273 else:
274 to_child, from_child, child_err = os.popen3(' '.join(cmd), 'b')
275 if data:
276 try:
277 to_child.write(data)
278
279 except IOError, e:
280 raise OSError(e)
281 to_child.close()
282 out = from_child.read()
283 err = child_err.read()
284
285
286 if err == '':
287 return out, err
288 else:
289 raise RunSubprocessError(cmd, out, err)
290
291
292
293
294
296 """
297 A class that can be used to portably generate formatted output to
298 a terminal. See
299 U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116}
300 for documentation. (This is a somewhat stripped-down version.)
301 """
302 BOL = ''
303 UP = ''
304 DOWN = ''
305 LEFT = ''
306 RIGHT = ''
307 CLEAR_EOL = ''
308 CLEAR_LINE = ''
309 BOLD = ''
310 NORMAL = ''
311 COLS = 75
312 UNDERLINE = ''
313 REVERSE = ''
314 BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
315
316 _STRING_CAPABILITIES = """
317 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 REVERSE=rev
318 CLEAR_EOL=el BOLD=bold UNDERLINE=smul NORMAL=sgr0""".split()
319 _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
320 _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
321
322
323
324
325 FORCE_SIMPLE_TERM = False
326
327 - def __init__(self, term_stream=sys.stdout):
328
329 if not term_stream.isatty(): return
330 if self.FORCE_SIMPLE_TERM: return
331
332
333 try: import curses
334 except:
335
336
337 self.BOL = '\r'
338 self.CLEAR_LINE = '\r' + ' '*self.COLS + '\r'
339
340
341
342 try: curses.setupterm()
343 except: return
344
345
346 self.COLS = curses.tigetnum('cols')
347
348
349 for capability in self._STRING_CAPABILITIES:
350 (attrib, cap_name) = capability.split('=')
351 setattr(self, attrib, self._tigetstr(cap_name) or '')
352 if self.BOL and self.CLEAR_EOL:
353 self.CLEAR_LINE = self.BOL+self.CLEAR_EOL
354
355
356 set_fg = self._tigetstr('setf')
357 if set_fg:
358 for i,color in zip(range(len(self._COLORS)), self._COLORS):
359 setattr(self, color, curses.tparm(set_fg, i) or '')
360 set_fg_ansi = self._tigetstr('setaf')
361 if set_fg_ansi:
362 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
363 setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
364
366
367
368
369 import curses
370 cap = curses.tigetstr(cap_name) or ''
371 return re.sub(r'\$<\d+>[/*]?', '', cap)
372
374 """
375 Replace each $-substitutions in the given template string with
376 the corresponding terminal control string (if it's defined) or
377 '' (if it's not).
378 """
379 return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
380
382 s = match.group()
383 if s == '$$': return s
384 else: return getattr(self, s[2:-1])
385