summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/runner.py175
1 files changed, 175 insertions, 0 deletions
diff --git a/ranger/runner.py b/ranger/runner.py
new file mode 100644
index 00000000..c5d5a78c
--- /dev/null
+++ b/ranger/runner.py
@@ -0,0 +1,175 @@
+# Copyright (c) 2009, 2010 hut <hut@lavabit.com>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+This module is an abstract layer over subprocess.Popen
+
+It gives you highlevel control about how processes are run.
+
+Example:
+run = Runner(logfunc=print)
+run('sleep 2', wait=True)         # waits until the process exists
+run(['ls', '--help'], flags='p')  # pipes output to pager
+run()                             # prints an error message
+
+List of allowed flags:
+s: silent mode. output will be discarded.
+d: detach the process.
+p: redirect output to the pager
+(An uppercase key ensures that a certain flag will not be used.)
+"""
+
+import os
+import sys
+from subprocess import Popen, PIPE
+from ranger.ext.shell_escape import shell_escape
+from ranger.ext.waitpid_no_intr import waitpid_no_intr
+
+
+ALLOWED_FLAGS = 'sdpSDP'
+devnull = open(os.devnull, 'a')
+
+
+class Context(object):
+	"""
+	A context object contains data on how to run a process.
+	"""
+
+	def __init__(self, **keywords):
+		self.__dict__ = keywords
+	
+	@property
+	def filepaths(self):
+		if hasattr(self, files):
+			return [f.path for f in self.files]
+		return []
+
+	def __iter__(self):
+		"""Iterate over file paths"""
+		return iter(self.filepaths)
+
+	def squash_flags(self):
+		"""Remove duplicates and lowercase counterparts of uppercase flags"""
+		for flag in self.flags:
+			if ord(flag) <= 90:
+				bad = flag + flag.lower()
+				self.flags = ''.join(c for c in self.flags if c not in bad)
+
+
+class Runner(object):
+	def __init__(self, ui=None, logfunc=None, apps=None):
+		self.ui = ui
+		self.logfunc = logfunc
+		self.apps = apps
+	
+	def _log(self, text):
+		try:
+			self.logfunc(text)
+		except TypeError:
+			pass
+		return False
+
+	def _activate_ui(self, boolean):
+		if self.ui is not None:
+			if boolean:
+				try: self.ui.initialize()
+				except: self._log("Failed to initialize UI")
+			else:
+				try: self.ui.suspend()
+				except: self._log("Failed to suspend UI")
+	
+	def __call__(self, action=None, try_app_first=False,
+			app='default', files=None, mode=0,
+			flags='', wait=True, **popen_kws):
+		"""
+		Run the application in the way specified by the options.
+
+		Returns False if nothing can be done, None if there was an error,
+		otherwise the process object returned by Popen().
+
+		This function tries to find an action if none is defined.
+		"""
+
+		# Find an action if none was supplied by
+		# creating a Context object and passing it to
+		# an Application object.
+
+		context = Context(app=app, files=files, mode=mode,
+				flags=flags, wait=wait, popen_kws=popen_kws)
+
+		if self.apps:
+			if try_app_first and action is not None:
+				test = self.apps.apply(app, context)
+				if test:
+					action = test
+			if action is None:
+				action = self.apps.apply(app, context)
+				if action is None:
+					return self._log("No action found!")
+
+		if action is None:
+			return self._log("No way of determining the action!")
+
+		# Preconditions
+
+		context.squash_flags()
+		popen_kws = context.popen_kws  # shortcut
+
+		toggle_ui = True
+		pipe_output = False
+
+		popen_kws['args'] = action
+		if 'shell' not in popen_kws:
+			popen_kws['shell'] = isinstance(action, str)
+		if 'stdout' not in popen_kws:
+			popen_kws['stdout'] = sys.stdout
+		if 'stderr' not in popen_kws:
+			popen_kws['stderr'] = sys.stderr
+
+		# Evaluate the flags to determine keywords
+		# for Popen() and other variables
+
+		if 'p' in context.flags:
+			popen_kws['stdout'] = PIPE
+			popen_kws['stderr'] = PIPE
+			toggle_ui = False
+			pipe_output = True
+			context.wait = False
+		if 's' in context.flags or 'd' in context.flags:
+			for key in ('stdout', 'stderr', 'stdin'):
+				popen_kws[key] = devnull
+		if 'd' in context.flags:
+			toggle_ui = False
+			context.wait = False
+	
+		# Finally, run it
+
+		if toggle_ui:
+			self._activate_ui(False)
+		try:
+			process = None
+			try:
+				process = Popen(**popen_kws)
+			except:
+				self._log("Failed to run: " + str(action))
+			else:
+				if context.wait:
+					waitpid_no_intr(process.pid)
+		finally:
+			if toggle_ui:
+				self._activate_ui(True)
+			if pipe_output and process:
+				return self(action='less', app='pager', try_app_first=True,
+						stdin=process.stdout)
+			return process