2 # Copyright (c) 2012 The Chromium 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 if sys
.version_info
< (2, 6, 0):
18 sys
.stderr
.write("python 2.6 or later is required run this script\n")
22 def IncludeFiles(filters
, files
):
23 """Filter files based on inclusion lists
25 Return a list of files which match and of the Unix shell-style wildcards
26 provided, or return all the files if no filter is provided."""
30 for file_filter
in filters
:
31 match |
= set(fnmatch
.filter(files
, file_filter
))
32 return [name
for name
in files
if name
in match
]
35 def ExcludeFiles(filters
, files
):
36 """Filter files based on exclusions lists
38 Return a list of files which do not match any of the Unix shell-style
39 wildcards provided, or return all the files if no filter is provided."""
43 for file_filter
in filters
:
44 excludes
= set(fnmatch
.filter(files
, file_filter
))
46 return [name
for name
in files
if name
not in match
]
49 def CopyPath(options
, src
, dst
):
50 """CopyPath from src to dst
52 Copy a fully specified src to a fully specified dst. If src and dst are
53 both files, the dst file is removed first to prevent error. If and include
54 or exclude list are provided, the destination is first matched against that
57 if not IncludeFiles(options
.includes
, [src
]):
61 if not ExcludeFiles(options
.excludes
, [src
]):
65 print 'cp %s %s' % (src
, dst
)
67 # If the source is a single file, copy it individually
68 if os
.path
.isfile(src
):
69 # We can not copy over a directory with a file.
70 if os
.path
.exists(dst
):
71 if not os
.path
.isfile(dst
):
72 msg
= "cp: cannot overwrite non-file '%s' with file." % dst
74 # If the destination exists as a file, remove it before copying to avoid
78 # Now copy to the non-existent fully qualified target
82 # Otherwise it's a directory, ignore it unless allowed
83 if os
.path
.isdir(src
):
84 if not options
.recursive
:
85 print "cp: omitting directory '%s'" % src
88 # We can not copy over a file with a directory.
89 if os
.path
.exists(dst
):
90 if not os
.path
.isdir(dst
):
91 msg
= "cp: cannot overwrite non-directory '%s' with directory." % dst
94 # if it didn't exist, create the directory
97 # Now copy all members
98 for filename
in os
.listdir(src
):
99 srcfile
= os
.path
.join(src
, filename
)
100 dstfile
= os
.path
.join(dst
, filename
)
101 CopyPath(options
, srcfile
, dstfile
)
106 """A Unix cp style copy.
108 Copies multiple sources to a single destination using the normal cp
109 semantics. In addition, it support inclusion and exclusion filters which
110 allows the copy to skip certain types of files."""
111 parser
= optparse
.OptionParser(usage
='usage: cp [Options] sources... dest')
113 '-R', '-r', '--recursive', dest
='recursive', action
='store_true',
115 help='copy directories recursively.')
117 '-v', '--verbose', dest
='verbose', action
='store_true',
119 help='verbose output.')
121 '--include', dest
='includes', action
='append', default
=[],
122 help='include files matching this expression.')
124 '--exclude', dest
='excludes', action
='append', default
=[],
125 help='exclude files matching this expression.')
126 options
, files
= parser
.parse_args(args
)
128 parser
.error('ERROR: expecting SOURCE(s) and DEST.')
135 files
= glob
.glob(src
)
137 raise OSError('cp: no such file or directory: ' + src
)
139 src_list
.extend(files
)
142 # If the destination is a directory, then append the basename of the src
143 # to the destination.
144 if os
.path
.isdir(dst
):
145 CopyPath(options
, src
, os
.path
.join(dst
, os
.path
.basename(src
)))
147 CopyPath(options
, src
, dst
)
151 """A Unix style mkdir"""
152 parser
= optparse
.OptionParser(usage
='usage: mkdir [Options] DIRECTORY...')
154 '-p', '--parents', dest
='parents', action
='store_true',
156 help='ignore existing parents, create parents as needed.')
158 '-v', '--verbose', dest
='verbose', action
='store_true',
160 help='verbose output.')
162 options
, dsts
= parser
.parse_args(args
)
164 parser
.error('ERROR: expecting DIRECTORY...')
172 if os
.path
.isdir(dst
):
175 raise OSError('mkdir: Already exists: ' + dst
)
177 raise OSError('mkdir: Failed to create: ' + dst
)
181 def MovePath(options
, src
, dst
):
182 """MovePath from src to dst
184 Moves the src to the dst much like the Unix style mv command, except it
185 only handles one source at a time. Because of possible temporary failures
186 do to locks (such as anti-virus software on Windows), the function will retry
188 # if the destination is not an existing directory, then overwrite it
189 if os
.path
.isdir(dst
):
190 dst
= os
.path
.join(dst
, os
.path
.basename(src
))
192 # If the destination exists, the remove it
193 if os
.path
.exists(dst
):
195 Remove(['-vfr', dst
])
196 if os
.path
.exists(dst
):
197 raise OSError('mv: FAILED TO REMOVE ' + dst
)
199 raise OSError('mv: already exists ' + dst
)
204 except OSError as error
:
205 print 'Failed on %s with %s, retrying' % (src
, error
)
208 raise OSError('mv: ' + error
)
212 parser
= optparse
.OptionParser(usage
='usage: mv [Options] sources... dest')
214 '-v', '--verbose', dest
='verbose', action
='store_true',
216 help='verbose output.')
218 '-f', '--force', dest
='force', action
='store_true',
220 help='force, do not error it files already exist.')
221 options
, files
= parser
.parse_args(args
)
223 parser
.error('ERROR: expecting SOURCE... and DEST.')
229 print 'mv %s %s' % (' '.join(srcs
), dst
)
232 MovePath(options
, src
, dst
)
239 Removes the list of paths. Because of possible temporary failures do to locks
240 (such as anti-virus software on Windows), the function will retry up to five
242 parser
= optparse
.OptionParser(usage
='usage: rm [Options] PATHS...')
244 '-R', '-r', '--recursive', dest
='recursive', action
='store_true',
246 help='remove directories recursively.')
248 '-v', '--verbose', dest
='verbose', action
='store_true',
250 help='verbose output.')
252 '-f', '--force', dest
='force', action
='store_true',
254 help='force, do not error it files does not exist.')
255 options
, files
= parser
.parse_args(args
)
257 parser
.error('ERROR: expecting FILE...')
260 for pattern
in files
:
261 dst_files
= glob
.glob(pattern
)
263 # Ignore non existing files when using force
266 raise OSError('rm: no such file or directory: ' + pattern
)
268 for dst
in dst_files
:
272 if os
.path
.isfile(dst
) or os
.path
.islink(dst
):
275 # Check every time, since it may have been deleted after the
276 # previous failed attempt.
277 if os
.path
.isfile(dst
) or os
.path
.islink(dst
):
280 except OSError as error
:
283 raise OSError('rm: ' + str(error
))
284 print 'Failed remove with %s, retrying' % error
287 if options
.recursive
:
290 if os
.path
.isdir(dst
):
293 except OSError as error
:
296 raise OSError('rm: ' + str(error
))
297 print 'Failed rmtree with %s, retrying' % error
300 except OSError as error
:
305 def MakeZipPath(os_path
, isdir
, iswindows
):
306 """Changes a path into zipfile format.
308 # doctest doesn't seem to honor r'' strings, so the backslashes need to be
310 >>> MakeZipPath(r'C:\\users\\foobar\\blah', False, True)
312 >>> MakeZipPath('/tmp/tmpfoobar/something', False, False)
313 'tmp/tmpfoobar/something'
314 >>> MakeZipPath('./somefile.txt', False, False)
316 >>> MakeZipPath('somedir', True, False)
318 >>> MakeZipPath('../dir/filename.txt', False, False)
319 '../dir/filename.txt'
320 >>> MakeZipPath('dir/../filename.txt', False, False)
326 # zipfile paths are always posix-style. They also have the drive
327 # letter and leading slashes removed.
328 zip_path
= ntpath
.splitdrive(os_path
)[1].replace('\\', '/')
329 if zip_path
.startswith('/'):
330 zip_path
= zip_path
[1:]
331 zip_path
= posixpath
.normpath(zip_path
)
332 # zipfile also always appends a slash to a directory name.
338 def OSMakeZipPath(os_path
):
339 return MakeZipPath(os_path
, os
.path
.isdir(os_path
), sys
.platform
== 'win32')
345 Compresses the listed files."""
346 parser
= optparse
.OptionParser(usage
='usage: zip [Options] zipfile list')
348 '-r', dest
='recursive', action
='store_true',
350 help='recurse into directories')
352 '-q', dest
='quiet', action
='store_true',
354 help='quiet operation')
355 options
, files
= parser
.parse_args(args
)
357 parser
.error('ERROR: expecting ZIPFILE and LIST.')
363 for src_arg
in src_args
:
364 globbed_src_args
= glob
.glob(src_arg
)
365 if not globbed_src_args
:
366 if not options
.quiet
:
367 print 'zip warning: name not matched: %s' % (src_arg
,)
369 for src_file
in globbed_src_args
:
370 src_file
= os
.path
.normpath(src_file
)
371 src_files
.append(src_file
)
372 if options
.recursive
and os
.path
.isdir(src_file
):
373 for root
, dirs
, files
in os
.walk(src_file
):
375 src_files
.append(os
.path
.join(root
, dirname
))
376 for filename
in files
:
377 src_files
.append(os
.path
.join(root
, filename
))
380 # zip_data represents a list of the data to be written or appended to the
381 # zip_stream. It is a list of tuples:
382 # (OS file path, zip path/zip file info, and file data)
383 # In all cases one of the |os path| or the |file data| will be None.
384 # |os path| is None when there is no OS file to write to the archive (i.e.
385 # the file data already existed in the archive). |file data| is None when the
386 # file is new (never existed in the archive) or being updated.
388 new_files_to_add
= [OSMakeZipPath(src_file
) for src_file
in src_files
]
389 zip_path_to_os_path_dict
= dict((new_files_to_add
[i
], src_files
[i
])
390 for i
in range(len(src_files
)))
393 zip_stream
= zipfile
.ZipFile(dest_zip
, 'r')
394 files_to_update
= set(new_files_to_add
).intersection(
395 set(zip_stream
.namelist()))
397 # As far as I can tell, there is no way to update a zip entry using
398 # zipfile; the best you can do is rewrite the archive.
399 # Iterate through the zipfile to maintain file order.
401 for zip_path
in zip_stream
.namelist():
402 if zip_path
in files_to_update
:
403 os_path
= zip_path_to_os_path_dict
[zip_path
]
404 zip_data
.append((os_path
, zip_path
, None))
405 new_files_to_add
.remove(zip_path
)
407 file_bytes
= zip_stream
.read(zip_path
)
408 file_info
= zip_stream
.getinfo(zip_path
)
409 zip_data
.append((None, file_info
, file_bytes
))
416 for zip_path
in new_files_to_add
:
417 zip_data
.append((zip_path_to_os_path_dict
[zip_path
], zip_path
, None))
420 print 'zip error: Nothing to do! (%s)' % (dest_zip
,)
424 zip_stream
= zipfile
.ZipFile(dest_zip
, write_mode
, zipfile
.ZIP_DEFLATED
)
425 for os_path
, file_info_or_zip_path
, file_bytes
in zip_data
:
426 if isinstance(file_info_or_zip_path
, zipfile
.ZipInfo
):
427 zip_path
= file_info_or_zip_path
.filename
429 zip_path
= file_info_or_zip_path
432 st
= os
.stat(os_path
)
433 if stat
.S_ISDIR(st
.st_mode
):
434 # Python 2.6 on the buildbots doesn't support writing directories to
435 # zip files. This was resolved in a later version of Python 2.6.
436 # We'll work around it by writing an empty file with the correct
437 # path. (This is basically what later versions do anyway.)
438 zip_info
= zipfile
.ZipInfo()
439 zip_info
.filename
= zip_path
440 zip_info
.date_time
= time
.localtime(st
.st_mtime
)[0:6]
441 zip_info
.compress_type
= zip_stream
.compression
442 zip_info
.flag_bits
= 0x00
443 zip_info
.external_attr
= (st
[0] & 0xFFFF) << 16L
445 zip_info
.compress_size
= 0
446 zip_info
.file_size
= 0
447 zip_stream
.writestr(zip_info
, '')
449 zip_stream
.write(os_path
, zip_path
)
451 zip_stream
.writestr(file_info_or_zip_path
, file_bytes
)
453 if not options
.quiet
:
454 if zip_path
in new_files_to_add
:
457 operation
= 'updating'
458 zip_info
= zip_stream
.getinfo(zip_path
)
459 if (zip_info
.compress_type
== zipfile
.ZIP_STORED
or
460 zip_info
.file_size
== 0):
461 print ' %s: %s (stored 0%%)' % (operation
, zip_path
)
462 elif zip_info
.compress_type
== zipfile
.ZIP_DEFLATED
:
463 print ' %s: %s (deflated %d%%)' % (operation
, zip_path
,
464 100 - zip_info
.compress_size
* 100 / zip_info
.file_size
)
471 def FindExeInPath(filename
):
472 env_path
= os
.environ
.get('PATH', '')
473 paths
= env_path
.split(os
.pathsep
)
475 def IsExecutableFile(path
):
476 return os
.path
.isfile(path
) and os
.access(path
, os
.X_OK
)
478 if os
.path
.sep
in filename
:
479 if IsExecutableFile(filename
):
483 filepath
= os
.path
.join(path
, filename
)
484 if IsExecutableFile(filepath
):
485 return os
.path
.abspath(os
.path
.join(path
, filename
))
489 """A Unix style which.
491 Looks for all arguments in the PATH environment variable, and prints their
492 path if they are executable files.
494 Note: If you pass an argument with a path to which, it will just test if it
495 is executable, not if it is in the path.
497 parser
= optparse
.OptionParser(usage
='usage: which args...')
498 _
, files
= parser
.parse_args(args
)
503 for filename
in files
:
504 fullname
= FindExeInPath(filename
)
525 print 'No command specified'
526 print 'Available commands: %s' % ' '.join(FuncMap
)
529 func
= FuncMap
.get(func_name
)
531 print 'Do not recognize command: %s' % func_name
532 print 'Available commands: %s' % ' '.join(FuncMap
)
535 return func(args
[1:])
536 except KeyboardInterrupt:
537 print '%s: interrupted' % func_name
540 if __name__
== '__main__':
541 sys
.exit(main(sys
.argv
[1:]))