diff options
-rw-r--r-- | ranger/core/actions.py | 108 | ||||
-rw-r--r-- | ranger/ext/macrodict.py | 81 |
2 files changed, 121 insertions, 68 deletions
diff --git a/ranger/core/actions.py b/ranger/core/actions.py index fe7d7e5e..42a1d2f5 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -20,21 +20,20 @@ from hashlib import sha512 from logging import getLogger import ranger +from ranger.container.directory import Directory +from ranger.container.file import File +from ranger.container.settings import ALLOWED_SETTINGS, ALLOWED_VALUES +from ranger.core.loader import CommandLoader, CopyLoader +from ranger.core.shared import FileManagerAware, SettingsAware +from ranger.core.tab import Tab from ranger.ext.direction import Direction -from ranger.ext.relative_symlink import relative_symlink from ranger.ext.keybinding_parser import key_to_string, construct_keybinding -from ranger.ext.safe_path import get_safe_path -from ranger.ext.shell_escape import shell_quote +from ranger.ext.macrodict import MacroDict, MACRO_FAIL, macro_val from ranger.ext.next_available_filename import next_available_filename +from ranger.ext.relative_symlink import relative_symlink from ranger.ext.rifle import squash_flags, ASK_COMMAND -from ranger.core.shared import FileManagerAware, SettingsAware -from ranger.core.tab import Tab -from ranger.container.directory import Directory -from ranger.container.file import File -from ranger.core.loader import CommandLoader, CopyLoader -from ranger.container.settings import ALLOWED_SETTINGS, ALLOWED_VALUES - -MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" +from ranger.ext.safe_path import get_safe_path +from ranger.ext.shell_escape import shell_quote LOG = getLogger(__name__) @@ -293,8 +292,8 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m raise ValueError("Could not apply macros to `%s'" % string) return result - def get_macros(self): # pylint: disable=too-many-branches,too-many-statements - macros = {} + def get_macros(self): + macros = MacroDict() macros['rangerdir'] = ranger.RANGERDIR if not ranger.args.clean: @@ -302,57 +301,39 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m macros['datadir'] = self.fm.datapath() macros['space'] = ' ' - if self.fm.thisfile: - macros['f'] = self.fm.thisfile.relative_path - else: - macros['f'] = MACRO_FAIL + macros['f'] = lambda: self.fm.thisfile.relative_path - if self.fm.thistab.get_selection: - macros['p'] = [os.path.join(self.fm.thisdir.path, fl.relative_path) - for fl in self.fm.thistab.get_selection()] - macros['s'] = [fl.relative_path for fl in self.fm.thistab.get_selection()] - else: - macros['p'] = MACRO_FAIL - macros['s'] = MACRO_FAIL + macros['p'] = lambda: [os.path.join(self.fm.thisdir.path, + fl.relative_path) for fl in + self.fm.thistab.get_selection()] + macros['s'] = lambda: [fl.relative_path for fl in + self.fm.thistab.get_selection()] - if self.fm.copy_buffer: - macros['c'] = [fl.path for fl in self.fm.copy_buffer] - else: - macros['c'] = MACRO_FAIL + macros['c'] = lambda: [fl.path for fl in self.fm.copy_buffer] - if self.fm.thisdir.files: - macros['t'] = [fl.relative_path for fl in self.fm.thisdir.files - if fl.realpath in self.fm.tags or []] - else: - macros['t'] = MACRO_FAIL + macros['t'] = lambda: [fl.relative_path for fl in + self.fm.thisdir.files if fl.realpath in + self.fm.tags or []] - if self.fm.thisdir: - macros['d'] = self.fm.thisdir.path - else: - macros['d'] = '.' + macros['d'] = macro_val(lambda: self.fm.thisdir.path, fallback='.') # define d/f/p/s macros for each tab for i in range(1, 10): + # pylint: disable=cell-var-from-loop try: tab = self.fm.tabs[i] - except KeyError: + tabdir = tab.thisdir + except (KeyError, AttributeError): continue - tabdir = tab.thisdir if not tabdir: continue i = str(i) - macros[i + 'd'] = tabdir.path - if tabdir.get_selection(): - macros[i + 'p'] = [os.path.join(tabdir.path, fl.relative_path) - for fl in tabdir.get_selection()] - macros[i + 's'] = [fl.path for fl in tabdir.get_selection()] - else: - macros[i + 'p'] = MACRO_FAIL - macros[i + 's'] = MACRO_FAIL - if tabdir.pointed_obj: - macros[i + 'f'] = tabdir.pointed_obj.path - else: - macros[i + 'f'] = MACRO_FAIL + macros[i + 'd'] = lambda: tabdir.path + macros[i + 'p'] = lambda: [os.path.join(tabdir.path, + fl.relative_path) for fl in + tabdir.get_selection()] + macros[i + 's'] = lambda: [fl.path for fl in tabdir.get_selection()] + macros[i + 'f'] = lambda: tabdir.pointed_obj.path # define D/F/P/S for the next tab found_current_tab = False @@ -368,25 +349,16 @@ class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-m found_current_tab = True if found_current_tab and next_tab is None: next_tab = self.fm.tabs[first_tab] - next_tab_dir = next_tab.thisdir + try: + next_tab_dir = next_tab.thisdir + except AttributeError: + return macros - if next_tab_dir: - macros['D'] = str(next_tab_dir.path) - if next_tab.thisfile: - macros['F'] = next_tab.thisfile.path - else: - macros['F'] = MACRO_FAIL - if next_tab_dir.get_selection(): - macros['P'] = [os.path.join(next_tab.path, fl.path) + macros['D'] = lambda: str(next_tab_dir.path) + macros['F'] = lambda: next_tab.thisfile.path + macros['P'] = lambda: [os.path.join(next_tab.path, fl.path) for fl in next_tab.get_selection()] - macros['S'] = [fl.path for fl in next_tab.get_selection()] - else: - macros['P'] = MACRO_FAIL - macros['S'] = MACRO_FAIL - else: - macros['D'] = MACRO_FAIL - macros['F'] = MACRO_FAIL - macros['S'] = MACRO_FAIL + macros['S'] = lambda: [fl.path for fl in next_tab.get_selection()] return macros diff --git a/ranger/ext/macrodict.py b/ranger/ext/macrodict.py new file mode 100644 index 00000000..8a8a4a3d --- /dev/null +++ b/ranger/ext/macrodict.py @@ -0,0 +1,81 @@ +import sys + + +MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" + + +def macro_val(thunk, fallback=MACRO_FAIL): + try: + return thunk() + except AttributeError: + return fallback + + +try: + from collections.abc import MutableMapping # pylint: disable=no-name-in-module +except ImportError: + from collections import MutableMapping + + +class MacroDict(MutableMapping): + """Mapping that returns a fallback value when thunks error + + Macros can be used in scenarios where several attributes aren't + initialized yet. To avoid errors in these cases we have to delay the + evaluation of these attributes using ``lambda``s. This + ``MutableMapping`` evaluates these thunks before returning them + replacing them with a fallback value if necessary. + + For convenience it also catches ``TypeError`` so you can store + non-callable values without thunking. + + >>> m = MacroDict() + >>> o = type("", (object,), {})() + >>> o.existing_attribute = "I exist!" + + >>> m['a'] = "plain value" + >>> m['b'] = lambda: o.non_existent_attribute + >>> m['c'] = lambda: o.existing_attribute + + >>> m['a'] + 'plain value' + >>> m['b'] + '<\\x01\\x01MACRO_HAS_NO_VALUE\\x01\\x01>' + >>> m['c'] + 'I exist!' + """ + + def __init__(self, *args, **kwargs): + super(MacroDict, self).__init__() + self.__dict__.update(*args, **kwargs) + + def __setitem__(self, key, value): + try: + real_val = value() + if real_val is None: + real_val = MACRO_FAIL + except AttributeError: + real_val = MACRO_FAIL + except TypeError: + real_val = value + self.__dict__[key] = real_val + + def __getitem__(self, key): + return self.__dict__[key] + + def __delitem__(self, key): + del self.__dict__[key] + + def __iter__(self): + return iter(self.__dict__) + + def __len__(self): + return len(self.__dict__) + + def __str__(self): + return str(self.__dict__) + + +if __name__ == '__main__': + import doctest + sys.exit(doctest.testmod()[0]) |