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