about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authortoonn <toonn@toonn.io>2021-09-05 21:25:25 +0200
committertoonn <toonn@toonn.io>2021-09-05 21:28:22 +0200
commitd75dd6971ee8f67eebedfc797ae57154ae1ffb69 (patch)
treef18bf0e1e2dfd4c9185003c3db6f2cec9cff5124
parentbb2734a3899aed931390ec16a64c5938cb68277c (diff)
downloadranger-d75dd6971ee8f67eebedfc797ae57154ae1ffb69.tar.gz
commands: Rename setlocal to setinpath
The `setlocal` name is hard to remember, `setintag` is much easier to
recall, so we will follow the same naming style. `setlocal` remains
available as an alias for `setinpath`.

A new abstract class, `_setlocal`, is introduced as a basis for the
`setinpath` command because we want to reuse it for the original
behavior of `setlocal`. Several bug fixes have been integrated in this
new base class. The shifting of arguments was only incidentally correct
most of the time. The matching of quoted arguments only worked in the
absence of nested quotes.
-rwxr-xr-xranger/config/commands.py93
1 files changed, 75 insertions, 18 deletions
diff --git a/ranger/config/commands.py b/ranger/config/commands.py
index 1bd6f96f..5af41723 100755
--- a/ranger/config/commands.py
+++ b/ranger/config/commands.py
@@ -482,36 +482,93 @@ class set_(Command):
         return None
 
 
-class setlocal(set_):
-    """:setlocal path=<regular expression> <option name>=<python expression>
+class _setlocal(set_):
+    """Shared class for setinpath and setinregex
 
-    Gives an option a new value.
+    By implementing the _arg abstract propery you can affect what the name of
+    the pattern/path/regex argument can be, this should be a regular expression
+    without match groups.
+
+    By implementing the _format_arg abstract method you can affect how the
+    argument is formatted as a regular expression.
     """
-    PATH_RE_DQUOTED = re.compile(r'^setlocal\s+path="(.*?)"')
-    PATH_RE_SQUOTED = re.compile(r"^setlocal\s+path='(.*?)'")
-    PATH_RE_UNQUOTED = re.compile(r'^path=(.*?)$')
+    from abc import (ABCMeta, abstractmethod, abstractproperty)
+
+    __metaclass__ = ABCMeta
+
+    @abstractproperty
+    def _arg(self):
+        """The name of the option for the path/regex"""
+        raise NotImplementedError
+
+    def __init__(self, *args, **kwargs):
+        super(set_, self).__init__(*args, **kwargs)
+        # We require quoting of paths with whitespace so we have to take care
+        # not to match escaped quotes.
+        self.PATH_RE_DQUOTED = re.compile(
+            r'^set.+?\s+{arg}="(.*?[^\\])"'.format(arg=self._arg())
+        )
+        self.PATH_RE_SQUOTED = re.compile(
+            r"^set.+?\s+{arg}='(.*?[^\\])'".format(arg=self._arg())
+        )
+        self.PATH_RE_UNQUOTED = re.compile(
+            r'^{arg}=(.+?)$'.format(arg=self._arg())
+        )
 
     def _re_shift(self, match):
         if not match:
             return None
-        path = os.path.expanduser(match.group(1))
-        for _ in range(len(path.split())):
+        path = match.group(1)
+        for _ in range(1 + min(1, len(path.split()))):
             self.shift()
-        return path
+        return os.path.expanduser(path)
+
+    @abstractmethod
+    def _format_arg(self, arg):
+        """How to format the argument as a regular expression"""
+        raise NotImplementedError
 
     def execute(self):
-        path = self._re_shift(self.PATH_RE_DQUOTED.match(self.line))
-        if path is None:
-            path = self._re_shift(self.PATH_RE_SQUOTED.match(self.line))
-        if path is None:
-            path = self._re_shift(self.PATH_RE_UNQUOTED.match(self.arg(1)))
-        if path is None and self.fm.thisdir:
-            path = "^" + re.escape(self.fm.thisdir.path) + "$"
-        if not path:
+        arg = self._re_shift(self.PATH_RE_DQUOTED.match(self.line))
+        branch = 0
+        if arg is None:
+            arg = self._re_shift(self.PATH_RE_SQUOTED.match(self.line))
+            branch = 1
+        if arg is None:
+            arg = self._re_shift(self.PATH_RE_UNQUOTED.match(self.arg(1)))
+            branch = 2
+        if arg is None and self.fm.thisdir:
+            arg = self.fm.thisdir.path
+            branch = 3
+        if arg is None:
             return
+        else:
+            arg = self._format_arg(arg)
 
         name, value, _ = self.parse_setting_line()
-        self.fm.set_option_from_string(name, value, localpath=path)
+        self.fm.set_option_from_string(name, value, localpath=arg)
+
+
+class setinpath(_setlocal):
+    """:setinpath path=<path> <option name>=<python expression>
+
+    Sets an option when in a directory that matches <path>, relative paths can
+    match multiple directories, for example, ``path=build`` would match a build
+    directory in any directory. If the <path> contains whitespace it needs to
+    be quoted and nested quotes need to be backslash-escaped. The "path"
+    argument can also be named "pattern" to allow for easier switching with
+    ``setinregex``.
+    """
+    def _arg(self):
+        return "(?:path|pattern)"
+
+    def _format_arg(self, arg):
+        return "{0}$".format(re.escape(arg))
+
+
+class setlocal(setinpath):
+    """:setlocal is an alias for :setinpath"""
+    pass
 
 
 class setintag(set_):