diff -r 10eaab5887cd Lib/shutil.py --- a/Lib/shutil.py Mon Apr 23 14:25:19 2012 +0800 +++ b/Lib/shutil.py Tue Apr 24 00:28:33 2012 +0800 @@ -921,3 +921,81 @@ lines = size.lines return os.terminal_size((columns, lines)) + +def launch(path, operation='open'): + """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. + + On Windows, calls `os.startfile(operation)`. + + 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 FileNotFoundError: when path doesn't exist + :raises ValueError: when OS cannot find an application association + + :returns: None + + Example: + >>> launch('README') + """ + + # 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() + + if hasattr(os,'startfile') and hasattr(os.startfile, '__call__'): + os.startfile(path, operation) + elif sys.platform.startswith('darwin'): + # 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 FileNotFoundError("The path %s does not exist" % + repr(path), path) from err + elif stderr_output.startswith("No application can open"): + raise ValueError("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 FileNotFoundError("Path %s does not exist" % repr(path), path) from err + elif err.returncode == 3: + raise ValueError("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