2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
17 def IncludeFiles(filters
, files
):
18 """Filter files based on inclusion lists
20 Return a list of files which match and of the Unix shell-style wildcards
21 provided, or return all the files if no filter is provided."""
25 for filter in filters
:
26 match |
= set(fnmatch
.filter(files
, filter))
27 return [name
for name
in files
if name
in match
]
30 def ExcludeFiles(filters
, files
):
31 """Filter files based on exclusions lists
33 Return a list of files which do not match any of the Unix shell-style
34 wildcards provided, or return all the files if no filter is provided."""
38 for filter in filters
:
39 excludes
= set(fnmatch
.filter(files
, filter))
41 return [name
for name
in files
if name
not in match
]
44 def CopyPath(options
, src
, dst
):
45 """CopyPath from src to dst
47 Copy a fully specified src to a fully specified dst. If src and dst are
48 both files, the dst file is removed first to prevent error. If and include
49 or exclude list are provided, the destination is first matched against that
52 if not IncludeFiles(options
.includes
, [src
]):
56 if not ExcludeFiles(options
.excludes
, [src
]):
60 print 'cp %s %s' % (src
, dst
)
62 # If the source is a single file, copy it individually
63 if os
.path
.isfile(src
):
64 # We can not copy over a directory with a file.
65 if os
.path
.exists(dst
):
66 if not os
.path
.isfile(dst
):
67 msg
= "cp: cannot overwrite non-file '%s' with file." % dst
69 # If the destination exists as a file, remove it before copying to avoid
73 # Now copy to the non-existent fully qualified target
77 # Otherwise it's a directory, ignore it unless allowed
78 if os
.path
.isdir(src
):
79 if not options
.recursive
:
80 print "cp: omitting directory '%s'" % src
83 # We can not copy over a file with a directory.
84 if os
.path
.exists(dst
):
85 if not os
.path
.isdir(dst
):
86 msg
= "cp: cannot overwrite non-directory '%s' with directory." % dst
89 # if it didn't exist, create the directory
92 # Now copy all members
93 for filename
in os
.listdir(src
):
94 srcfile
= os
.path
.join(src
, filename
)
95 dstfile
= os
.path
.join(dst
, filename
)
96 CopyPath(options
, srcfile
, dstfile
)
101 """A Unix cp style copy.
103 Copies multiple sources to a single destination using the normal cp
104 semantics. In addition, it support inclusion and exclusion filters which
105 allows the copy to skip certain types of files."""
106 parser
= optparse
.OptionParser(usage
='usage: cp [Options] souces... dest')
108 '-R', '-r', '--recursive', dest
='recursive', action
='store_true',
110 help='copy directories recursively.')
112 '-v', '--verbose', dest
='verbose', action
='store_true',
114 help='verbose output.')
116 '--include', dest
='includes', action
='append', default
=[],
117 help='include files matching this expression.')
119 '--exclude', dest
='excludes', action
='append', default
=[],
120 help='exclude files matching this expression.')
121 options
, files
= parser
.parse_args(args
)
123 parser
.error('ERROR: expecting SOURCE(s) and DEST.')
130 files
= glob
.glob(src
)
132 raise OSError('cp: no such file or directory: ' + src
)
134 src_list
.extend(files
)
137 # If the destination is a directory, then append the basename of the src
138 # to the destination.
139 if os
.path
.isdir(dst
):
140 CopyPath(options
, src
, os
.path
.join(dst
, os
.path
.basename(src
)))
142 CopyPath(options
, src
, dst
)
146 """A Unix style mkdir"""
147 parser
= optparse
.OptionParser(usage
='usage: mkdir [Options] DIRECTORY...')
149 '-p', '--parents', dest
='parents', action
='store_true',
151 help='ignore existing parents, create parents as needed.')
153 '-v', '--verbose', dest
='verbose', action
='store_true',
155 help='verbose output.')
157 options
, dsts
= parser
.parse_args(args
)
159 parser
.error('ERROR: expecting DIRECTORY...')
166 except OSError as error
:
167 if os
.path
.isdir(dst
):
170 raise OSError('mkdir: Already exsists: ' + dst
)
172 raise OSError('mkdir: Failed to create: ' + dst
)
176 def MovePath(options
, src
, dst
):
177 """MovePath from src to dst
179 Moves the src to the dst much like the Unix style mv command, except it
180 only handles one source at a time. Because of possible temporary failures
181 do to locks (such as anti-virus software on Windows), the function will retry
183 # if the destination is not an existing directory, then overwrite it
184 if os
.path
.isdir(dst
):
185 dst
= os
.path
.join(dst
, os
.path
.basename(src
))
187 # If the destination exists, the remove it
188 if os
.path
.exists(dst
):
190 Remove(['-vfr', dst
])
191 if os
.path
.exists(dst
):
192 raise OSError('mv: FAILED TO REMOVE ' + dst
)
194 raise OSError('mv: already exists ' + dst
)
199 except OSError as error
:
200 print 'Failed on %s with %s, retrying' % (src
, error
)
203 raise OSError('mv: ' + error
)
207 parser
= optparse
.OptionParser(usage
='usage: mv [Options] souces... dest')
209 '-v', '--verbose', dest
='verbose', action
='store_true',
211 help='verbose output.')
213 '-f', '--force', dest
='force', action
='store_true',
215 help='force, do not error it files already exist.')
216 options
, files
= parser
.parse_args(args
)
218 parser
.error('ERROR: expecting SOURCE... and DEST.')
220 print 'mv %s %s' % (src
, dst
)
226 MovePath(options
, src
, dst
)
233 Removes the list of paths. Because of possible temporary failures do to locks
234 (such as anti-virus software on Windows), the function will retry up to five
236 parser
= optparse
.OptionParser(usage
='usage: rm [Options] PATHS...')
238 '-R', '-r', '--recursive', dest
='recursive', action
='store_true',
240 help='remove directories recursively.')
242 '-v', '--verbose', dest
='verbose', action
='store_true',
244 help='verbose output.')
246 '-f', '--force', dest
='force', action
='store_true',
248 help='force, do not error it files does not exist.')
249 options
, files
= parser
.parse_args(args
)
251 parser
.error('ERROR: expecting FILE...')
254 for pattern
in files
:
255 dst_files
= glob
.glob(pattern
)
256 # Ignore non existing files when using force
257 if len(dst_files
) == 0 and options
.force
:
258 print "rm: Skipping " + pattern
260 elif len(dst_files
) == 0:
261 raise OSError('rm: no such file or directory: ' + pattern
)
263 for dst
in dst_files
:
267 if os
.path
.isfile(dst
) or os
.path
.islink(dst
):
270 # Check every time, since it may have been deleted after the
271 # previous failed attempt.
272 if os
.path
.isfile(dst
) or os
.path
.islink(dst
):
275 except OSError as error
:
278 raise OSError('rm: ' + str(error
))
279 print 'Failed remove with %s, retrying' % error
282 if options
.recursive
:
285 if os
.path
.isdir(dst
):
288 except OSError as error
:
291 raise OSError('rm: ' + str(error
))
292 print 'Failed rmtree with %s, retrying' % error
295 except OSError as error
:
300 def MakeZipPath(os_path
, isdir
, iswindows
):
301 """Changes a path into zipfile format.
303 # doctest doesn't seem to honor r'' strings, so the backslashes need to be
305 >>> MakeZipPath(r'C:\\users\\foobar\\blah', False, True)
307 >>> MakeZipPath('/tmp/tmpfoobar/something', False, False)
308 'tmp/tmpfoobar/something'
309 >>> MakeZipPath('./somefile.txt', False, False)
311 >>> MakeZipPath('somedir', True, False)
313 >>> MakeZipPath('../dir/filename.txt', False, False)
314 '../dir/filename.txt'
315 >>> MakeZipPath('dir/../filename.txt', False, False)
321 # zipfile paths are always posix-style. They also have the drive
322 # letter and leading slashes removed.
323 zip_path
= ntpath
.splitdrive(os_path
)[1].replace('\\', '/')
324 if zip_path
.startswith('/'):
325 zip_path
= zip_path
[1:]
326 zip_path
= posixpath
.normpath(zip_path
)
327 # zipfile also always appends a slash to a directory name.
333 def OSMakeZipPath(os_path
):
334 return MakeZipPath(os_path
, os
.path
.isdir(os_path
), sys
.platform
== 'win32')
340 Compresses the listed files."""
341 parser
= optparse
.OptionParser(usage
='usage: zip [Options] zipfile list')
343 '-r', dest
='recursive', action
='store_true',
345 help='recurse into directories')
347 '-q', dest
='quiet', action
='store_true',
349 help='quiet operation')
350 options
, files
= parser
.parse_args(args
)
352 parser
.error('ERROR: expecting ZIPFILE and LIST.')
358 for src_arg
in src_args
:
359 globbed_src_args
= glob
.glob(src_arg
)
360 if len(globbed_src_args
) == 0:
361 if not options
.quiet
:
362 print 'zip warning: name not matched: %s' % (src_arg
,)
364 for src_file
in globbed_src_args
:
365 src_file
= os
.path
.normpath(src_file
)
366 src_files
.append(src_file
)
367 if options
.recursive
and os
.path
.isdir(src_file
):
368 for root
, dirs
, files
in os
.walk(src_file
):
370 src_files
.append(os
.path
.join(root
, dir))
372 src_files
.append(os
.path
.join(root
, file))
375 # zip_data represents a list of the data to be written or appended to the
376 # zip_stream. It is a list of tuples:
377 # (OS file path, zip path/zip file info, and file data)
378 # In all cases one of the |os path| or the |file data| will be None.
379 # |os path| is None when there is no OS file to write to the archive (i.e.
380 # the file data already existed in the archive). |file data| is None when the
381 # file is new (never existed in the archive) or being updated.
383 new_files_to_add
= [OSMakeZipPath(src_file
) for src_file
in src_files
]
384 zip_path_to_os_path_dict
= dict((new_files_to_add
[i
], src_files
[i
])
385 for i
in range(len(src_files
)))
388 zip_stream
= zipfile
.ZipFile(dest_zip
, 'r')
389 files_to_update
= set(new_files_to_add
).intersection(
390 set(zip_stream
.namelist()))
392 # As far as I can tell, there is no way to update a zip entry using
393 # zipfile; the best you can do is rewrite the archive.
394 # Iterate through the zipfile to maintain file order.
396 for zip_path
in zip_stream
.namelist():
397 if zip_path
in files_to_update
:
398 os_path
= zip_path_to_os_path_dict
[zip_path
]
399 zip_data
.append((os_path
, zip_path
, None))
400 new_files_to_add
.remove(zip_path
)
402 file_bytes
= zip_stream
.read(zip_path
)
403 file_info
= zip_stream
.getinfo(zip_path
)
404 zip_data
.append((None, file_info
, file_bytes
))
411 for zip_path
in new_files_to_add
:
412 zip_data
.append((zip_path_to_os_path_dict
[zip_path
], zip_path
, None))
415 print 'zip error: Nothing to do! (%s)' % (dest_zip
,)
419 zip_stream
= zipfile
.ZipFile(dest_zip
, write_mode
, zipfile
.ZIP_DEFLATED
)
420 for os_path
, file_info_or_zip_path
, file_bytes
in zip_data
:
421 if isinstance(file_info_or_zip_path
, zipfile
.ZipInfo
):
422 zip_path
= file_info_or_zip_path
.filename
424 zip_path
= file_info_or_zip_path
427 zip_stream
.write(os_path
, zip_path
)
429 zip_stream
.writestr(file_info_or_zip_path
, file_bytes
)
431 if not options
.quiet
:
432 if zip_path
in new_files_to_add
:
435 operation
= 'updating'
436 zip_info
= zip_stream
.getinfo(zip_path
)
437 if (zip_info
.compress_type
== zipfile
.ZIP_STORED
or
438 zip_info
.file_size
== 0):
439 print ' %s: %s (stored 0%%)' % (operation
, zip_path
)
440 elif zip_info
.compress_type
== zipfile
.ZIP_DEFLATED
:
441 print ' %s: %s (deflated %d%%)' % (operation
, zip_path
,
442 100 - zip_info
.compress_size
* 100 / zip_info
.file_size
)
458 if __name__
== '__main__':
459 func
= FuncMap
.get(sys
.argv
[1])
461 print 'Do not recognize: ' + sys
.argv
[1]
463 sys
.exit(func(sys
.argv
[2:]))