diff -r 10eaab5887cd Lib/shutil.py --- a/Lib/shutil.py Mon Apr 23 14:25:19 2012 +0800 +++ b/Lib/shutil.py Mon Apr 23 18:24:58 2012 +0800 @@ -822,7 +822,7 @@ def disk_usage(path): """Return disk usage statistics about the given path. - Returned valus is a named tuple with attributes 'total', 'used' and + Returned value is a named tuple with attributes 'total', 'used' and 'free', which are the amount of total, used and free space, in bytes. """ st = os.statvfs(path) @@ -921,3 +921,95 @@ lines = size.lines return os.terminal_size((columns, lines)) + +def launch(path, operation='open', gui=True, fallback=True): + """Open file with the application that the OS associates with the file-type + + `path` is the path to the file or directory to open + + `operation` is the desired application action. Only 'open' is fully + supported. For Windows (and any OS that implements os.startfile), + 'edit', 'print', 'explore', are supported. The operation 'find' is + supported on Windows if `path` is to a directory. If not, the containing + folder/directory for the path is used for the os.startfile 'find' operation. + + `gui` indicates the caller's preference for launching a GUI application + rather than an application with a command-line interface. NOT IMPLEMENTED + + Fallback controls whether launch() falls back gracefully to less prefered + application options--to a nonGUI application if no GUI application + is found, or to a viewer application if no edit application can be found. + + On Windows, calls os.startfile(), and attempts to call it first, + irrespective of os.name or system.platform. + + On Mac OS X, uses the ```open `_`` command. + + On non-Mac Unix, uses the ```xdg-open `_`` command and fails + if it can't be run (e.g., when it's not installed). + + :raises NotImplementedError: on non-Mac Unix when ``xdg-open`` fails + :raises IOError: with *:attr:`errno` = :data:`errno.ENOENT`*, when path doesn't exist + :raises OSError: with *:attr:`errno` = :data:`errno.ENOSYS`*, when OS cannot find an appropriate application association + + :returns: None + + Example: + >>> launch('README') + """ + + # FIXME: Is OSError(errno.ENOSYS) the right error to raise? + # TODO: Utilize best-practices from Mercurial project: + # http://selenic.com/repo/hg-stable/file/2770d03ae49f/mercurial/ui.py + # http://selenic.com/repo/hg-stable/file/2770d03ae49f/mercurial/util.py + + import sys, os, subprocess, errno + + # Ensure cross-platform path (Windows-friendly) + path = os.path.normpath(path) + + # Guess user's desired operation (Windows verb) + operation = operation or 'open' + # Force case and padding to comply with Windows verb specification + operation = operation.strip().lower() + # Too presumptive, and wouldn't help for most typos ??? + # operation = operation[:min(len(operation),7] + + # if os.name == 'nt' or sys.platform.startswith('win32') ??? + if hasattr(os,'startfile') and hasattr(os.startfile, '__call__'): + os.startfile(path, operation) + elif sys.platform.startswith('darwin'): # or os.name == 'mac' ??? + # https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/open.1.html + proc = subprocess.Popen( ['open', path], stderr=subprocess.PIPE, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL) + _, stderr_output = proc.communicate() + if proc.returncode: + if stderr_output.endswith("does not exist."): + raise IOError(errno.ENOENT, "The path %s does not exist" % + repr(path), path) from err + elif stderr_output.startswith("No application can open"): + raise OSError(errno.ENOSYS, + "No application associated with file type in", + path) from err + elif os.name == 'posix': + try: + subprocess.check_call(['xdg-open', path], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + except subprocess.CalledProcessError as err: + # http://portland.freedesktop.org/xdg-utils-1.0/xdg-open.html manpage for return code meanings + if err.returncode == 2: + raise IOError(errno.ENOENT, "Path %s does not exist" % repr(path), path) from err + elif err.returncode == 3: + raise OSError(errno.ENOSYS, + "No application associated with file type in", + path) from err + else: + raise + except EnvironmentError as err: + if err.errno == errno.ENOENT: # xdg-open not installed/accessible + raise NotImplementedError from err + else: + raise