[kupfer] commands: Add actions Filter through Command and Send to Command



commit 62b8c8cbe9842c1d8114e379988ce2ee37194675
Author: Ulrik Sverdrup <ulrik sverdrup gmail com>
Date:   Mon Feb 28 19:36:46 2011 +0100

    commands: Add actions Filter through Command and Send to Command
    
    Allow to exchange text with commands (script/executables in the
    catalog itself) by stdin/stdout. Add a scripts folder to your catalog
    to use (or add scripts to Favorites)
    
    Not final whether both the commands will stay.
    
    Add stdin supplication feature to utils.AsyncCommand for this purpose.

 kupfer/plugin/commands.py |   84 ++++++++++++++++++++++++++++++++++++++++++++-
 kupfer/utils.py           |   41 ++++++++++++++++++++--
 2 files changed, 121 insertions(+), 4 deletions(-)
---
diff --git a/kupfer/plugin/commands.py b/kupfer/plugin/commands.py
index 7194416..daf33ab 100644
--- a/kupfer/plugin/commands.py
+++ b/kupfer/plugin/commands.py
@@ -1,5 +1,9 @@
 __kupfer_name__ = _("Shell Commands")
 __kupfer_sources__ = ()
+__kupfer_actions__ = (
+		"WriteToCommand",
+		"FilterThroughCommand",
+	)
 __kupfer_text_sources__ = ("CommandTextSource",)
 __description__ = _("Run commandline programs")
 __version__ = ""
@@ -10,7 +14,7 @@ import shlex
 
 import gobject
 
-from kupfer.objects import TextSource, Leaf, TextLeaf, Action
+from kupfer.objects import TextSource, Leaf, TextLeaf, Action, FileLeaf
 from kupfer.obj.fileactions import Execute
 from kupfer import utils, icons
 from kupfer import commandexec
@@ -49,6 +53,84 @@ class GetOutput (Action):
 	def get_description(self):
 		return _("Run program and return its output")
 
+class WriteToCommand (Action):
+	def __init__(self):
+		Action.__init__(self, _("Send to Command..."))
+
+	def activate(self, leaf, iobj):
+		# use shlex to allow simple quoting
+		commandline = iobj.object
+		try:
+			argv = unicode_shlex_split(commandline)
+		except ValueError:
+			# Exception raised on unpaired quotation marks
+			argv = commandline.split(None, 1)
+		ctx = commandexec.DefaultActionExecutionContext()
+		token = ctx.get_async_token()
+		pretty.print_debug(__name__, "Spawning without timeout")
+		acom = utils.AsyncCommand(argv, self.finish_callback, None,
+		                          stdin=leaf.object)
+		acom.token = token
+
+	def item_types(self):
+		yield TextLeaf
+
+	def requires_object(self):
+		return True
+
+	def object_types(self):
+		yield FileLeaf
+
+	def valid_object(self, iobj, for_item=None):
+		return not iobj.is_dir() and os.access(iobj.object, os.X_OK | os.R_OK)
+
+	def finish_callback(self, acommand, stdout, stderr):
+		pretty.print_debug(__name__, "Exited:", acommand)
+		pass
+
+	def get_description(self):
+		return _("Run program and supply text on the standard input")
+
+class FilterThroughCommand (Action):
+	def __init__(self):
+		Action.__init__(self, _("Filter through Command..."))
+
+	def activate(self, leaf, iobj):
+		# use shlex to allow simple quoting
+		commandline = iobj.object
+		try:
+			argv = unicode_shlex_split(commandline)
+		except ValueError:
+			# Exception raised on unpaired quotation marks
+			argv = commandline.split(None, 1)
+		ctx = commandexec.DefaultActionExecutionContext()
+		token = ctx.get_async_token()
+		pretty.print_debug(__name__, "Spawning without timeout")
+		acom = utils.AsyncCommand(argv, self.finish_callback, None,
+		                          stdin=leaf.object)
+		acom.token = token
+
+	def item_types(self):
+		yield TextLeaf
+
+	def requires_object(self):
+		return True
+
+	def object_types(self):
+		yield FileLeaf
+
+	def valid_object(self, iobj, for_item=None):
+		return not iobj.is_dir() and os.access(iobj.object, os.X_OK | os.R_OK)
+
+	def finish_callback(self, acommand, stdout, stderr):
+		pretty.print_debug(__name__, "Exited:", acommand)
+		ctx = commandexec.DefaultActionExecutionContext()
+		leaf = TextLeaf(kupferstring.fromlocale(stdout))
+		ctx.register_late_result(acommand.token, leaf)
+
+	def get_description(self):
+		return _("Run program and supply text on the standard input")
+
 class Command (TextLeaf):
 	def __init__(self, exepath, name):
 		TextLeaf.__init__(self, name, name)
