blob: 8d9a4650bdef475d79822e8176b5d0f40bf46254 [file] [log] [blame]
Greg Warda82122b2000-02-17 23:56:15 +00001"""distutils.command.sdist
2
3Implements the Distutils 'sdist' command (create a source distribution)."""
4
5# created 1999/09/22, Greg Ward
6
Greg Ward3ce77fd2000-03-02 01:49:45 +00007__revision__ = "$Id$"
Greg Warda82122b2000-02-17 23:56:15 +00008
9import sys, os, string, re
10import fnmatch
11from types import *
12from glob import glob
Greg Warda82122b2000-02-17 23:56:15 +000013from distutils.core import Command
Greg Ward6a9a5452000-04-22 03:11:55 +000014from distutils.util import newer, create_tree, remove_tree, native_path
15from distutils.archive_util import check_archive_formats
Greg Warda82122b2000-02-17 23:56:15 +000016from distutils.text_file import TextFile
Greg Ward6a9a5452000-04-22 03:11:55 +000017from distutils.errors import DistutilsExecError, DistutilsOptionError
Greg Warda82122b2000-02-17 23:56:15 +000018
19
Greg Ward1993f9a2000-02-18 00:13:53 +000020class sdist (Command):
Greg Warda82122b2000-02-17 23:56:15 +000021
22 description = "create a source distribution (tarball, zip file, etc.)"
23
Greg Wardbbeceea2000-02-18 00:25:39 +000024 user_options = [
25 ('template=', 't',
26 "name of manifest template file [default: MANIFEST.in]"),
27 ('manifest=', 'm',
28 "name of manifest file [default: MANIFEST]"),
29 ('use-defaults', None,
30 "include the default file set in the manifest "
31 "[default; disable with --no-defaults]"),
32 ('manifest-only', None,
33 "just regenerate the manifest and then stop"),
34 ('force-manifest', None,
35 "forcibly regenerate the manifest and carry on as usual"),
Greg Wardbbeceea2000-02-18 00:25:39 +000036 ('formats=', None,
Greg Wardf1948782000-04-25 01:38:20 +000037 "formats for source distribution (tar, ztar, gztar, bztar, or zip)"),
Greg Wardbbeceea2000-02-18 00:25:39 +000038 ('keep-tree', 'k',
39 "keep the distribution tree around after creating " +
40 "archive file(s)"),
41 ]
Greg Warda82122b2000-02-17 23:56:15 +000042 negative_opts = {'use-defaults': 'no-defaults'}
43
44 default_format = { 'posix': 'gztar',
45 'nt': 'zip' }
46
47 exclude_re = re.compile (r'\s*!\s*(\S+)') # for manifest lines
48
49
Greg Warde01149c2000-02-18 00:35:22 +000050 def initialize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000051 # 'template' and 'manifest' are, respectively, the names of
52 # the manifest template and manifest file.
53 self.template = None
54 self.manifest = None
55
56 # 'use_defaults': if true, we will include the default file set
57 # in the manifest
58 self.use_defaults = 1
59
60 self.manifest_only = 0
61 self.force_manifest = 0
62
63 self.formats = None
Greg Warda82122b2000-02-17 23:56:15 +000064 self.keep_tree = 0
65
66
Greg Warde01149c2000-02-18 00:35:22 +000067 def finalize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000068 if self.manifest is None:
69 self.manifest = "MANIFEST"
70 if self.template is None:
71 self.template = "MANIFEST.in"
72
73 if self.formats is None:
74 try:
75 self.formats = [self.default_format[os.name]]
76 except KeyError:
77 raise DistutilsPlatformError, \
Greg Ward578c10d2000-03-31 02:50:04 +000078 "don't know how to create source distributions " + \
79 "on platform %s" % os.name
Greg Warda82122b2000-02-17 23:56:15 +000080 elif type (self.formats) is StringType:
81 self.formats = string.split (self.formats, ',')
82
Greg Ward6a9a5452000-04-22 03:11:55 +000083 bad_format = check_archive_formats (self.formats)
84 if bad_format:
85 raise DistutilsOptionError, \
86 "unknown archive format '%s'" % bad_format
87
Greg Warda82122b2000-02-17 23:56:15 +000088
89 def run (self):
90
91 # 'files' is the list of files that will make up the manifest
92 self.files = []
93
94 # Ensure that all required meta-data is given; warn if not (but
95 # don't die, it's not *that* serious!)
96 self.check_metadata ()
97
98 # Do whatever it takes to get the list of files to process
99 # (process the manifest template, read an existing manifest,
100 # whatever). File list is put into 'self.files'.
101 self.get_file_list ()
102
103 # If user just wanted us to regenerate the manifest, stop now.
104 if self.manifest_only:
105 return
106
107 # Otherwise, go ahead and create the source distribution tarball,
108 # or zipfile, or whatever.
109 self.make_distribution ()
110
111
112 def check_metadata (self):
113
Greg Ward535f2d92000-04-21 04:37:12 +0000114 metadata = self.distribution.metadata
Greg Warda82122b2000-02-17 23:56:15 +0000115
116 missing = []
117 for attr in ('name', 'version', 'url'):
Greg Ward535f2d92000-04-21 04:37:12 +0000118 if not (hasattr (metadata, attr) and getattr (metadata, attr)):
Greg Warda82122b2000-02-17 23:56:15 +0000119 missing.append (attr)
120
121 if missing:
122 self.warn ("missing required meta-data: " +
123 string.join (missing, ", "))
124
Greg Ward535f2d92000-04-21 04:37:12 +0000125 if metadata.author:
126 if not metadata.author_email:
Greg Warda82122b2000-02-17 23:56:15 +0000127 self.warn ("missing meta-data: if 'author' supplied, " +
128 "'author_email' must be supplied too")
Greg Ward535f2d92000-04-21 04:37:12 +0000129 elif metadata.maintainer:
130 if not metadata.maintainer_email:
Greg Warda82122b2000-02-17 23:56:15 +0000131 self.warn ("missing meta-data: if 'maintainer' supplied, " +
132 "'maintainer_email' must be supplied too")
133 else:
134 self.warn ("missing meta-data: either (author and author_email) " +
135 "or (maintainer and maintainer_email) " +
136 "must be supplied")
137
138 # check_metadata ()
139
140
141 def get_file_list (self):
142 """Figure out the list of files to include in the source
143 distribution, and put it in 'self.files'. This might
144 involve reading the manifest template (and writing the
145 manifest), or just reading the manifest, or just using
146 the default file set -- it all depends on the user's
147 options and the state of the filesystem."""
148
149
150 template_exists = os.path.isfile (self.template)
151 if template_exists:
152 template_newer = newer (self.template, self.manifest)
153
154 # Regenerate the manifest if necessary (or if explicitly told to)
155 if ((template_exists and template_newer) or
156 self.force_manifest or
157 self.manifest_only):
158
159 if not template_exists:
160 self.warn (("manifest template '%s' does not exist " +
161 "(using default file list)") %
162 self.template)
163
164 # Add default file set to 'files'
165 if self.use_defaults:
166 self.find_defaults ()
167
168 # Read manifest template if it exists
169 if template_exists:
170 self.read_template ()
171
172 # File list now complete -- sort it so that higher-level files
173 # come first
174 sortable_files = map (os.path.split, self.files)
175 sortable_files.sort ()
176 self.files = []
177 for sort_tuple in sortable_files:
178 self.files.append (apply (os.path.join, sort_tuple))
179
180 # Remove duplicates from the file list
181 for i in range (len(self.files)-1, 0, -1):
182 if self.files[i] == self.files[i-1]:
183 del self.files[i]
184
185 # And write complete file list (including default file set) to
186 # the manifest.
187 self.write_manifest ()
188
189 # Don't regenerate the manifest, just read it in.
190 else:
191 self.read_manifest ()
192
193 # get_file_list ()
194
195
196 def find_defaults (self):
197
198 standards = [('README', 'README.txt'), 'setup.py']
199 for fn in standards:
200 if type (fn) is TupleType:
201 alts = fn
Greg Ward48401122000-02-24 03:17:43 +0000202 got_it = 0
Greg Warda82122b2000-02-17 23:56:15 +0000203 for fn in alts:
204 if os.path.exists (fn):
205 got_it = 1
206 self.files.append (fn)
207 break
208
209 if not got_it:
210 self.warn ("standard file not found: should have one of " +
211 string.join (alts, ', '))
212 else:
213 if os.path.exists (fn):
214 self.files.append (fn)
215 else:
216 self.warn ("standard file '%s' not found" % fn)
217
218 optional = ['test/test*.py']
219 for pattern in optional:
220 files = filter (os.path.isfile, glob (pattern))
221 if files:
222 self.files.extend (files)
223
Greg Ward578c10d2000-03-31 02:50:04 +0000224 if self.distribution.has_pure_modules():
Greg Warda82122b2000-02-17 23:56:15 +0000225 build_py = self.find_peer ('build_py')
Greg Warda82122b2000-02-17 23:56:15 +0000226 self.files.extend (build_py.get_source_files ())
227
Greg Ward578c10d2000-03-31 02:50:04 +0000228 if self.distribution.has_ext_modules():
Greg Warda82122b2000-02-17 23:56:15 +0000229 build_ext = self.find_peer ('build_ext')
Greg Warda82122b2000-02-17 23:56:15 +0000230 self.files.extend (build_ext.get_source_files ())
231
Greg Ward60908f12000-04-09 03:51:40 +0000232 if self.distribution.has_c_libraries():
233 build_clib = self.find_peer ('build_clib')
234 self.files.extend (build_clib.get_source_files ())
235
Greg Warda82122b2000-02-17 23:56:15 +0000236
Greg Warda82122b2000-02-17 23:56:15 +0000237 def search_dir (self, dir, pattern=None):
238 """Recursively find files under 'dir' matching 'pattern' (a string
239 containing a Unix-style glob pattern). If 'pattern' is None,
240 find all files under 'dir'. Return the list of found
241 filenames."""
242
243 allfiles = findall (dir)
244 if pattern is None:
245 return allfiles
246
247 pattern_re = translate_pattern (pattern)
248 files = []
249 for file in allfiles:
250 if pattern_re.match (os.path.basename (file)):
251 files.append (file)
252
253 return files
254
255 # search_dir ()
256
257
258 def exclude_pattern (self, pattern):
259 """Remove filenames from 'self.files' that match 'pattern'."""
260 print "exclude_pattern: pattern=%s" % pattern
261 pattern_re = translate_pattern (pattern)
262 for i in range (len (self.files)-1, -1, -1):
263 if pattern_re.match (self.files[i]):
264 print "removing %s" % self.files[i]
265 del self.files[i]
266
267
268 def recursive_exclude_pattern (self, dir, pattern=None):
269 """Remove filenames from 'self.files' that are under 'dir'
270 and whose basenames match 'pattern'."""
271
272 print "recursive_exclude_pattern: dir=%s, pattern=%s" % (dir, pattern)
273 if pattern is None:
274 pattern_re = None
275 else:
276 pattern_re = translate_pattern (pattern)
277
278 for i in range (len (self.files)-1, -1, -1):
279 (cur_dir, cur_base) = os.path.split (self.files[i])
280 if (cur_dir == dir and
281 (pattern_re is None or pattern_re.match (cur_base))):
282 print "removing %s" % self.files[i]
283 del self.files[i]
284
285
286 def read_template (self):
287 """Read and parse the manifest template file named by
288 'self.template' (usually "MANIFEST.in"). Process all file
289 specifications (include and exclude) in the manifest template
290 and add the resulting filenames to 'self.files'."""
291
292 assert self.files is not None and type (self.files) is ListType
293
294 template = TextFile (self.template,
295 strip_comments=1,
296 skip_blanks=1,
297 join_lines=1,
298 lstrip_ws=1,
299 rstrip_ws=1,
300 collapse_ws=1)
301
302 all_files = findall ()
303
304 while 1:
305
306 line = template.readline()
307 if line is None: # end of file
308 break
309
310 words = string.split (line)
311 action = words[0]
312
313 # First, check that the right number of words are present
314 # for the given action (which is the first word)
315 if action in ('include','exclude',
316 'global-include','global-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000317 if len (words) < 2:
Greg Warda82122b2000-02-17 23:56:15 +0000318 template.warn \
319 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000320 "'%s' expects <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000321 action)
322 continue
323
Greg Ward9d5afa92000-04-21 04:31:10 +0000324 pattern_list = map(native_path, words[1:])
Greg Warda82122b2000-02-17 23:56:15 +0000325
326 elif action in ('recursive-include','recursive-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000327 if len (words) < 3:
Greg Warda82122b2000-02-17 23:56:15 +0000328 template.warn \
329 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000330 "'%s' expects <dir> <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000331 action)
332 continue
333
Greg Ward9d5afa92000-04-21 04:31:10 +0000334 dir = native_path(words[1])
335 pattern_list = map (native_path, words[2:])
Greg Warda82122b2000-02-17 23:56:15 +0000336
337 elif action in ('graft','prune'):
338 if len (words) != 2:
339 template.warn \
340 ("invalid manifest template line: " +
341 "'%s' expects a single <dir_pattern>" %
342 action)
343 continue
344
Greg Ward2b9e43f2000-04-14 00:49:30 +0000345 dir_pattern = native_path (words[1])
Greg Warda82122b2000-02-17 23:56:15 +0000346
347 else:
348 template.warn ("invalid manifest template line: " +
349 "unknown action '%s'" % action)
350 continue
351
352 # OK, now we know that the action is valid and we have the
353 # right number of words on the line for that action -- so we
354 # can proceed with minimal error-checking. Also, we have
Greg Ward2b9e43f2000-04-14 00:49:30 +0000355 # defined either (pattern), (dir and pattern), or
356 # (dir_pattern) -- so we don't have to spend any time
357 # digging stuff up out of 'words'.
Greg Warda82122b2000-02-17 23:56:15 +0000358
359 if action == 'include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000360 print "include", string.join(pattern_list)
361 for pattern in pattern_list:
362 files = select_pattern (all_files, pattern, anchor=1)
363 if not files:
364 template.warn ("no files found matching '%s'" % pattern)
365 else:
366 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000367
368 elif action == 'exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000369 print "exclude", string.join(pattern_list)
370 for pattern in pattern_list:
371 num = exclude_pattern (self.files, pattern, anchor=1)
372 if num == 0:
373 template.warn (
374 "no previously-included files found matching '%s'"%
375 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000376
377 elif action == 'global-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000378 print "global-include", string.join(pattern_list)
379 for pattern in pattern_list:
380 files = select_pattern (all_files, pattern, anchor=0)
381 if not files:
382 template.warn (("no files found matching '%s' " +
383 "anywhere in distribution") %
384 pattern)
385 else:
386 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000387
388 elif action == 'global-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000389 print "global-exclude", string.join(pattern_list)
390 for pattern in pattern_list:
391 num = exclude_pattern (self.files, pattern, anchor=0)
392 if num == 0:
393 template.warn \
394 (("no previously-included files matching '%s' " +
395 "found anywhere in distribution") %
396 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000397
398 elif action == 'recursive-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000399 print "recursive-include", dir, string.join(pattern_list)
400 for pattern in pattern_list:
401 files = select_pattern (all_files, pattern, prefix=dir)
402 if not files:
403 template.warn (("no files found matching '%s' " +
404 "under directory '%s'") %
405 (pattern, dir))
406 else:
407 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000408
409 elif action == 'recursive-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000410 print "recursive-exclude", dir, string.join(pattern_list)
411 for pattern in pattern_list:
412 num = exclude_pattern (self.files, pattern, prefix=dir)
413 if num == 0:
414 template.warn \
415 (("no previously-included files matching '%s' " +
416 "found under directory '%s'") %
417 (pattern, dir))
Greg Warda82122b2000-02-17 23:56:15 +0000418
419 elif action == 'graft':
420 print "graft", dir_pattern
421 files = select_pattern (all_files, None, prefix=dir_pattern)
422 if not files:
423 template.warn ("no directories found matching '%s'" %
424 dir_pattern)
425 else:
426 self.files.extend (files)
427
428 elif action == 'prune':
429 print "prune", dir_pattern
430 num = exclude_pattern (self.files, None, prefix=dir_pattern)
431 if num == 0:
432 template.warn \
433 (("no previously-included directories found " +
434 "matching '%s'") %
435 dir_pattern)
436 else:
437 raise RuntimeError, \
438 "this cannot happen: invalid action '%s'" % action
439
440 # while loop over lines of template file
441
442 # read_template ()
443
444
445 def write_manifest (self):
446 """Write the file list in 'self.files' (presumably as filled in
447 by 'find_defaults()' and 'read_template()') to the manifest file
448 named by 'self.manifest'."""
449
450 manifest = open (self.manifest, "w")
451 for fn in self.files:
452 manifest.write (fn + '\n')
453 manifest.close ()
454
455 # write_manifest ()
456
457
458 def read_manifest (self):
459 """Read the manifest file (named by 'self.manifest') and use
460 it to fill in 'self.files', the list of files to include
461 in the source distribution."""
462
463 manifest = open (self.manifest)
464 while 1:
465 line = manifest.readline ()
466 if line == '': # end of file
467 break
468 if line[-1] == '\n':
469 line = line[0:-1]
470 self.files.append (line)
471
472 # read_manifest ()
473
474
475
476 def make_release_tree (self, base_dir, files):
477
Greg Ward578c10d2000-03-31 02:50:04 +0000478 # Create all the directories under 'base_dir' necessary to
479 # put 'files' there.
480 create_tree (base_dir, files,
481 verbose=self.verbose, dry_run=self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000482
483 # And walk over the list of files, either making a hard link (if
484 # os.link exists) to each one that doesn't already exist in its
485 # corresponding location under 'base_dir', or copying each file
486 # that's out-of-date in 'base_dir'. (Usually, all files will be
487 # out-of-date, because by default we blow away 'base_dir' when
488 # we're done making the distribution archives.)
489
Greg Ward578c10d2000-03-31 02:50:04 +0000490 if hasattr (os, 'link'): # can make hard links on this system
491 link = 'hard'
Greg Warda82122b2000-02-17 23:56:15 +0000492 msg = "making hard links in %s..." % base_dir
Greg Ward578c10d2000-03-31 02:50:04 +0000493 else: # nope, have to copy
494 link = None
Greg Warda82122b2000-02-17 23:56:15 +0000495 msg = "copying files to %s..." % base_dir
496
497 self.announce (msg)
498 for file in files:
499 dest = os.path.join (base_dir, file)
Greg Ward578c10d2000-03-31 02:50:04 +0000500 self.copy_file (file, dest, link=link)
Greg Warda82122b2000-02-17 23:56:15 +0000501
502 # make_release_tree ()
503
504
Greg Warda82122b2000-02-17 23:56:15 +0000505 def make_distribution (self):
506
Greg Ward578c10d2000-03-31 02:50:04 +0000507 # Don't warn about missing meta-data here -- should be (and is!)
508 # done elsewhere.
Greg Ward0ae7f762000-04-22 02:51:25 +0000509 base_dir = self.distribution.get_fullname()
Greg Warda82122b2000-02-17 23:56:15 +0000510
511 # Remove any files that match "base_dir" from the fileset -- we
512 # don't want to go distributing the distribution inside itself!
513 self.exclude_pattern (base_dir + "*")
514
515 self.make_release_tree (base_dir, self.files)
516 for fmt in self.formats:
Greg Ward578c10d2000-03-31 02:50:04 +0000517 self.make_archive (base_dir, fmt, base_dir=base_dir)
Greg Warda82122b2000-02-17 23:56:15 +0000518
519 if not self.keep_tree:
Greg Ward2dc139c2000-03-18 15:43:42 +0000520 remove_tree (base_dir, self.verbose, self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000521
522# class Dist
523
524
525# ----------------------------------------------------------------------
526# Utility functions
527
528def findall (dir = os.curdir):
529 """Find all files under 'dir' and return the list of full
530 filenames (relative to 'dir')."""
531
532 list = []
533 stack = [dir]
534 pop = stack.pop
535 push = stack.append
536
537 while stack:
538 dir = pop()
539 names = os.listdir (dir)
540
541 for name in names:
542 if dir != os.curdir: # avoid the dreaded "./" syndrome
543 fullname = os.path.join (dir, name)
544 else:
545 fullname = name
546 list.append (fullname)
547 if os.path.isdir (fullname) and not os.path.islink(fullname):
548 push (fullname)
549
550 return list
551
552
553def select_pattern (files, pattern, anchor=1, prefix=None):
554 """Select strings (presumably filenames) from 'files' that match
555 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not
556 quite the same as implemented by the 'fnmatch' module: '*' and '?'
557 match non-special characters, where "special" is platform-dependent:
558 slash on Unix, colon, slash, and backslash on DOS/Windows, and colon
559 on Mac OS.
560
561 If 'anchor' is true (the default), then the pattern match is more
562 stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
563 'anchor' is false, both of these will match.
564
565 If 'prefix' is supplied, then only filenames starting with 'prefix'
566 (itself a pattern) and ending with 'pattern', with anything in
567 between them, will match. 'anchor' is ignored in this case.
568
569 Return the list of matching strings, possibly empty."""
570
571 matches = []
572 pattern_re = translate_pattern (pattern, anchor, prefix)
573 print "select_pattern: applying re %s" % pattern_re.pattern
574 for name in files:
575 if pattern_re.search (name):
576 matches.append (name)
577 print " adding", name
578
579 return matches
580
581# select_pattern ()
582
583
584def exclude_pattern (files, pattern, anchor=1, prefix=None):
585
586 pattern_re = translate_pattern (pattern, anchor, prefix)
587 print "exclude_pattern: applying re %s" % pattern_re.pattern
588 for i in range (len(files)-1, -1, -1):
589 if pattern_re.search (files[i]):
590 print " removing", files[i]
591 del files[i]
592
593# exclude_pattern ()
594
595
596def glob_to_re (pattern):
597 """Translate a shell-like glob pattern to a regular expression;
598 return a string containing the regex. Differs from
599 'fnmatch.translate()' in that '*' does not match "special
600 characters" (which are platform-specific)."""
601 pattern_re = fnmatch.translate (pattern)
602
603 # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
604 # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
605 # and by extension they shouldn't match such "special characters" under
606 # any OS. So change all non-escaped dots in the RE to match any
607 # character except the special characters.
608 # XXX currently the "special characters" are just slash -- i.e. this is
609 # Unix-only.
610 pattern_re = re.sub (r'(^|[^\\])\.', r'\1[^/]', pattern_re)
611 return pattern_re
612
613# glob_to_re ()
614
615
616def translate_pattern (pattern, anchor=1, prefix=None):
617 """Translate a shell-like wildcard pattern to a compiled regular
618 expression. Return the compiled regex."""
619
620 if pattern:
621 pattern_re = glob_to_re (pattern)
622 else:
623 pattern_re = ''
624
625 if prefix is not None:
626 prefix_re = (glob_to_re (prefix))[0:-1] # ditch trailing $
627 pattern_re = "^" + os.path.join (prefix_re, ".*" + pattern_re)
628 else: # no prefix -- respect anchor flag
629 if anchor:
630 pattern_re = "^" + pattern_re
631
632 return re.compile (pattern_re)
633
634# translate_pattern ()