summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--ranger/ext/rifle.py92
1 files changed, 68 insertions, 24 deletions
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index a61fb2a3..0e4442d7 100644
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -1,6 +1,20 @@
+#!/usr/bin/python
 # Copyright (C) 2012  Roman Zimbelmann <romanz@lavabit.com>
 # This software is distributed under the terms of the GNU GPL version 3.
 
+"""
+rifle, the file executor/opener of ranger
+
+This can be used as a standalone program or can be embedded in python code.
+When used together with ranger, it doesn't have to be installed to $PATH.
+
+Example usage:
+
+	rifle = Rifle("rilfe.conf")
+	rifle.reload_config()
+	rifle.execute(["file1", "file2"])
+"""
+
 import os.path
 import re, sys
 from subprocess import Popen, PIPE
@@ -10,6 +24,8 @@ from ranger.ext.get_executables import get_executables
 import time
 
 def _is_terminal():
+	# Check if stdin (file descriptor 0), stdout (fd 1) and
+	# stderr (fd 2) are connected to a terminal
 	try:
 		os.ttyname(0)
 		os.ttyname(1)
@@ -45,6 +61,7 @@ class Rifle(object):
 		self._app_flags = False
 
 	def reload_config(self, config_file=None):
+		"""Replace the current configuration with the one in config_file"""
 		if config_file is None:
 			config_file = self.config_file
 		f = open(config_file, 'r')
@@ -64,14 +81,28 @@ class Rifle(object):
 			command = command.strip()
 			self.rules.append((command, tests))
 			lineno += 1
+		f.close()
+
+	def _eval_condition(self, condition, files, label):
+		# Handle the negation of conditions starting with an exclamation mark,
+		# then pass on the arguments to _eval_condition2().
+
+		if condition[0].startswith('!'):
+			new_condition = tuple([condition[0][1:]]) + tuple(condition[1:])
+			return not self._eval_condition2(new_condition, files, label)
+		return self._eval_condition2(condition, files, label)
+
+	def _eval_condition2(self, rule, files, label):
+		# This function evaluates the condition, after _eval_condition() handled
+		# negation of conditions starting with a "!".
 
-	def _eval_rule(self, rule, files, label):
 		function = rule[0]
 		argument = rule[1] if len(rule) > 1 else ''
 		if not files:
 			return False
 
 		self._app_flags = ''
+		self._app_label = None
 
 		if function == 'ext':
 			extension = os.path.basename(files[0]).rsplit('.', 1)[-1]
@@ -87,12 +118,10 @@ class Rifle(object):
 		if function == 'terminal':
 			return _is_terminal()
 		if function == 'label':
+			self._app_label = argument
 			if label:
-				self._found_label = argument == label
-			else:
-				# don't care about label in this case
-				self._found_label = True
-			return self._found_label
+				return argument == label
+			return True
 		if function == 'flag':
 			self._app_flags = argument
 			return True
@@ -102,6 +131,7 @@ class Rifle(object):
 			return True
 
 	def _get_mimetype(self, fname):
+		# Spawn "file" to determine the mime-type of the given file.
 		if self._mimetype:
 			return self._mimetype
 		mimetype = spawn("file", "--mime-type", "-Lb", fname)
@@ -120,44 +150,60 @@ class Rifle(object):
 		return command
 
 	def list_commands(self, files, mimetype=None):
+		"""
+		Returns one 4-tuple for all currently applicable commands
+		The 4-tuple contains (count, command, label, flags).
+		count is the index, counted from 0 upwards,
+		command is the command that will be executed.
+		label and flags are the label and flags specified in the rule.
+		"""
 		self._mimetype = mimetype
-		command = None
 		count = 0
 		result = []
 		t = time.time()
 		for cmd, tests in self.rules:
 			for test in tests:
-				if not self._eval_rule(test, files, None):
+				if not self._eval_condition(test, files, None):
 					break
 			else:
-				result.append((count, cmd, self._app_flags))
+				result.append((count, cmd, self._app_label, self._app_flags))
 				count += 1
-		#sys.stderr.write("%f\n" % (time.time() - t))
 		return result
 
-	def execute(self, files, number=0, label=None, mimetype=None):
+	def execute(self, files, way=0, label=None, flags=None, mimetype=None):
+		"""
+		Executes the given list of files.
+
+		The default way to run files is 0.  Specifying way=N means rifle should
+		execute the Nth command whose conditions match for the given files.
+
+		If a label is specified, only rules with this label will be considered.
+		Specifying the mimetype will override the mimetype returned by `file`.
+
+		By specifying a flag, you extend the flag that is defined in the rule.
+		Uppercase flags negate the respective lowercase flags.
+		For example: if the flag in the rule is "pw" and you specify "Pf", then
+		the "p" flag is negated and the "f" flag is added, resulting in "wf".
+		"""
 		self._mimetype = mimetype
-		self._found_label = True
 		command = None
 		count = 0
+		# Determine command
 		for cmd, tests in self.rules:
-			if label:
-				self._found_label = False
 			for test in tests:
-				if not self._eval_rule(test, files, label):
-					#print("fails on test %s" % str(test))
+				if not self._eval_condition(test, files, label):
 					break
 			else:
-				if not self._found_label:
-					pass
-				elif count != number:
+				if count != way:
 					count += 1
 				else:
 					command = self.hook_command_preprocessing(command)
 					command = self._build_command(files, cmd)
 					break
-		#print(command)
-		if command is not None:
+		# Execute command
+		if command is None:
+			self.hook_logger("No action found.")
+		else:
 			command = self.hook_command_postprocessing(command)
 			self.hook_before_executing(command, self._mimetype, self._app_flags)
 			try:
@@ -166,12 +212,10 @@ class Rifle(object):
 			finally:
 				self.hook_after_executing(command, self._mimetype, self._app_flags)
 
-		else:
-			self.hook_logger("No action found.")
 
 if __name__ == '__main__':
 	import sys
 	rifle = Rifle(os.environ['HOME'] + '/.config/ranger/rifle.conf')
 	rifle.reload_config()
 	#print(rifle.list_commands(sys.argv[1:]))
-	rifle.execute(sys.argv[1:], number=0)
+	rifle.execute(sys.argv[1:], way=0)