diff --git a/kupfer/utils.py b/kupfer/utils.py
index b1c3e1b..b813588 100644
--- a/kupfer/utils.py
+++ b/kupfer/utils.py
@@ -84,14 +84,19 @@ class AsyncCommand (object):
 	when command is killed after @timeout_s seconds, whichever
 	comes first.
 
+	If @timeout_s is None, no timeout is used
+
+	If stdin is a byte string, it is supplied on the command's stdin.
+
 	finish_callback -> (AsyncCommand, stdout_output, stderr_output)
 	"""
 	# the maximum input (bytes) we'll read in one shot (one io_callback)
 	max_input_buf = 512 * 1024
 
-	def __init__(self, argv, finish_callback, timeout_s):
+	def __init__(self, argv, finish_callback, timeout_s, stdin=None):
 		self.stdout = []
 		self.stderr = []
+		self.stdin = []
 		self.timeout = False
 		self.killed = False
 		self.finished = False
@@ -104,13 +109,29 @@ class AsyncCommand (object):
 		pid, stdin_fd, stdout_fd, stderr_fd = \
 		     glib.spawn_async(argv, standard_output=True, standard_input=True,
 		                      standard_error=True, flags=flags)
-		os.close(stdin_fd)
+
+		if stdin:
+			self.stdin[:] = self._split_string(stdin, self.max_input_buf)
+			in_io_flags = glib.IO_OUT | glib.IO_ERR | glib.IO_HUP | glib.IO_NVAL
+			glib.io_add_watch(stdin_fd, in_io_flags, self._in_io_callback,
+			                  self.stdin)
+		else:
+			os.close(stdin_fd)
+
 		io_flags = glib.IO_IN | glib.IO_ERR | glib.IO_HUP | glib.IO_NVAL
 		glib.io_add_watch(stdout_fd, io_flags, self._io_callback, self.stdout)
 		glib.io_add_watch(stderr_fd, io_flags, self._io_callback, self.stderr)
 		self.pid = pid
 		glib.child_watch_add(pid, self._child_callback)
-		glib.timeout_add_seconds(timeout_s, self._timeout_callback)
+		if timeout_s is not None:
+			glib.timeout_add_seconds(timeout_s, self._timeout_callback)
+
+	def _split_string(self, s, length):
+		"""Split @s in pieces of @length"""
+		L = []
+		for i in xrange(0, len(s)//length + 1):
+			L.append(s[i*length:(i+1)*length])
+		return L
 
 	def _io_callback(self, sourcefd, condition, databuf):
 		if condition & glib.IO_IN:
@@ -118,6 +139,20 @@ class AsyncCommand (object):
 			return True
 		return False
 
+	def _in_io_callback(self, sourcefd, condition, databuf):
+		"""write to child's stdin"""
+		if condition & glib.IO_OUT:
+			if not databuf:
+				os.close(sourcefd)
+				return False
+			s = databuf.pop(0)
+			written = os.write(sourcefd, s)
+			if written < len(s):
+				databuf.insert(0, s[written:])
+			pretty.print_debug(__name__, "Wrote", repr(s[:written]))
+			return True
+		return False
+
 	def _child_callback(self, pid, condition):
 		self.finished = True
 		self.finish_callback(self, "".join(self.stdout), "".join(self.stderr))



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]