blob: 221a4d99341ec30cf176d33854a27b16f1ff78f4 [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 Ward9d17a7a2000-06-07 03:00:06 +000016from distutils.archive_util import check_archive_formats,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 Ward9d17a7a2000-06-07 03:00:06 +000038 "formats for source distribution"),
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 Ward9d17a7a2000-06-07 03:00:06 +000043 # prints all possible arguments to --formats
44 def show_formats():
45 from distutils.fancy_getopt import FancyGetopt
46 list_of_formats=[]
47 for format in ARCHIVE_FORMATS.keys():
48 list_of_formats.append(("formats="+format,None,ARCHIVE_FORMATS[format][2]))
49 list_of_formats.sort()
50 pretty_printer=FancyGetopt(list_of_formats)
51 pretty_printer.print_help("List of available distribution formats:")
52
53 help_options = [
54 ('help-formats', None,
55 "lists available distribution formats",show_formats),
56 ]
57
Greg Warda82122b2000-02-17 23:56:15 +000058 negative_opts = {'use-defaults': 'no-defaults'}
59
60 default_format = { 'posix': 'gztar',
61 'nt': 'zip' }
62
63 exclude_re = re.compile (r'\s*!\s*(\S+)') # for manifest lines
64
65
Greg Warde01149c2000-02-18 00:35:22 +000066 def initialize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000067 # 'template' and 'manifest' are, respectively, the names of
68 # the manifest template and manifest file.
69 self.template = None
70 self.manifest = None
71
72 # 'use_defaults': if true, we will include the default file set
73 # in the manifest
74 self.use_defaults = 1
75
76 self.manifest_only = 0
77 self.force_manifest = 0
78
79 self.formats = None
Greg Warda82122b2000-02-17 23:56:15 +000080 self.keep_tree = 0
81
Greg Wardd87eb732000-06-01 01:10:56 +000082 self.archive_files = None
83
Greg Warda82122b2000-02-17 23:56:15 +000084
Greg Warde01149c2000-02-18 00:35:22 +000085 def finalize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000086 if self.manifest is None:
87 self.manifest = "MANIFEST"
88 if self.template is None:
89 self.template = "MANIFEST.in"
90
Greg Ward62d5a572000-06-04 15:12:51 +000091 self.ensure_string_list('formats')
Greg Warda82122b2000-02-17 23:56:15 +000092 if self.formats is None:
93 try:
94 self.formats = [self.default_format[os.name]]
95 except KeyError:
96 raise DistutilsPlatformError, \
Greg Ward578c10d2000-03-31 02:50:04 +000097 "don't know how to create source distributions " + \
98 "on platform %s" % os.name
Greg Warda82122b2000-02-17 23:56:15 +000099
Greg Ward6a9a5452000-04-22 03:11:55 +0000100 bad_format = check_archive_formats (self.formats)
101 if bad_format:
102 raise DistutilsOptionError, \
103 "unknown archive format '%s'" % bad_format
104
Greg Warda82122b2000-02-17 23:56:15 +0000105
106 def run (self):
107
108 # 'files' is the list of files that will make up the manifest
109 self.files = []
110
111 # Ensure that all required meta-data is given; warn if not (but
112 # don't die, it's not *that* serious!)
113 self.check_metadata ()
114
115 # Do whatever it takes to get the list of files to process
116 # (process the manifest template, read an existing manifest,
117 # whatever). File list is put into 'self.files'.
118 self.get_file_list ()
119
120 # If user just wanted us to regenerate the manifest, stop now.
121 if self.manifest_only:
122 return
123
124 # Otherwise, go ahead and create the source distribution tarball,
125 # or zipfile, or whatever.
126 self.make_distribution ()
127
128
129 def check_metadata (self):
130
Greg Ward535f2d92000-04-21 04:37:12 +0000131 metadata = self.distribution.metadata
Greg Warda82122b2000-02-17 23:56:15 +0000132
133 missing = []
134 for attr in ('name', 'version', 'url'):
Greg Ward535f2d92000-04-21 04:37:12 +0000135 if not (hasattr (metadata, attr) and getattr (metadata, attr)):
Greg Warda82122b2000-02-17 23:56:15 +0000136 missing.append (attr)
137
138 if missing:
139 self.warn ("missing required meta-data: " +
140 string.join (missing, ", "))
141
Greg Ward535f2d92000-04-21 04:37:12 +0000142 if metadata.author:
143 if not metadata.author_email:
Greg Warda82122b2000-02-17 23:56:15 +0000144 self.warn ("missing meta-data: if 'author' supplied, " +
145 "'author_email' must be supplied too")
Greg Ward535f2d92000-04-21 04:37:12 +0000146 elif metadata.maintainer:
147 if not metadata.maintainer_email:
Greg Warda82122b2000-02-17 23:56:15 +0000148 self.warn ("missing meta-data: if 'maintainer' supplied, " +
149 "'maintainer_email' must be supplied too")
150 else:
151 self.warn ("missing meta-data: either (author and author_email) " +
152 "or (maintainer and maintainer_email) " +
153 "must be supplied")
154
155 # check_metadata ()
156
157
158 def get_file_list (self):
159 """Figure out the list of files to include in the source
160 distribution, and put it in 'self.files'. This might
161 involve reading the manifest template (and writing the
162 manifest), or just reading the manifest, or just using
163 the default file set -- it all depends on the user's
164 options and the state of the filesystem."""
165
166
167 template_exists = os.path.isfile (self.template)
168 if template_exists:
169 template_newer = newer (self.template, self.manifest)
170
171 # Regenerate the manifest if necessary (or if explicitly told to)
172 if ((template_exists and template_newer) or
173 self.force_manifest or
174 self.manifest_only):
175
176 if not template_exists:
177 self.warn (("manifest template '%s' does not exist " +
178 "(using default file list)") %
179 self.template)
180
181 # Add default file set to 'files'
182 if self.use_defaults:
183 self.find_defaults ()
184
185 # Read manifest template if it exists
186 if template_exists:
187 self.read_template ()
188
189 # File list now complete -- sort it so that higher-level files
190 # come first
191 sortable_files = map (os.path.split, self.files)
192 sortable_files.sort ()
193 self.files = []
194 for sort_tuple in sortable_files:
195 self.files.append (apply (os.path.join, sort_tuple))
196
197 # Remove duplicates from the file list
198 for i in range (len(self.files)-1, 0, -1):
199 if self.files[i] == self.files[i-1]:
200 del self.files[i]
201
202 # And write complete file list (including default file set) to
203 # the manifest.
204 self.write_manifest ()
205
206 # Don't regenerate the manifest, just read it in.
207 else:
208 self.read_manifest ()
209
210 # get_file_list ()
211
212
213 def find_defaults (self):
214
215 standards = [('README', 'README.txt'), 'setup.py']
216 for fn in standards:
217 if type (fn) is TupleType:
218 alts = fn
Greg Ward48401122000-02-24 03:17:43 +0000219 got_it = 0
Greg Warda82122b2000-02-17 23:56:15 +0000220 for fn in alts:
221 if os.path.exists (fn):
222 got_it = 1
223 self.files.append (fn)
224 break
225
226 if not got_it:
227 self.warn ("standard file not found: should have one of " +
228 string.join (alts, ', '))
229 else:
230 if os.path.exists (fn):
231 self.files.append (fn)
232 else:
233 self.warn ("standard file '%s' not found" % fn)
234
235 optional = ['test/test*.py']
236 for pattern in optional:
237 files = filter (os.path.isfile, glob (pattern))
238 if files:
239 self.files.extend (files)
240
Greg Ward578c10d2000-03-31 02:50:04 +0000241 if self.distribution.has_pure_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000242 build_py = self.get_finalized_command ('build_py')
Greg Warda82122b2000-02-17 23:56:15 +0000243 self.files.extend (build_py.get_source_files ())
244
Greg Ward578c10d2000-03-31 02:50:04 +0000245 if self.distribution.has_ext_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000246 build_ext = self.get_finalized_command ('build_ext')
Greg Warda82122b2000-02-17 23:56:15 +0000247 self.files.extend (build_ext.get_source_files ())
248
Greg Ward60908f12000-04-09 03:51:40 +0000249 if self.distribution.has_c_libraries():
Greg Ward4fb29e52000-05-27 17:27:23 +0000250 build_clib = self.get_finalized_command ('build_clib')
Greg Ward60908f12000-04-09 03:51:40 +0000251 self.files.extend (build_clib.get_source_files ())
252
Greg Warda82122b2000-02-17 23:56:15 +0000253
Greg Warda82122b2000-02-17 23:56:15 +0000254 def search_dir (self, dir, pattern=None):
255 """Recursively find files under 'dir' matching 'pattern' (a string
256 containing a Unix-style glob pattern). If 'pattern' is None,
257 find all files under 'dir'. Return the list of found
258 filenames."""
259
260 allfiles = findall (dir)
261 if pattern is None:
262 return allfiles
263
264 pattern_re = translate_pattern (pattern)
265 files = []
266 for file in allfiles:
267 if pattern_re.match (os.path.basename (file)):
268 files.append (file)
269
270 return files
271
272 # search_dir ()
273
274
275 def exclude_pattern (self, pattern):
276 """Remove filenames from 'self.files' that match 'pattern'."""
277 print "exclude_pattern: pattern=%s" % pattern
278 pattern_re = translate_pattern (pattern)
279 for i in range (len (self.files)-1, -1, -1):
280 if pattern_re.match (self.files[i]):
281 print "removing %s" % self.files[i]
282 del self.files[i]
283
284
285 def recursive_exclude_pattern (self, dir, pattern=None):
286 """Remove filenames from 'self.files' that are under 'dir'
287 and whose basenames match 'pattern'."""
288
289 print "recursive_exclude_pattern: dir=%s, pattern=%s" % (dir, pattern)
290 if pattern is None:
291 pattern_re = None
292 else:
293 pattern_re = translate_pattern (pattern)
294
295 for i in range (len (self.files)-1, -1, -1):
296 (cur_dir, cur_base) = os.path.split (self.files[i])
297 if (cur_dir == dir and
298 (pattern_re is None or pattern_re.match (cur_base))):
299 print "removing %s" % self.files[i]
300 del self.files[i]
301
302
303 def read_template (self):
304 """Read and parse the manifest template file named by
305 'self.template' (usually "MANIFEST.in"). Process all file
306 specifications (include and exclude) in the manifest template
307 and add the resulting filenames to 'self.files'."""
308
309 assert self.files is not None and type (self.files) is ListType
310
311 template = TextFile (self.template,
312 strip_comments=1,
313 skip_blanks=1,
314 join_lines=1,
315 lstrip_ws=1,
316 rstrip_ws=1,
317 collapse_ws=1)
318
319 all_files = findall ()
320
321 while 1:
322
323 line = template.readline()
324 if line is None: # end of file
325 break
326
327 words = string.split (line)
328 action = words[0]
329
330 # First, check that the right number of words are present
331 # for the given action (which is the first word)
332 if action in ('include','exclude',
333 'global-include','global-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000334 if len (words) < 2:
Greg Warda82122b2000-02-17 23:56:15 +0000335 template.warn \
336 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000337 "'%s' expects <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000338 action)
339 continue
340
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000341 pattern_list = map(convert_path, words[1:])
Greg Warda82122b2000-02-17 23:56:15 +0000342
343 elif action in ('recursive-include','recursive-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000344 if len (words) < 3:
Greg Warda82122b2000-02-17 23:56:15 +0000345 template.warn \
346 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000347 "'%s' expects <dir> <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000348 action)
349 continue
350
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000351 dir = convert_path(words[1])
352 pattern_list = map (convert_path, words[2:])
Greg Warda82122b2000-02-17 23:56:15 +0000353
354 elif action in ('graft','prune'):
355 if len (words) != 2:
356 template.warn \
357 ("invalid manifest template line: " +
358 "'%s' expects a single <dir_pattern>" %
359 action)
360 continue
361
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000362 dir_pattern = convert_path (words[1])
Greg Warda82122b2000-02-17 23:56:15 +0000363
364 else:
365 template.warn ("invalid manifest template line: " +
366 "unknown action '%s'" % action)
367 continue
368
369 # OK, now we know that the action is valid and we have the
370 # right number of words on the line for that action -- so we
371 # can proceed with minimal error-checking. Also, we have
Greg Ward2b9e43f2000-04-14 00:49:30 +0000372 # defined either (pattern), (dir and pattern), or
373 # (dir_pattern) -- so we don't have to spend any time
374 # digging stuff up out of 'words'.
Greg Warda82122b2000-02-17 23:56:15 +0000375
376 if action == 'include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000377 print "include", string.join(pattern_list)
378 for pattern in pattern_list:
379 files = select_pattern (all_files, pattern, anchor=1)
380 if not files:
381 template.warn ("no files found matching '%s'" % pattern)
382 else:
383 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000384
385 elif action == 'exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000386 print "exclude", string.join(pattern_list)
387 for pattern in pattern_list:
388 num = exclude_pattern (self.files, pattern, anchor=1)
389 if num == 0:
390 template.warn (
391 "no previously-included files found matching '%s'"%
392 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000393
394 elif action == 'global-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000395 print "global-include", string.join(pattern_list)
396 for pattern in pattern_list:
397 files = select_pattern (all_files, pattern, anchor=0)
398 if not files:
399 template.warn (("no files found matching '%s' " +
400 "anywhere in distribution") %
401 pattern)
402 else:
403 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000404
405 elif action == 'global-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000406 print "global-exclude", string.join(pattern_list)
407 for pattern in pattern_list:
408 num = exclude_pattern (self.files, pattern, anchor=0)
409 if num == 0:
410 template.warn \
411 (("no previously-included files matching '%s' " +
412 "found anywhere in distribution") %
413 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000414
415 elif action == 'recursive-include':
Greg Ward9d5afa92000-04-21 04:31:10 +0000416 print "recursive-include", dir, string.join(pattern_list)
417 for pattern in pattern_list:
418 files = select_pattern (all_files, pattern, prefix=dir)
419 if not files:
420 template.warn (("no files found matching '%s' " +
421 "under directory '%s'") %
422 (pattern, dir))
423 else:
424 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000425
426 elif action == 'recursive-exclude':
Greg Ward9d5afa92000-04-21 04:31:10 +0000427 print "recursive-exclude", dir, string.join(pattern_list)
428 for pattern in pattern_list:
429 num = exclude_pattern (self.files, pattern, prefix=dir)
430 if num == 0:
431 template.warn \
432 (("no previously-included files matching '%s' " +
433 "found under directory '%s'") %
434 (pattern, dir))
Greg Warda82122b2000-02-17 23:56:15 +0000435
436 elif action == 'graft':
437 print "graft", dir_pattern
438 files = select_pattern (all_files, None, prefix=dir_pattern)
439 if not files:
440 template.warn ("no directories found matching '%s'" %
441 dir_pattern)
442 else:
443 self.files.extend (files)
444
445 elif action == 'prune':
446 print "prune", dir_pattern
447 num = exclude_pattern (self.files, None, prefix=dir_pattern)
448 if num == 0:
449 template.warn \
450 (("no previously-included directories found " +
451 "matching '%s'") %
452 dir_pattern)
453 else:
454 raise RuntimeError, \
455 "this cannot happen: invalid action '%s'" % action
456
457 # while loop over lines of template file
458
Greg Warde5b267c2000-05-27 03:03:23 +0000459 # Prune away the build and source distribution directories
Greg Ward4fb29e52000-05-27 17:27:23 +0000460 build = self.get_finalized_command ('build')
Greg Warde5b267c2000-05-27 03:03:23 +0000461 exclude_pattern (self.files, None, prefix=build.build_base)
462
463 base_dir = self.distribution.get_fullname()
464 exclude_pattern (self.files, None, prefix=base_dir)
465
Greg Warda82122b2000-02-17 23:56:15 +0000466 # read_template ()
467
468
469 def write_manifest (self):
470 """Write the file list in 'self.files' (presumably as filled in
471 by 'find_defaults()' and 'read_template()') to the manifest file
472 named by 'self.manifest'."""
473
Greg Ward1b8e1d42000-04-26 01:12:40 +0000474 self.execute(write_file,
475 (self.manifest, self.files),
476 "writing manifest file")
Greg Warda82122b2000-02-17 23:56:15 +0000477
478 # write_manifest ()
479
480
481 def read_manifest (self):
482 """Read the manifest file (named by 'self.manifest') and use
483 it to fill in 'self.files', the list of files to include
484 in the source distribution."""
485
486 manifest = open (self.manifest)
487 while 1:
488 line = manifest.readline ()
489 if line == '': # end of file
490 break
491 if line[-1] == '\n':
492 line = line[0:-1]
493 self.files.append (line)
494
495 # read_manifest ()
496
497
498
499 def make_release_tree (self, base_dir, files):
500
Greg Ward578c10d2000-03-31 02:50:04 +0000501 # Create all the directories under 'base_dir' necessary to
502 # put 'files' there.
503 create_tree (base_dir, files,
504 verbose=self.verbose, dry_run=self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000505
506 # And walk over the list of files, either making a hard link (if
507 # os.link exists) to each one that doesn't already exist in its
508 # corresponding location under 'base_dir', or copying each file
509 # that's out-of-date in 'base_dir'. (Usually, all files will be
510 # out-of-date, because by default we blow away 'base_dir' when
511 # we're done making the distribution archives.)
512
Greg Ward578c10d2000-03-31 02:50:04 +0000513 if hasattr (os, 'link'): # can make hard links on this system
514 link = 'hard'
Greg Warda82122b2000-02-17 23:56:15 +0000515 msg = "making hard links in %s..." % base_dir
Greg Ward578c10d2000-03-31 02:50:04 +0000516 else: # nope, have to copy
517 link = None
Greg Warda82122b2000-02-17 23:56:15 +0000518 msg = "copying files to %s..." % base_dir
519
520 self.announce (msg)
521 for file in files:
522 dest = os.path.join (base_dir, file)
Greg Ward578c10d2000-03-31 02:50:04 +0000523 self.copy_file (file, dest, link=link)
Greg Warda82122b2000-02-17 23:56:15 +0000524
525 # make_release_tree ()
526
527
Greg Warda82122b2000-02-17 23:56:15 +0000528 def make_distribution (self):
529
Greg Ward578c10d2000-03-31 02:50:04 +0000530 # Don't warn about missing meta-data here -- should be (and is!)
531 # done elsewhere.
Greg Ward0ae7f762000-04-22 02:51:25 +0000532 base_dir = self.distribution.get_fullname()
Greg Warda82122b2000-02-17 23:56:15 +0000533
534 # Remove any files that match "base_dir" from the fileset -- we
535 # don't want to go distributing the distribution inside itself!
536 self.exclude_pattern (base_dir + "*")
537
538 self.make_release_tree (base_dir, self.files)
Greg Wardd87eb732000-06-01 01:10:56 +0000539 archive_files = [] # remember names of files we create
Greg Warda82122b2000-02-17 23:56:15 +0000540 for fmt in self.formats:
Greg Wardd87eb732000-06-01 01:10:56 +0000541 file = self.make_archive (base_dir, fmt, base_dir=base_dir)
542 archive_files.append(file)
543
544 self.archive_files = archive_files
Greg Warda82122b2000-02-17 23:56:15 +0000545
546 if not self.keep_tree:
Greg Ward2dc139c2000-03-18 15:43:42 +0000547 remove_tree (base_dir, self.verbose, self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000548
Greg Wardd87eb732000-06-01 01:10:56 +0000549 def get_archive_files (self):
550 """Return the list of archive files created when the command
551 was run, or None if the command hasn't run yet.
552 """
553 return self.archive_files
554
Greg Wardfcd974e2000-05-25 01:10:04 +0000555# class sdist
Greg Warda82122b2000-02-17 23:56:15 +0000556
557
558# ----------------------------------------------------------------------
559# Utility functions
560
561def findall (dir = os.curdir):
562 """Find all files under 'dir' and return the list of full
563 filenames (relative to 'dir')."""
564
565 list = []
566 stack = [dir]
567 pop = stack.pop
568 push = stack.append
569
570 while stack:
571 dir = pop()
572 names = os.listdir (dir)
573
574 for name in names:
575 if dir != os.curdir: # avoid the dreaded "./" syndrome
576 fullname = os.path.join (dir, name)
577 else:
578 fullname = name
579 list.append (fullname)
580 if os.path.isdir (fullname) and not os.path.islink(fullname):
581 push (fullname)
582
583 return list
584
585
586def select_pattern (files, pattern, anchor=1, prefix=None):
587 """Select strings (presumably filenames) from 'files' that match
588 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not
589 quite the same as implemented by the 'fnmatch' module: '*' and '?'
590 match non-special characters, where "special" is platform-dependent:
591 slash on Unix, colon, slash, and backslash on DOS/Windows, and colon
592 on Mac OS.
593
594 If 'anchor' is true (the default), then the pattern match is more
595 stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
596 'anchor' is false, both of these will match.
597
598 If 'prefix' is supplied, then only filenames starting with 'prefix'
599 (itself a pattern) and ending with 'pattern', with anything in
600 between them, will match. 'anchor' is ignored in this case.
601
602 Return the list of matching strings, possibly empty."""
603
604 matches = []
605 pattern_re = translate_pattern (pattern, anchor, prefix)
606 print "select_pattern: applying re %s" % pattern_re.pattern
607 for name in files:
608 if pattern_re.search (name):
609 matches.append (name)
610 print " adding", name
611
612 return matches
613
614# select_pattern ()
615
616
617def exclude_pattern (files, pattern, anchor=1, prefix=None):
618
619 pattern_re = translate_pattern (pattern, anchor, prefix)
620 print "exclude_pattern: applying re %s" % pattern_re.pattern
621 for i in range (len(files)-1, -1, -1):
622 if pattern_re.search (files[i]):
623 print " removing", files[i]
624 del files[i]
625
626# exclude_pattern ()
627
628
629def glob_to_re (pattern):
630 """Translate a shell-like glob pattern to a regular expression;
631 return a string containing the regex. Differs from
632 'fnmatch.translate()' in that '*' does not match "special
633 characters" (which are platform-specific)."""
634 pattern_re = fnmatch.translate (pattern)
635
636 # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
637 # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
638 # and by extension they shouldn't match such "special characters" under
639 # any OS. So change all non-escaped dots in the RE to match any
640 # character except the special characters.
641 # XXX currently the "special characters" are just slash -- i.e. this is
642 # Unix-only.
643 pattern_re = re.sub (r'(^|[^\\])\.', r'\1[^/]', pattern_re)
644 return pattern_re
645
646# glob_to_re ()
647
648
649def translate_pattern (pattern, anchor=1, prefix=None):
650 """Translate a shell-like wildcard pattern to a compiled regular
651 expression. Return the compiled regex."""
652
653 if pattern:
654 pattern_re = glob_to_re (pattern)
655 else:
656 pattern_re = ''
657
658 if prefix is not None:
659 prefix_re = (glob_to_re (prefix))[0:-1] # ditch trailing $
660 pattern_re = "^" + os.path.join (prefix_re, ".*" + pattern_re)
661 else: # no prefix -- respect anchor flag
662 if anchor:
663 pattern_re = "^" + pattern_re
664
665 return re.compile (pattern_re)
666
667# translate_pattern ()