blob: 03de85b1aee35d0f0936a83d14c5eb6cea04f1be [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
76 if self.formats is None:
77 try:
78 self.formats = [self.default_format[os.name]]
79 except KeyError:
80 raise DistutilsPlatformError, \
Greg Ward578c10d2000-03-31 02:50:04 +000081 "don't know how to create source distributions " + \
82 "on platform %s" % os.name
Greg Warda82122b2000-02-17 23:56:15 +000083 elif type (self.formats) is StringType:
84 self.formats = string.split (self.formats, ',')
85
Greg Ward6a9a5452000-04-22 03:11:55 +000086 bad_format = check_archive_formats (self.formats)
87 if bad_format:
88 raise DistutilsOptionError, \
89 "unknown archive format '%s'" % bad_format
90
Greg Warda82122b2000-02-17 23:56:15 +000091
92 def run (self):
93
94 # 'files' is the list of files that will make up the manifest
95 self.files = []
96
97 # Ensure that all required meta-data is given; warn if not (but
98 # don't die, it's not *that* serious!)
99 self.check_metadata ()
100
101 # Do whatever it takes to get the list of files to process
102 # (process the manifest template, read an existing manifest,
103 # whatever). File list is put into 'self.files'.
104 self.get_file_list ()
105
106 # If user just wanted us to regenerate the manifest, stop now.
107 if self.manifest_only:
108 return
109
110 # Otherwise, go ahead and create the source distribution tarball,
111 # or zipfile, or whatever.
112 self.make_distribution ()
113
114
115 def check_metadata (self):
116
Greg Ward535f2d92000-04-21 04:37:12 +0000117 metadata = self.distribution.metadata
Greg Warda82122b2000-02-17 23:56:15 +0000118
119 missing = []
120 for attr in ('name', 'version', 'url'):
Greg Ward535f2d92000-04-21 04:37:12 +0000121 if not (hasattr (metadata, attr) and getattr (metadata, attr)):
Greg Warda82122b2000-02-17 23:56:15 +0000122 missing.append (attr)
123
124 if missing:
125 self.warn ("missing required meta-data: " +
126 string.join (missing, ", "))
127
Greg Ward535f2d92000-04-21 04:37:12 +0000128 if metadata.author:
129 if not metadata.author_email:
Greg Warda82122b2000-02-17 23:56:15 +0000130 self.warn ("missing meta-data: if 'author' supplied, " +
131 "'author_email' must be supplied too")
Greg Ward535f2d92000-04-21 04:37:12 +0000132 elif metadata.maintainer:
133 if not metadata.maintainer_email:
Greg Warda82122b2000-02-17 23:56:15 +0000134 self.warn ("missing meta-data: if 'maintainer' supplied, " +
135 "'maintainer_email' must be supplied too")
136 else:
137 self.warn ("missing meta-data: either (author and author_email) " +
138 "or (maintainer and maintainer_email) " +
139 "must be supplied")
140
141 # check_metadata ()
142
143
144 def get_file_list (self):
145 """Figure out the list of files to include in the source
146 distribution, and put it in 'self.files'. This might
147 involve reading the manifest template (and writing the
148 manifest), or just reading the manifest, or just using
149 the default file set -- it all depends on the user's
150 options and the state of the filesystem."""
151
152
153 template_exists = os.path.isfile (self.template)
154 if template_exists:
155 template_newer = newer (self.template, self.manifest)
156
157 # Regenerate the manifest if necessary (or if explicitly told to)
158 if ((template_exists and template_newer) or
159 self.force_manifest or
160 self.manifest_only):
161
162 if not template_exists:
163 self.warn (("manifest template '%s' does not exist " +
164 "(using default file list)") %
165 self.template)
166
167 # Add default file set to 'files'
168 if self.use_defaults:
169 self.find_defaults ()
170
171 # Read manifest template if it exists
172 if template_exists:
173 self.read_template ()
174
175 # File list now complete -- sort it so that higher-level files
176 # come first
177 sortable_files = map (os.path.split, self.files)
178 sortable_files.sort ()
179 self.files = []
180 for sort_tuple in sortable_files:
181 self.files.append (apply (os.path.join, sort_tuple))
182
183 # Remove duplicates from the file list
184 for i in range (len(self.files)-1, 0, -1):
185 if self.files[i] == self.files[i-1]:
186 del self.files[i]
187
188 # And write complete file list (including default file set) to
189 # the manifest.
190 self.write_manifest ()
191
192 # Don't regenerate the manifest, just read it in.
193 else:
194 self.read_manifest ()
195
196 # get_file_list ()
197
198
199 def find_defaults (self):
200
201 standards = [('README', 'README.txt'), 'setup.py']
202 for fn in standards:
203 if type (fn) is TupleType:
204 alts = fn
Greg Ward48401122000-02-24 03:17:43 +0000205 got_it = 0
Greg Warda82122b2000-02-17 23:56:15 +0000206 for fn in alts:
207 if os.path.exists (fn):
208 got_it = 1
209 self.files.append (fn)
210 break
211
212 if not got_it:
213 self.warn ("standard file not found: should have one of " +
214 string.join (alts, ', '))
215 else:
216 if os.path.exists (fn):
217 self.files.append (fn)
218 else:
219 self.warn ("standard file '%s' not found" % fn)
220
221 optional = ['test/test*.py']
222 for pattern in optional:
223 files = filter (os.path.isfile, glob (pattern))
224 if files:
225 self.files.extend (files)
226
Greg Ward578c10d2000-03-31 02:50:04 +0000227 if self.distribution.has_pure_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000228 build_py = self.get_finalized_command ('build_py')
Greg Warda82122b2000-02-17 23:56:15 +0000229 self.files.extend (build_py.get_source_files ())
230
Greg Ward578c10d2000-03-31 02:50:04 +0000231 if self.distribution.has_ext_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000232 build_ext = self.get_finalized_command ('build_ext')
Greg Warda82122b2000-02-17 23:56:15 +0000233 self.files.extend (build_ext.get_source_files ())
234
Greg Ward60908f12000-04-09 03:51:40 +0000235 if self.distribution.has_c_libraries():
Greg Ward4fb29e52000-05-27 17:27:23 +0000236 build_clib = self.get_finalized_command ('build_clib')
Greg Ward60908f12000-04-09 03:51:40 +0000237 self.files.extend (build_clib.get_source_files ())
238
Greg Warda82122b2000-02-17 23:56:15 +0000239
Greg Warda82122b2000-02-17 23:56:15 +0000240 def search_dir (self, dir, pattern=None):
241 """Recursively find files under 'dir' matching 'pattern' (a string
242 containing a Unix-style glob pattern). If 'pattern' is None,
243 find all files under 'dir'. Return the list of found
244 filenames."""
245
246 allfiles = findall (dir)
247 if pattern is None:
248 return allfiles
249
250 pattern_re = translate_pattern (pattern)
251 files = []
252 for file in allfiles:
253 if pattern_re.match (os.path.basename (file)):
254 files.append (file)
255
256 return files
257
258 # search_dir ()
259
260
261 def exclude_pattern (self, pattern):
262 """Remove filenames from 'self.files' that match 'pattern'."""
263 print "exclude_pattern: pattern=%s" % pattern
264 pattern_re = translate_pattern (pattern)
265 for i in range (len (self.files)-1, -1, -1):
266 if pattern_re.match (self.files[i]):
267 print "removing %s" % self.files[i]
268 del self.files[i]
269
270
271 def recursive_exclude_pattern (self, dir, pattern=None):
272 """Remove filenames from 'self.files' that are under 'dir'
273 and whose basenames match 'pattern'."""
274
275 print "recursive_exclude_pattern: dir=%s, pattern=%s" % (dir, pattern)
276 if pattern is None:
277 pattern_re = None
278 else:
279 pattern_re = translate_pattern (pattern)
280
281 for i in range (len (self.files)-1, -1, -1):
282 (cur_dir, cur_base) = os.path.split (self.files[i])
283 if (cur_dir == dir and
284 (pattern_re is None or pattern_re.match (cur_base))):
285 print "removing %s" % self.files[i]
286 del self.files[i]
287
288
289 def read_template (self):
290 """Read and parse the manifest template file named by
291 'self.template' (usually "MANIFEST.in"). Process all file
292 specifications (include and exclude) in the manifest template
293 and add the resulting filenames to 'self.files'."""
294
295 assert self.files is not None and type (self.files) is ListType
296
297 template = TextFile (self.template,
298 strip_comments=1,
299 skip_blanks=1,
300 join_lines=1,
301 lstrip_ws=1,
302 rstrip_ws=1,
303 collapse_ws=1)
304
305 all_files = findall ()
306
307 while 1:
308
309 line = template.readline()
310 if line is None: # end of file
311 break
312
313 words = string.split (line)
314 action = words[0]
315
316 # First, check that the right number of words are present
317 # for the given action (which is the first word)
318 if action in ('include','exclude',
319 'global-include','global-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000320 if len (words) < 2:
Greg Warda82122b2000-02-17 23:56:15 +0000321 template.warn \
322 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000323 "'%s' expects <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000324 action)
325 continue
326
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000327 pattern_list = map(convert_path, words[1:])
Greg Warda82122b2000-02-17 23:56:15 +0000328
329 elif action in ('recursive-include','recursive-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000330 if len (words) < 3:
Greg Warda82122b2000-02-17 23:56:15 +0000331 template.warn \
332 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000333 "'%s' expects <dir> <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000334 action)
335 continue
336
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000337 dir = convert_path(words[1])
338 pattern_list = map (convert_path, words[2:])
Greg Warda82122b2000-02-17 23:56:15 +0000339
340 elif action in ('graft','prune'):
341 if len (words) != 2:
342 template.warn \
343 ("invalid manifest template line: " +
344 "'%s' expects a single <dir_pattern>" %
345 action)
346 continue
347
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000348 dir_pattern = convert_path (words[1])
Greg Warda82122b2000-02-17 23:56:15 +0000349
350 else:
351 template.warn ("invalid manifest template line: " +
352 "unknown action '%s'" % action)
353 continue
354
355 # OK, now we know that the action is valid and we have the
356 # right number of words on the line for that action -- so we
357 # can proceed with minimal error-checking. Also, we have
Greg Ward2b9e43f2000-04-14 00:49:30 +0000358 # defined either (pattern), (dir and pattern), or
359 # (dir_pattern) -- so we don't have to spend any time
360 # digging stuff up out of 'words'.
Greg Warda82122b2000-02-17 23:56:15 +0000361
362 if action == 'include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000363 print "include", string.join(pattern_list)
364 for pattern in pattern_list:
365 files = select_pattern (all_files, pattern, anchor=1)
366 if not files:
367 template.warn ("no files found matching '%s'" % pattern)
368 else:
369 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000370
371 elif action == 'exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000372 print "exclude", string.join(pattern_list)
373 for pattern in pattern_list:
374 num = exclude_pattern (self.files, pattern, anchor=1)
375 if num == 0:
376 template.warn (
377 "no previously-included files found matching '%s'"%
378 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000379
380 elif action == 'global-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000381 print "global-include", string.join(pattern_list)
382 for pattern in pattern_list:
383 files = select_pattern (all_files, pattern, anchor=0)
384 if not files:
385 template.warn (("no files found matching '%s' " +
386 "anywhere in distribution") %
387 pattern)
388 else:
389 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000390
391 elif action == 'global-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000392 print "global-exclude", string.join(pattern_list)
393 for pattern in pattern_list:
394 num = exclude_pattern (self.files, pattern, anchor=0)
395 if num == 0:
396 template.warn \
397 (("no previously-included files matching '%s' " +
398 "found anywhere in distribution") %
399 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000400
401 elif action == 'recursive-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000402 print "recursive-include", dir, string.join(pattern_list)
403 for pattern in pattern_list:
404 files = select_pattern (all_files, pattern, prefix=dir)
405 if not files:
406 template.warn (("no files found matching '%s' " +
407 "under directory '%s'") %
408 (pattern, dir))
409 else:
410 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000411
412 elif action == 'recursive-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000413 print "recursive-exclude", dir, string.join(pattern_list)
414 for pattern in pattern_list:
415 num = exclude_pattern (self.files, pattern, prefix=dir)
416 if num == 0:
417 template.warn \
418 (("no previously-included files matching '%s' " +
419 "found under directory '%s'") %
420 (pattern, dir))
Greg Warda82122b2000-02-17 23:56:15 +0000421
422 elif action == 'graft':
423 print "graft", dir_pattern
424 files = select_pattern (all_files, None, prefix=dir_pattern)
425 if not files:
426 template.warn ("no directories found matching '%s'" %
427 dir_pattern)
428 else:
429 self.files.extend (files)
430
431 elif action == 'prune':
432 print "prune", dir_pattern
433 num = exclude_pattern (self.files, None, prefix=dir_pattern)
434 if num == 0:
435 template.warn \
436 (("no previously-included directories found " +
437 "matching '%s'") %
438 dir_pattern)
439 else:
440 raise RuntimeError, \
441 "this cannot happen: invalid action '%s'" % action
442
443 # while loop over lines of template file
444
Greg Warde5b267c2000-05-27 03:03:23 +0000445 # Prune away the build and source distribution directories
Greg Ward4fb29e52000-05-27 17:27:23 +0000446 build = self.get_finalized_command ('build')
Greg Warde5b267c2000-05-27 03:03:23 +0000447 exclude_pattern (self.files, None, prefix=build.build_base)
448
449 base_dir = self.distribution.get_fullname()
450 exclude_pattern (self.files, None, prefix=base_dir)
451
Greg Warda82122b2000-02-17 23:56:15 +0000452 # read_template ()
453
454
455 def write_manifest (self):
456 """Write the file list in 'self.files' (presumably as filled in
457 by 'find_defaults()' and 'read_template()') to the manifest file
458 named by 'self.manifest'."""
459
Greg Ward1b8e1d42000-04-26 01:12:40 +0000460 self.execute(write_file,
461 (self.manifest, self.files),
462 "writing manifest file")
Greg Warda82122b2000-02-17 23:56:15 +0000463
464 # write_manifest ()
465
466
467 def read_manifest (self):
468 """Read the manifest file (named by 'self.manifest') and use
469 it to fill in 'self.files', the list of files to include
470 in the source distribution."""
471
472 manifest = open (self.manifest)
473 while 1:
474 line = manifest.readline ()
475 if line == '': # end of file
476 break
477 if line[-1] == '\n':
478 line = line[0:-1]
479 self.files.append (line)
480
481 # read_manifest ()
482
483
484
485 def make_release_tree (self, base_dir, files):
486
Greg Ward578c10d2000-03-31 02:50:04 +0000487 # Create all the directories under 'base_dir' necessary to
488 # put 'files' there.
489 create_tree (base_dir, files,
490 verbose=self.verbose, dry_run=self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000491
492 # And walk over the list of files, either making a hard link (if
493 # os.link exists) to each one that doesn't already exist in its
494 # corresponding location under 'base_dir', or copying each file
495 # that's out-of-date in 'base_dir'. (Usually, all files will be
496 # out-of-date, because by default we blow away 'base_dir' when
497 # we're done making the distribution archives.)
498
Greg Ward578c10d2000-03-31 02:50:04 +0000499 if hasattr (os, 'link'): # can make hard links on this system
500 link = 'hard'
Greg Warda82122b2000-02-17 23:56:15 +0000501 msg = "making hard links in %s..." % base_dir
Greg Ward578c10d2000-03-31 02:50:04 +0000502 else: # nope, have to copy
503 link = None
Greg Warda82122b2000-02-17 23:56:15 +0000504 msg = "copying files to %s..." % base_dir
505
506 self.announce (msg)
507 for file in files:
508 dest = os.path.join (base_dir, file)
Greg Ward578c10d2000-03-31 02:50:04 +0000509 self.copy_file (file, dest, link=link)
Greg Warda82122b2000-02-17 23:56:15 +0000510
511 # make_release_tree ()
512
513
Greg Warda82122b2000-02-17 23:56:15 +0000514 def make_distribution (self):
515
Greg Ward578c10d2000-03-31 02:50:04 +0000516 # Don't warn about missing meta-data here -- should be (and is!)
517 # done elsewhere.
Greg Ward0ae7f762000-04-22 02:51:25 +0000518 base_dir = self.distribution.get_fullname()
Greg Warda82122b2000-02-17 23:56:15 +0000519
520 # Remove any files that match "base_dir" from the fileset -- we
521 # don't want to go distributing the distribution inside itself!
522 self.exclude_pattern (base_dir + "*")
523
524 self.make_release_tree (base_dir, self.files)
Greg Wardd87eb732000-06-01 01:10:56 +0000525 archive_files = [] # remember names of files we create
Greg Warda82122b2000-02-17 23:56:15 +0000526 for fmt in self.formats:
Greg Wardd87eb732000-06-01 01:10:56 +0000527 file = self.make_archive (base_dir, fmt, base_dir=base_dir)
528 archive_files.append(file)
529
530 self.archive_files = archive_files
Greg Warda82122b2000-02-17 23:56:15 +0000531
532 if not self.keep_tree:
Greg Ward2dc139c2000-03-18 15:43:42 +0000533 remove_tree (base_dir, self.verbose, self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000534
Greg Wardd87eb732000-06-01 01:10:56 +0000535 def get_archive_files (self):
536 """Return the list of archive files created when the command
537 was run, or None if the command hasn't run yet.
538 """
539 return self.archive_files
540
Greg Wardfcd974e2000-05-25 01:10:04 +0000541# class sdist
Greg Warda82122b2000-02-17 23:56:15 +0000542
543
544# ----------------------------------------------------------------------
545# Utility functions
546
547def findall (dir = os.curdir):
548 """Find all files under 'dir' and return the list of full
549 filenames (relative to 'dir')."""
550
551 list = []
552 stack = [dir]
553 pop = stack.pop
554 push = stack.append
555
556 while stack:
557 dir = pop()
558 names = os.listdir (dir)
559
560 for name in names:
561 if dir != os.curdir: # avoid the dreaded "./" syndrome
562 fullname = os.path.join (dir, name)
563 else:
564 fullname = name
565 list.append (fullname)
566 if os.path.isdir (fullname) and not os.path.islink(fullname):
567 push (fullname)
568
569 return list
570
571
572def select_pattern (files, pattern, anchor=1, prefix=None):
573 """Select strings (presumably filenames) from 'files' that match
574 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not
575 quite the same as implemented by the 'fnmatch' module: '*' and '?'
576 match non-special characters, where "special" is platform-dependent:
577 slash on Unix, colon, slash, and backslash on DOS/Windows, and colon
578 on Mac OS.
579
580 If 'anchor' is true (the default), then the pattern match is more
581 stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
582 'anchor' is false, both of these will match.
583
584 If 'prefix' is supplied, then only filenames starting with 'prefix'
585 (itself a pattern) and ending with 'pattern', with anything in
586 between them, will match. 'anchor' is ignored in this case.
587
588 Return the list of matching strings, possibly empty."""
589
590 matches = []
591 pattern_re = translate_pattern (pattern, anchor, prefix)
592 print "select_pattern: applying re %s" % pattern_re.pattern
593 for name in files:
594 if pattern_re.search (name):
595 matches.append (name)
596 print " adding", name
597
598 return matches
599
600# select_pattern ()
601
602
603def exclude_pattern (files, pattern, anchor=1, prefix=None):
604
605 pattern_re = translate_pattern (pattern, anchor, prefix)
606 print "exclude_pattern: applying re %s" % pattern_re.pattern
607 for i in range (len(files)-1, -1, -1):
608 if pattern_re.search (files[i]):
609 print " removing", files[i]
610 del files[i]
611
612# exclude_pattern ()
613
614
615def glob_to_re (pattern):
616 """Translate a shell-like glob pattern to a regular expression;
617 return a string containing the regex. Differs from
618 'fnmatch.translate()' in that '*' does not match "special
619 characters" (which are platform-specific)."""
620 pattern_re = fnmatch.translate (pattern)
621
622 # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
623 # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
624 # and by extension they shouldn't match such "special characters" under
625 # any OS. So change all non-escaped dots in the RE to match any
626 # character except the special characters.
627 # XXX currently the "special characters" are just slash -- i.e. this is
628 # Unix-only.
629 pattern_re = re.sub (r'(^|[^\\])\.', r'\1[^/]', pattern_re)
630 return pattern_re
631
632# glob_to_re ()
633
634
635def translate_pattern (pattern, anchor=1, prefix=None):
636 """Translate a shell-like wildcard pattern to a compiled regular
637 expression. Return the compiled regex."""
638
639 if pattern:
640 pattern_re = glob_to_re (pattern)
641 else:
642 pattern_re = ''
643
644 if prefix is not None:
645 prefix_re = (glob_to_re (prefix))[0:-1] # ditch trailing $
646 pattern_re = "^" + os.path.join (prefix_re, ".*" + pattern_re)
647 else: # no prefix -- respect anchor flag
648 if anchor:
649 pattern_re = "^" + pattern_re
650
651 return re.compile (pattern_re)
652
653# translate_pattern ()