blob: af88eba07a57c91cc327f2b5ecc98ee729735edd [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 Wardd8dfb4c2000-05-31 02:32:10 +000014from distutils.util import newer, create_tree, remove_tree, convert_path, \
Greg Ward1b8e1d42000-04-26 01:12:40 +000015 write_file
Greg Ward6a9a5452000-04-22 03:11:55 +000016from distutils.archive_util import check_archive_formats
Greg Warda82122b2000-02-17 23:56:15 +000017from distutils.text_file import TextFile
Greg Ward6a9a5452000-04-22 03:11:55 +000018from distutils.errors import DistutilsExecError, DistutilsOptionError
Greg Warda82122b2000-02-17 23:56:15 +000019
20
Greg Ward1993f9a2000-02-18 00:13:53 +000021class sdist (Command):
Greg Warda82122b2000-02-17 23:56:15 +000022
23 description = "create a source distribution (tarball, zip file, etc.)"
24
Greg Wardbbeceea2000-02-18 00:25:39 +000025 user_options = [
26 ('template=', 't',
27 "name of manifest template file [default: MANIFEST.in]"),
28 ('manifest=', 'm',
29 "name of manifest file [default: MANIFEST]"),
30 ('use-defaults', None,
31 "include the default file set in the manifest "
32 "[default; disable with --no-defaults]"),
Greg Ward839d5322000-04-26 01:14:33 +000033 ('manifest-only', 'o',
Greg Wardbbeceea2000-02-18 00:25:39 +000034 "just regenerate the manifest and then stop"),
Greg Ward839d5322000-04-26 01:14:33 +000035 ('force-manifest', 'f',
Greg Wardbbeceea2000-02-18 00:25:39 +000036 "forcibly regenerate the manifest and carry on as usual"),
Greg Wardbbeceea2000-02-18 00:25:39 +000037 ('formats=', None,
Greg Wardf1948782000-04-25 01:38:20 +000038 "formats for source distribution (tar, ztar, gztar, bztar, or zip)"),
Greg Wardbbeceea2000-02-18 00:25:39 +000039 ('keep-tree', 'k',
40 "keep the distribution tree around after creating " +
41 "archive file(s)"),
42 ]
Greg Warda82122b2000-02-17 23:56:15 +000043 negative_opts = {'use-defaults': 'no-defaults'}
44
45 default_format = { 'posix': 'gztar',
46 'nt': 'zip' }
47
48 exclude_re = re.compile (r'\s*!\s*(\S+)') # for manifest lines
49
50
Greg Warde01149c2000-02-18 00:35:22 +000051 def initialize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000052 # 'template' and 'manifest' are, respectively, the names of
53 # the manifest template and manifest file.
54 self.template = None
55 self.manifest = None
56
57 # 'use_defaults': if true, we will include the default file set
58 # in the manifest
59 self.use_defaults = 1
60
61 self.manifest_only = 0
62 self.force_manifest = 0
63
64 self.formats = None
Greg Warda82122b2000-02-17 23:56:15 +000065 self.keep_tree = 0
66
Greg Wardd87eb732000-06-01 01:10:56 +000067 self.archive_files = None
68
Greg Warda82122b2000-02-17 23:56:15 +000069
Greg Warde01149c2000-02-18 00:35:22 +000070 def finalize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000071 if self.manifest is None:
72 self.manifest = "MANIFEST"
73 if self.template is None:
74 self.template = "MANIFEST.in"
75
Greg Ward62d5a572000-06-04 15:12:51 +000076 self.ensure_string_list('formats')
Greg Warda82122b2000-02-17 23:56:15 +000077 if self.formats is None:
78 try:
79 self.formats = [self.default_format[os.name]]
80 except KeyError:
81 raise DistutilsPlatformError, \
Greg Ward578c10d2000-03-31 02:50:04 +000082 "don't know how to create source distributions " + \
83 "on platform %s" % os.name
Greg Warda82122b2000-02-17 23:56:15 +000084
Greg Ward6a9a5452000-04-22 03:11:55 +000085 bad_format = check_archive_formats (self.formats)
86 if bad_format:
87 raise DistutilsOptionError, \
88 "unknown archive format '%s'" % bad_format
89
Greg Warda82122b2000-02-17 23:56:15 +000090
91 def run (self):
92
93 # 'files' is the list of files that will make up the manifest
94 self.files = []
95
96 # Ensure that all required meta-data is given; warn if not (but
97 # don't die, it's not *that* serious!)
98 self.check_metadata ()
99
100 # Do whatever it takes to get the list of files to process
101 # (process the manifest template, read an existing manifest,
102 # whatever). File list is put into 'self.files'.
103 self.get_file_list ()
104
105 # If user just wanted us to regenerate the manifest, stop now.
106 if self.manifest_only:
107 return
108
109 # Otherwise, go ahead and create the source distribution tarball,
110 # or zipfile, or whatever.
111 self.make_distribution ()
112
113
114 def check_metadata (self):
115
Greg Ward535f2d92000-04-21 04:37:12 +0000116 metadata = self.distribution.metadata
Greg Warda82122b2000-02-17 23:56:15 +0000117
118 missing = []
119 for attr in ('name', 'version', 'url'):
Greg Ward535f2d92000-04-21 04:37:12 +0000120 if not (hasattr (metadata, attr) and getattr (metadata, attr)):
Greg Warda82122b2000-02-17 23:56:15 +0000121 missing.append (attr)
122
123 if missing:
124 self.warn ("missing required meta-data: " +
125 string.join (missing, ", "))
126
Greg Ward535f2d92000-04-21 04:37:12 +0000127 if metadata.author:
128 if not metadata.author_email:
Greg Warda82122b2000-02-17 23:56:15 +0000129 self.warn ("missing meta-data: if 'author' supplied, " +
130 "'author_email' must be supplied too")
Greg Ward535f2d92000-04-21 04:37:12 +0000131 elif metadata.maintainer:
132 if not metadata.maintainer_email:
Greg Warda82122b2000-02-17 23:56:15 +0000133 self.warn ("missing meta-data: if 'maintainer' supplied, " +
134 "'maintainer_email' must be supplied too")
135 else:
136 self.warn ("missing meta-data: either (author and author_email) " +
137 "or (maintainer and maintainer_email) " +
138 "must be supplied")
139
140 # check_metadata ()
141
142
143 def get_file_list (self):
144 """Figure out the list of files to include in the source
145 distribution, and put it in 'self.files'. This might
146 involve reading the manifest template (and writing the
147 manifest), or just reading the manifest, or just using
148 the default file set -- it all depends on the user's
149 options and the state of the filesystem."""
150
151
152 template_exists = os.path.isfile (self.template)
153 if template_exists:
154 template_newer = newer (self.template, self.manifest)
155
156 # Regenerate the manifest if necessary (or if explicitly told to)
157 if ((template_exists and template_newer) or
158 self.force_manifest or
159 self.manifest_only):
160
161 if not template_exists:
162 self.warn (("manifest template '%s' does not exist " +
163 "(using default file list)") %
164 self.template)
165
166 # Add default file set to 'files'
167 if self.use_defaults:
168 self.find_defaults ()
169
170 # Read manifest template if it exists
171 if template_exists:
172 self.read_template ()
173
174 # File list now complete -- sort it so that higher-level files
175 # come first
176 sortable_files = map (os.path.split, self.files)
177 sortable_files.sort ()
178 self.files = []
179 for sort_tuple in sortable_files:
180 self.files.append (apply (os.path.join, sort_tuple))
181
182 # Remove duplicates from the file list
183 for i in range (len(self.files)-1, 0, -1):
184 if self.files[i] == self.files[i-1]:
185 del self.files[i]
186
187 # And write complete file list (including default file set) to
188 # the manifest.
189 self.write_manifest ()
190
191 # Don't regenerate the manifest, just read it in.
192 else:
193 self.read_manifest ()
194
195 # get_file_list ()
196
197
198 def find_defaults (self):
199
200 standards = [('README', 'README.txt'), 'setup.py']
201 for fn in standards:
202 if type (fn) is TupleType:
203 alts = fn
Greg Ward48401122000-02-24 03:17:43 +0000204 got_it = 0
Greg Warda82122b2000-02-17 23:56:15 +0000205 for fn in alts:
206 if os.path.exists (fn):
207 got_it = 1
208 self.files.append (fn)
209 break
210
211 if not got_it:
212 self.warn ("standard file not found: should have one of " +
213 string.join (alts, ', '))
214 else:
215 if os.path.exists (fn):
216 self.files.append (fn)
217 else:
218 self.warn ("standard file '%s' not found" % fn)
219
220 optional = ['test/test*.py']
221 for pattern in optional:
222 files = filter (os.path.isfile, glob (pattern))
223 if files:
224 self.files.extend (files)
225
Greg Ward578c10d2000-03-31 02:50:04 +0000226 if self.distribution.has_pure_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000227 build_py = self.get_finalized_command ('build_py')
Greg Warda82122b2000-02-17 23:56:15 +0000228 self.files.extend (build_py.get_source_files ())
229
Greg Ward578c10d2000-03-31 02:50:04 +0000230 if self.distribution.has_ext_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000231 build_ext = self.get_finalized_command ('build_ext')
Greg Warda82122b2000-02-17 23:56:15 +0000232 self.files.extend (build_ext.get_source_files ())
233
Greg Ward60908f12000-04-09 03:51:40 +0000234 if self.distribution.has_c_libraries():
Greg Ward4fb29e52000-05-27 17:27:23 +0000235 build_clib = self.get_finalized_command ('build_clib')
Greg Ward60908f12000-04-09 03:51:40 +0000236 self.files.extend (build_clib.get_source_files ())
237
Greg Warda82122b2000-02-17 23:56:15 +0000238
Greg Warda82122b2000-02-17 23:56:15 +0000239 def search_dir (self, dir, pattern=None):
240 """Recursively find files under 'dir' matching 'pattern' (a string
241 containing a Unix-style glob pattern). If 'pattern' is None,
242 find all files under 'dir'. Return the list of found
243 filenames."""
244
245 allfiles = findall (dir)
246 if pattern is None:
247 return allfiles
248
249 pattern_re = translate_pattern (pattern)
250 files = []
251 for file in allfiles:
252 if pattern_re.match (os.path.basename (file)):
253 files.append (file)
254
255 return files
256
257 # search_dir ()
258
259
260 def exclude_pattern (self, pattern):
261 """Remove filenames from 'self.files' that match 'pattern'."""
262 print "exclude_pattern: pattern=%s" % pattern
263 pattern_re = translate_pattern (pattern)
264 for i in range (len (self.files)-1, -1, -1):
265 if pattern_re.match (self.files[i]):
266 print "removing %s" % self.files[i]
267 del self.files[i]
268
269
270 def recursive_exclude_pattern (self, dir, pattern=None):
271 """Remove filenames from 'self.files' that are under 'dir'
272 and whose basenames match 'pattern'."""
273
274 print "recursive_exclude_pattern: dir=%s, pattern=%s" % (dir, pattern)
275 if pattern is None:
276 pattern_re = None
277 else:
278 pattern_re = translate_pattern (pattern)
279
280 for i in range (len (self.files)-1, -1, -1):
281 (cur_dir, cur_base) = os.path.split (self.files[i])
282 if (cur_dir == dir and
283 (pattern_re is None or pattern_re.match (cur_base))):
284 print "removing %s" % self.files[i]
285 del self.files[i]
286
287
288 def read_template (self):
289 """Read and parse the manifest template file named by
290 'self.template' (usually "MANIFEST.in"). Process all file
291 specifications (include and exclude) in the manifest template
292 and add the resulting filenames to 'self.files'."""
293
294 assert self.files is not None and type (self.files) is ListType
295
296 template = TextFile (self.template,
297 strip_comments=1,
298 skip_blanks=1,
299 join_lines=1,
300 lstrip_ws=1,
301 rstrip_ws=1,
302 collapse_ws=1)
303
304 all_files = findall ()
305
306 while 1:
307
308 line = template.readline()
309 if line is None: # end of file
310 break
311
312 words = string.split (line)
313 action = words[0]
314
315 # First, check that the right number of words are present
316 # for the given action (which is the first word)
317 if action in ('include','exclude',
318 'global-include','global-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000319 if len (words) < 2:
Greg Warda82122b2000-02-17 23:56:15 +0000320 template.warn \
321 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000322 "'%s' expects <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000323 action)
324 continue
325
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000326 pattern_list = map(convert_path, words[1:])
Greg Warda82122b2000-02-17 23:56:15 +0000327
328 elif action in ('recursive-include','recursive-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000329 if len (words) < 3:
Greg Warda82122b2000-02-17 23:56:15 +0000330 template.warn \
331 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000332 "'%s' expects <dir> <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000333 action)
334 continue
335
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000336 dir = convert_path(words[1])
337 pattern_list = map (convert_path, words[2:])
Greg Warda82122b2000-02-17 23:56:15 +0000338
339 elif action in ('graft','prune'):
340 if len (words) != 2:
341 template.warn \
342 ("invalid manifest template line: " +
343 "'%s' expects a single <dir_pattern>" %
344 action)
345 continue
346
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000347 dir_pattern = convert_path (words[1])
Greg Warda82122b2000-02-17 23:56:15 +0000348
349 else:
350 template.warn ("invalid manifest template line: " +
351 "unknown action '%s'" % action)
352 continue
353
354 # OK, now we know that the action is valid and we have the
355 # right number of words on the line for that action -- so we
356 # can proceed with minimal error-checking. Also, we have
Greg Ward2b9e43f2000-04-14 00:49:30 +0000357 # defined either (pattern), (dir and pattern), or
358 # (dir_pattern) -- so we don't have to spend any time
359 # digging stuff up out of 'words'.
Greg Warda82122b2000-02-17 23:56:15 +0000360
361 if action == 'include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000362 print "include", string.join(pattern_list)
363 for pattern in pattern_list:
364 files = select_pattern (all_files, pattern, anchor=1)
365 if not files:
366 template.warn ("no files found matching '%s'" % pattern)
367 else:
368 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000369
370 elif action == 'exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000371 print "exclude", string.join(pattern_list)
372 for pattern in pattern_list:
373 num = exclude_pattern (self.files, pattern, anchor=1)
374 if num == 0:
375 template.warn (
376 "no previously-included files found matching '%s'"%
377 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000378
379 elif action == 'global-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000380 print "global-include", string.join(pattern_list)
381 for pattern in pattern_list:
382 files = select_pattern (all_files, pattern, anchor=0)
383 if not files:
384 template.warn (("no files found matching '%s' " +
385 "anywhere in distribution") %
386 pattern)
387 else:
388 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000389
390 elif action == 'global-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000391 print "global-exclude", string.join(pattern_list)
392 for pattern in pattern_list:
393 num = exclude_pattern (self.files, pattern, anchor=0)
394 if num == 0:
395 template.warn \
396 (("no previously-included files matching '%s' " +
397 "found anywhere in distribution") %
398 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000399
400 elif action == 'recursive-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000401 print "recursive-include", dir, string.join(pattern_list)
402 for pattern in pattern_list:
403 files = select_pattern (all_files, pattern, prefix=dir)
404 if not files:
405 template.warn (("no files found matching '%s' " +
406 "under directory '%s'") %
407 (pattern, dir))
408 else:
409 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000410
411 elif action == 'recursive-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000412 print "recursive-exclude", dir, string.join(pattern_list)
413 for pattern in pattern_list:
414 num = exclude_pattern (self.files, pattern, prefix=dir)
415 if num == 0:
416 template.warn \
417 (("no previously-included files matching '%s' " +
418 "found under directory '%s'") %
419 (pattern, dir))
Greg Warda82122b2000-02-17 23:56:15 +0000420
421 elif action == 'graft':
422 print "graft", dir_pattern
423 files = select_pattern (all_files, None, prefix=dir_pattern)
424 if not files:
425 template.warn ("no directories found matching '%s'" %
426 dir_pattern)
427 else:
428 self.files.extend (files)
429
430 elif action == 'prune':
431 print "prune", dir_pattern
432 num = exclude_pattern (self.files, None, prefix=dir_pattern)
433 if num == 0:
434 template.warn \
435 (("no previously-included directories found " +
436 "matching '%s'") %
437 dir_pattern)
438 else:
439 raise RuntimeError, \
440 "this cannot happen: invalid action '%s'" % action
441
442 # while loop over lines of template file
443
Greg Warde5b267c2000-05-27 03:03:23 +0000444 # Prune away the build and source distribution directories
Greg Ward4fb29e52000-05-27 17:27:23 +0000445 build = self.get_finalized_command ('build')
Greg Warde5b267c2000-05-27 03:03:23 +0000446 exclude_pattern (self.files, None, prefix=build.build_base)
447
448 base_dir = self.distribution.get_fullname()
449 exclude_pattern (self.files, None, prefix=base_dir)
450
Greg Warda82122b2000-02-17 23:56:15 +0000451 # read_template ()
452
453
454 def write_manifest (self):
455 """Write the file list in 'self.files' (presumably as filled in
456 by 'find_defaults()' and 'read_template()') to the manifest file
457 named by 'self.manifest'."""
458
Greg Ward1b8e1d42000-04-26 01:12:40 +0000459 self.execute(write_file,
460 (self.manifest, self.files),
461 "writing manifest file")
Greg Warda82122b2000-02-17 23:56:15 +0000462
463 # write_manifest ()
464
465
466 def read_manifest (self):
467 """Read the manifest file (named by 'self.manifest') and use
468 it to fill in 'self.files', the list of files to include
469 in the source distribution."""
470
471 manifest = open (self.manifest)
472 while 1:
473 line = manifest.readline ()
474 if line == '': # end of file
475 break
476 if line[-1] == '\n':
477 line = line[0:-1]
478 self.files.append (line)
479
480 # read_manifest ()
481
482
483
484 def make_release_tree (self, base_dir, files):
485
Greg Ward578c10d2000-03-31 02:50:04 +0000486 # Create all the directories under 'base_dir' necessary to
487 # put 'files' there.
488 create_tree (base_dir, files,
489 verbose=self.verbose, dry_run=self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000490
491 # And walk over the list of files, either making a hard link (if
492 # os.link exists) to each one that doesn't already exist in its
493 # corresponding location under 'base_dir', or copying each file
494 # that's out-of-date in 'base_dir'. (Usually, all files will be
495 # out-of-date, because by default we blow away 'base_dir' when
496 # we're done making the distribution archives.)
497
Greg Ward578c10d2000-03-31 02:50:04 +0000498 if hasattr (os, 'link'): # can make hard links on this system
499 link = 'hard'
Greg Warda82122b2000-02-17 23:56:15 +0000500 msg = "making hard links in %s..." % base_dir
Greg Ward578c10d2000-03-31 02:50:04 +0000501 else: # nope, have to copy
502 link = None
Greg Warda82122b2000-02-17 23:56:15 +0000503 msg = "copying files to %s..." % base_dir
504
505 self.announce (msg)
506 for file in files:
507 dest = os.path.join (base_dir, file)
Greg Ward578c10d2000-03-31 02:50:04 +0000508 self.copy_file (file, dest, link=link)
Greg Warda82122b2000-02-17 23:56:15 +0000509
510 # make_release_tree ()
511
512
Greg Warda82122b2000-02-17 23:56:15 +0000513 def make_distribution (self):
514
Greg Ward578c10d2000-03-31 02:50:04 +0000515 # Don't warn about missing meta-data here -- should be (and is!)
516 # done elsewhere.
Greg Ward0ae7f762000-04-22 02:51:25 +0000517 base_dir = self.distribution.get_fullname()
Greg Warda82122b2000-02-17 23:56:15 +0000518
519 # Remove any files that match "base_dir" from the fileset -- we
520 # don't want to go distributing the distribution inside itself!
521 self.exclude_pattern (base_dir + "*")
522
523 self.make_release_tree (base_dir, self.files)
Greg Wardd87eb732000-06-01 01:10:56 +0000524 archive_files = [] # remember names of files we create
Greg Warda82122b2000-02-17 23:56:15 +0000525 for fmt in self.formats:
Greg Wardd87eb732000-06-01 01:10:56 +0000526 file = self.make_archive (base_dir, fmt, base_dir=base_dir)
527 archive_files.append(file)
528
529 self.archive_files = archive_files
Greg Warda82122b2000-02-17 23:56:15 +0000530
531 if not self.keep_tree:
Greg Ward2dc139c2000-03-18 15:43:42 +0000532 remove_tree (base_dir, self.verbose, self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000533
Greg Wardd87eb732000-06-01 01:10:56 +0000534 def get_archive_files (self):
535 """Return the list of archive files created when the command
536 was run, or None if the command hasn't run yet.
537 """
538 return self.archive_files
539
Greg Wardfcd974e2000-05-25 01:10:04 +0000540# class sdist
Greg Warda82122b2000-02-17 23:56:15 +0000541
542
543# ----------------------------------------------------------------------
544# Utility functions
545
546def findall (dir = os.curdir):
547 """Find all files under 'dir' and return the list of full
548 filenames (relative to 'dir')."""
549
550 list = []
551 stack = [dir]
552 pop = stack.pop
553 push = stack.append
554
555 while stack:
556 dir = pop()
557 names = os.listdir (dir)
558
559 for name in names:
560 if dir != os.curdir: # avoid the dreaded "./" syndrome
561 fullname = os.path.join (dir, name)
562 else:
563 fullname = name
564 list.append (fullname)
565 if os.path.isdir (fullname) and not os.path.islink(fullname):
566 push (fullname)
567
568 return list
569
570
571def select_pattern (files, pattern, anchor=1, prefix=None):
572 """Select strings (presumably filenames) from 'files' that match
573 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not
574 quite the same as implemented by the 'fnmatch' module: '*' and '?'
575 match non-special characters, where "special" is platform-dependent:
576 slash on Unix, colon, slash, and backslash on DOS/Windows, and colon
577 on Mac OS.
578
579 If 'anchor' is true (the default), then the pattern match is more
580 stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
581 'anchor' is false, both of these will match.
582
583 If 'prefix' is supplied, then only filenames starting with 'prefix'
584 (itself a pattern) and ending with 'pattern', with anything in
585 between them, will match. 'anchor' is ignored in this case.
586
587 Return the list of matching strings, possibly empty."""
588
589 matches = []
590 pattern_re = translate_pattern (pattern, anchor, prefix)
591 print "select_pattern: applying re %s" % pattern_re.pattern
592 for name in files:
593 if pattern_re.search (name):
594 matches.append (name)
595 print " adding", name
596
597 return matches
598
599# select_pattern ()
600
601
602def exclude_pattern (files, pattern, anchor=1, prefix=None):
603
604 pattern_re = translate_pattern (pattern, anchor, prefix)
605 print "exclude_pattern: applying re %s" % pattern_re.pattern
606 for i in range (len(files)-1, -1, -1):
607 if pattern_re.search (files[i]):
608 print " removing", files[i]
609 del files[i]
610
611# exclude_pattern ()
612
613
614def glob_to_re (pattern):
615 """Translate a shell-like glob pattern to a regular expression;
616 return a string containing the regex. Differs from
617 'fnmatch.translate()' in that '*' does not match "special
618 characters" (which are platform-specific)."""
619 pattern_re = fnmatch.translate (pattern)
620
621 # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
622 # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
623 # and by extension they shouldn't match such "special characters" under
624 # any OS. So change all non-escaped dots in the RE to match any
625 # character except the special characters.
626 # XXX currently the "special characters" are just slash -- i.e. this is
627 # Unix-only.
628 pattern_re = re.sub (r'(^|[^\\])\.', r'\1[^/]', pattern_re)
629 return pattern_re
630
631# glob_to_re ()
632
633
634def translate_pattern (pattern, anchor=1, prefix=None):
635 """Translate a shell-like wildcard pattern to a compiled regular
636 expression. Return the compiled regex."""
637
638 if pattern:
639 pattern_re = glob_to_re (pattern)
640 else:
641 pattern_re = ''
642
643 if prefix is not None:
644 prefix_re = (glob_to_re (prefix))[0:-1] # ditch trailing $
645 pattern_re = "^" + os.path.join (prefix_re, ".*" + pattern_re)
646 else: # no prefix -- respect anchor flag
647 if anchor:
648 pattern_re = "^" + pattern_re
649
650 return re.compile (pattern_re)
651
652# translate_pattern ()