diff options
author | nfnty <git@nfnty.se> | 2017-01-20 12:07:53 +0100 |
---|---|---|
committer | nfnty <git@nfnty.se> | 2017-01-20 12:07:53 +0100 |
commit | 1c90e610de6591e8052a1056c775219b5e767002 (patch) | |
tree | 69b56f67dee1d6dfcea869de897846baaded3fa6 /ranger | |
parent | e26d163debc9f55a89a27f94a43771526d2ff0b7 (diff) | |
parent | 03ee065e35c6a8f0e0dc1e66d8f4c3e83c51f315 (diff) | |
download | ranger-1c90e610de6591e8052a1056c775219b5e767002.tar.gz |
Merge branch 'lint'
Diffstat (limited to 'ranger')
74 files changed, 1831 insertions, 1476 deletions
diff --git a/ranger/__init__.py b/ranger/__init__.py index c2aa804e..a84bf3e8 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -8,9 +8,10 @@ directory hierarchy. The secondary task of ranger is to figure out which program you want to use to open your files with. """ +from __future__ import (absolute_import, print_function) + import sys import os -import tempfile # Information __license__ = 'GPL3' @@ -34,4 +35,6 @@ VERSION = 'ranger-master %s\n\nPython %s' % (__version__, sys.version) # and the configuration directory will be $XDG_CONFIG_HOME/ranger instead. CONFDIR = '~/.config/ranger' -from ranger.core.main import main +args = None # pylint: disable=invalid-name + +from ranger.core.main import main # NOQA pylint: disable=wrong-import-position diff --git a/ranger/api/__init__.py b/ranger/api/__init__.py index 5981c31a..992dfc20 100644 --- a/ranger/api/__init__.py +++ b/ranger/api/__init__.py @@ -3,10 +3,15 @@ """Files in this module contain helper functions used in configuration files.""" -# Hooks for use in plugins: +from __future__ import (absolute_import, print_function) + +import ranger # NOQA + +from ranger.core.linemode import LinemodeBase # NOQA -def hook_init(fm): +# Hooks for use in plugins: +def hook_init(fm): # pylint: disable=unused-argument """A hook that is called when ranger starts up. Parameters: @@ -20,7 +25,7 @@ def hook_init(fm): """ -def hook_ready(fm): +def hook_ready(fm): # pylint: disable=unused-argument """A hook that is called after the ranger UI is initialized. Parameters: @@ -32,8 +37,6 @@ def hook_ready(fm): NOT print anything to stdout anymore from here on. Use fm.notify instead. """ -from ranger.core.linemode import LinemodeBase - def register_linemode(linemode_class): """Add a custom linemode class. See ranger.core.linemode""" diff --git a/ranger/api/commands.py b/ranger/api/commands.py index 93c50adb..0a3ea470 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -3,12 +3,17 @@ # TODO: Add an optional "!" to all commands and set a flag if it's there +from __future__ import (absolute_import, print_function) + import os -import ranger import re import inspect -from collections import deque -from ranger.api import * +# COMPAT pylint: disable=unused-import +from collections import deque # NOQA +from ranger.api import LinemodeBase, hook_init, hook_ready, register_linemode # NOQA +# pylint: enable=unused-import + +import ranger from ranger.core.shared import FileManagerAware from ranger.ext.lazy_property import lazy_property @@ -16,6 +21,7 @@ _SETTINGS_RE = re.compile(r'^\s*([^\s]+?)=(.*)$') class CommandContainer(object): + def __init__(self): self.commands = {} @@ -25,10 +31,12 @@ class CommandContainer(object): def alias(self, name, full_command): try: cmd = type(name, (AliasCommand, ), dict()) + # pylint: disable=protected-access cmd._based_function = name cmd._function_name = name cmd._object_name = name cmd._line = full_command + # pylint: enable=protected-access self.commands[name] = cmd except Exception: @@ -50,17 +58,18 @@ class CommandContainer(object): attribute = getattr(obj, attribute_name) if hasattr(attribute, '__call__'): cmd = type(attribute_name, (FunctionCommand, ), dict(__doc__=attribute.__doc__)) + # pylint: disable=protected-access cmd._based_function = attribute cmd._function_name = attribute.__name__ cmd._object_name = obj.__class__.__name__ + # pylint: enable=protected-access self.commands[attribute_name] = cmd def get_command(self, name, abbrev=True): if abbrev: lst = [cls for cmd, cls in self.commands.items() - if cls.allow_abbrev and cmd.startswith(name) - or cmd == name] - if len(lst) == 0: + if cls.allow_abbrev and cmd.startswith(name) or cmd == name] + if not lst: raise KeyError if len(lst) == 1: return lst[0] @@ -98,12 +107,11 @@ class Command(FileManagerAware): self.firstpart = '' @classmethod - def get_name(self): - classdict = self.__mro__[0].__dict__ + def get_name(cls): + classdict = cls.__mro__[0].__dict__ if 'name' in classdict and classdict['name']: - return self.name - else: - return self.__name__ + return cls.name + return cls.__name__ def execute(self): """Override this""" @@ -149,9 +157,6 @@ class Command(FileManagerAware): self._setting_line = None self._shifted += 1 - def tabinsert(self, word): - return ''.join([self._tabinsert_left, word, self._tabinsert_right]) - def parse_setting_line(self): """ Parses the command line argument that is passed to the `:set` command. @@ -223,7 +228,7 @@ class Command(FileManagerAware): flags = "" args = self.line.split() rest = "" - if len(args) > 0: + if args: rest = self.line[len(args[0]):].lstrip() for arg in args[1:]: if arg == "--": @@ -241,18 +246,6 @@ class Command(FileManagerAware): import logging return logging.getLogger('ranger.commands.' + self.__class__.__name__) - # XXX: Lazy properties? Not so smart? self.line can change after all! - @lazy_property - def _tabinsert_left(self): - try: - return self.line[:self.line[0:self.pos].rindex(' ') + 1] - except ValueError: - return '' - - @lazy_property - def _tabinsert_right(self): - return self.line[self.pos:] - # COMPAT: this is still used in old commands.py configs def _tab_only_directories(self): from os.path import dirname, basename, expanduser, join @@ -280,7 +273,7 @@ class Command(FileManagerAware): else: _, dirnames, _ = next(os.walk(abs_dirname)) dirnames = [dn for dn in dirnames - if dn.startswith(rel_basename)] + if dn.startswith(rel_basename)] except (OSError, StopIteration): # os.walk found nothing pass @@ -288,7 +281,7 @@ class Command(FileManagerAware): dirnames.sort() # no results, return None - if len(dirnames) == 0: + if not dirnames: return # one result. since it must be a directory, append a slash. @@ -300,7 +293,7 @@ class Command(FileManagerAware): return (self.start(1) + join(rel_dirname, dirname) for dirname in dirnames) - def _tab_directory_content(self): + def _tab_directory_content(self): # pylint: disable=too-many-locals from os.path import dirname, basename, expanduser, join cwd = self.fm.thisdir.path @@ -331,30 +324,28 @@ class Command(FileManagerAware): else: # Fall back to old method with "os.walk" _, dirnames, filenames = next(os.walk(abs_dest)) - names = dirnames + filenames - names.sort() + names = sorted(dirnames + filenames) # are we in the middle of the filename? else: if directory.content_loaded: # Take the order from the directory object names = [f.basename for f in directory.files - if f.basename.startswith(rel_basename)] + if f.basename.startswith(rel_basename)] if self.fm.thisfile.basename in names: i = names.index(self.fm.thisfile.basename) names = names[i:] + names[:i] else: # Fall back to old method with "os.walk" _, dirnames, filenames = next(os.walk(abs_dirname)) - names = [name for name in (dirnames + filenames) - if name.startswith(rel_basename)] - names.sort() + names = sorted([name for name in (dirnames + filenames) + if name.startswith(rel_basename)]) except (OSError, StopIteration): # os.walk found nothing pass else: # no results, return None - if len(names) == 0: + if not names: return # one result. append a slash if it's a directory @@ -370,7 +361,7 @@ class Command(FileManagerAware): def _tab_through_executables(self): from ranger.ext.get_executables import get_executables programs = [program for program in get_executables() if - program.startswith(self.rest(1))] + program.startswith(self.rest(1))] if not programs: return if len(programs) == 1: @@ -384,14 +375,16 @@ class FunctionCommand(Command): _object_name = "" _function_name = "unknown" - def execute(self): + def execute(self): # pylint: disable=too-many-branches if not self._based_function: return if len(self.args) == 1: try: + # pylint: disable=not-callable return self._based_function(**{'narg': self.quantifier}) + # pylint: enable=not-callable except TypeError: - return self._based_function() + return self._based_function() # pylint: disable=not-callable args, keywords = list(), dict() for arg in self.args[1:]: @@ -418,20 +411,22 @@ class FunctionCommand(Command): try: if self.quantifier is None: - return self._based_function(*args, **keywords) + return self._based_function(*args, **keywords) # pylint: disable=not-callable else: try: - return self._based_function(*args, **keywords) + return self._based_function(*args, **keywords) # pylint: disable=not-callable except TypeError: del keywords['narg'] - return self._based_function(*args, **keywords) + return self._based_function(*args, **keywords) # pylint: disable=not-callable except TypeError: - if ranger.arg.debug: + if ranger.args.debug: raise else: - self.fm.notify("Bad arguments for %s.%s: %s, %s" % - (self._object_name, self._function_name, - repr(args), repr(keywords)), bad=True) + self.fm.notify( + "Bad arguments for %s.%s: %s, %s" % ( + self._object_name, self._function_name, repr(args), repr(keywords)), + bad=True, + ) class AliasCommand(Command): @@ -448,8 +443,10 @@ class AliasCommand(Command): def tab(self, tabnum): cmd = self._make_cmd() - args = inspect.signature(cmd.tab).parameters if self.fm.py3 else \ - inspect.getargspec(cmd.tab).args + if self.fm.py3: + args = inspect.signature(cmd.tab).parameters # pylint: disable=no-member + else: + args = inspect.getargspec(cmd.tab).args # pylint: disable=deprecated-method return cmd.tab(tabnum) if 'tabnum' in args else cmd.tab() def cancel(self): diff --git a/ranger/api/options.py b/ranger/api/options.py index 0cce1364..a2bf4e8c 100644 --- a/ranger/api/options.py +++ b/ranger/api/options.py @@ -2,7 +2,13 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. # THIS WHOLE FILE IS OBSOLETE AND EXISTS FOR BACKWARDS COMPATIBILITIY -import re -from re import compile as regexp -from ranger.api import * -from ranger.gui import color + +from __future__ import (absolute_import, print_function) + +# pylint: disable=unused-import +import re # NOQA +from re import compile as regexp # NOQA + +from ranger.api import LinemodeBase, hook_init, hook_ready, register_linemode # NOQA +from ranger.gui import color # NOQA +# pylint: enable=unused-import diff --git a/ranger/colorschemes/default.py b/ranger/colorschemes/default.py index d04e5f6a..7f4c48c5 100644 --- a/ranger/colorschemes/default.py +++ b/ranger/colorschemes/default.py @@ -1,14 +1,20 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from ranger.gui.colorscheme import ColorScheme -from ranger.gui.color import * +from ranger.gui.color import ( + black, blue, cyan, green, magenta, red, white, yellow, default, + normal, bold, reverse, + default_colors, +) class Default(ColorScheme): progress_bar_color = blue - def use(self, context): + def use(self, context): # pylint: disable=too-many-branches,too-many-statements fg, bg, attr = default_colors if context.reset: @@ -35,7 +41,7 @@ class Default(ColorScheme): fg = blue elif context.executable and not \ any((context.media, context.container, - context.fifo, context.socket)): + context.fifo, context.socket)): attr |= bold fg = green if context.socket: @@ -46,7 +52,7 @@ class Default(ColorScheme): if context.device: attr |= bold if context.link: - fg = context.good and cyan or magenta + fg = cyan if context.good else magenta if context.tag_marker and not context.selected: attr |= bold if fg in (red, magenta): @@ -74,7 +80,7 @@ class Default(ColorScheme): elif context.in_titlebar: attr |= bold if context.hostname: - fg = context.bad and red or green + fg = red if context.bad else green elif context.directory: fg = blue elif context.tab: diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py index fbcfab7d..00f51d5c 100644 --- a/ranger/colorschemes/jungle.py +++ b/ranger/colorschemes/jungle.py @@ -1,8 +1,10 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. -from ranger.gui.color import * +from __future__ import (absolute_import, print_function) + from ranger.colorschemes.default import Default +from ranger.gui.color import green, red, blue class Scheme(Default): diff --git a/ranger/colorschemes/snow.py b/ranger/colorschemes/snow.py index 90090854..2e0a004f 100644 --- a/ranger/colorschemes/snow.py +++ b/ranger/colorschemes/snow.py @@ -1,11 +1,14 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from ranger.gui.colorscheme import ColorScheme -from ranger.gui.color import * +from ranger.gui.color import default_colors, reverse, bold class Snow(ColorScheme): + def use(self, context): fg, bg, attr = default_colors diff --git a/ranger/colorschemes/solarized.py b/ranger/colorschemes/solarized.py index 8cdfb5ce..ad57dd8a 100644 --- a/ranger/colorschemes/solarized.py +++ b/ranger/colorschemes/solarized.py @@ -5,14 +5,20 @@ # from https://github.com/seebi/dircolors-solarized. # This is a modification of Roman Zimbelmann's default colorscheme. +from __future__ import (absolute_import, print_function) + from ranger.gui.colorscheme import ColorScheme -from ranger.gui.color import * +from ranger.gui.color import ( + cyan, magenta, red, white, default, + normal, bold, reverse, + default_colors, +) class Solarized(ColorScheme): progress_bar_color = 33 - def use(self, context): + def use(self, context): # pylint: disable=too-many-branches,too-many-statements fg, bg, attr = default_colors if context.reset: @@ -40,7 +46,7 @@ class Solarized(ColorScheme): fg = 33 elif context.executable and not \ any((context.media, context.container, - context.fifo, context.socket)): + context.fifo, context.socket)): fg = 64 attr |= bold if context.socket: @@ -56,7 +62,7 @@ class Solarized(ColorScheme): bg = 230 attr |= bold if context.link: - fg = context.good and 37 or 160 + fg = 37 if context.good else 160 attr |= bold if context.bad: bg = 235 @@ -87,13 +93,13 @@ class Solarized(ColorScheme): elif context.in_titlebar: attr |= bold if context.hostname: - fg = context.bad and 16 or 255 + fg = 16 if context.bad else 255 if context.bad: bg = 166 elif context.directory: fg = 33 elif context.tab: - fg = context.good and 47 or 33 + fg = 47 if context.good else 33 bg = 239 elif context.link: fg = cyan diff --git a/ranger/config/commands.py b/ranger/config/commands.py index a384f427..962a6a98 100755 --- a/ranger/config/commands.py +++ b/ranger/config/commands.py @@ -73,17 +73,23 @@ # File objects (for example self.fm.thisfile) have these useful attributes and # methods: # -# cf.path: The path to the file. -# cf.basename: The base name only. -# cf.load_content(): Force a loading of the directories content (which +# tfile.path: The path to the file. +# tfile.basename: The base name only. +# tfile.load_content(): Force a loading of the directories content (which # obviously works with directories only) -# cf.is_directory: True/False depending on whether it's a directory. +# tfile.is_directory: True/False depending on whether it's a directory. # # For advanced commands it is unavoidable to dive a bit into the source code # of ranger. # =================================================================== -from ranger.api.commands import * +from __future__ import (absolute_import, print_function) + +from collections import deque +import os +import re + +from ranger.api.commands import Command class alias(Command): @@ -107,6 +113,7 @@ class echo(Command): Display the text in the statusbar. """ + def execute(self): self.fm.notify(self.rest(1)) @@ -120,7 +127,6 @@ class cd(Command): """ def execute(self): - import os.path if self.arg(1) == '-r': self.shift() destination = os.path.realpath(self.rest(1)) @@ -138,15 +144,14 @@ class cd(Command): else: self.fm.cd(destination) - def tab(self, tabnum): - import os + def tab(self, tabnum): # pylint: disable=too-many-locals from os.path import dirname, basename, expanduser, join cwd = self.fm.thisdir.path rel_dest = self.rest(1) bookmarks = [v.path for v in self.fm.bookmarks.dct.values() - if rel_dest in v.path] + if rel_dest in v.path] # expand the tilde into the user directory if rel_dest.startswith('~'): @@ -167,7 +172,7 @@ class cd(Command): else: _, dirnames, _ = next(os.walk(abs_dirname)) dirnames = [dn for dn in dirnames - if dn.startswith(rel_basename)] + if dn.startswith(rel_basename)] except (OSError, StopIteration): # os.walk found nothing pass @@ -177,7 +182,7 @@ class cd(Command): dirnames = bookmarks + dirnames # no results, return None - if len(dirnames) == 0: + if not dirnames: return # one result. since it must be a directory, append a slash. @@ -194,6 +199,7 @@ class chain(Command): Calls multiple commands at once, separated by semicolons. """ + def execute(self): for command in [s.strip() for s in self.rest(1).split(";")]: self.fm.execute_console(command) @@ -230,28 +236,28 @@ class shell(Command): selection = self.fm.thistab.get_selection() if len(selection) == 1: return self.line + selection[0].shell_escaped_basename + ' ' - else: - return self.line + '%s ' - else: - before_word, start_of_word = self.line.rsplit(' ', 1) - return (before_word + ' ' + file.shell_escaped_basename - for file in self.fm.thisdir.files or [] - if file.shell_escaped_basename.startswith(start_of_word)) + return self.line + '%s ' + + before_word, start_of_word = self.line.rsplit(' ', 1) + return (before_word + ' ' + file.shell_escaped_basename + for file in self.fm.thisdir.files or [] + if file.shell_escaped_basename.startswith(start_of_word)) class open_with(Command): + def execute(self): app, flags, mode = self._get_app_flags_mode(self.rest(1)) self.fm.execute_file( - files=[f for f in self.fm.thistab.get_selection()], - app=app, - flags=flags, - mode=mode) + files=[f for f in self.fm.thistab.get_selection()], + app=app, + flags=flags, + mode=mode) def tab(self, tabnum): return self._tab_through_executables() - def _get_app_flags_mode(self, string): + def _get_app_flags_mode(self, string): # pylint: disable=too-many-branches,too-many-statements """Extracts the application, flags and mode from a string. examples: @@ -266,10 +272,7 @@ class open_with(Command): mode = 0 split = string.split() - if len(split) == 0: - pass - - elif len(split) == 1: + if len(split) == 1: part = split[0] if self._is_app(part): app = part @@ -326,11 +329,13 @@ class open_with(Command): def _is_app(self, arg): return not self._is_flags(arg) and not arg.isdigit() - def _is_flags(self, arg): + @staticmethod + def _is_flags(arg): from ranger.core.runner import ALLOWED_FLAGS return all(x in ALLOWED_FLAGS for x in arg) - def _is_mode(self, arg): + @staticmethod + def _is_mode(arg): return all(x in '0123456789' for x in arg) @@ -351,7 +356,7 @@ class set_(Command): else: self.fm.set_option_from_string(name, value) - def tab(self, tabnum): + def tab(self, tabnum): # pylint: disable=too-many-return-statements from ranger.gui.colorscheme import get_all_colorschemes name, value, name_done = self.parse_setting_line() settings = self.fm.settings @@ -359,12 +364,12 @@ class set_(Command): return sorted(self.firstpart + setting for setting in settings) if not value and not name_done: return sorted(self.firstpart + setting for setting in settings - if setting.startswith(name)) + if setting.startswith(name)) if not value: # Cycle through colorschemes when name, but no value is specified if name == "colorscheme": return sorted(self.firstpart + colorscheme for colorscheme - in get_all_colorschemes()) + in get_all_colorschemes()) return self.firstpart + str(settings[name]) if bool in settings.types_of(name): if 'true'.startswith(value.lower()): @@ -374,7 +379,7 @@ class set_(Command): # Tab complete colorscheme values if incomplete value is present if name == "colorscheme": return sorted(self.firstpart + colorscheme for colorscheme - in get_all_colorschemes() if colorscheme.startswith(value)) + in get_all_colorschemes() if colorscheme.startswith(value)) class setlocal(set_): @@ -385,7 +390,6 @@ class setlocal(set_): PATH_RE = re.compile(r'^\s*path="?(.*?)"?\s*$') def execute(self): - import os.path match = self.PATH_RE.match(self.arg(1)) if match: path = os.path.normpath(os.path.expanduser(match.group(1))) @@ -406,6 +410,7 @@ class setintag(setlocal): Sets an option for directories that are tagged with a specific tag. """ + def execute(self): tags = self.arg(1) self.shift() @@ -414,12 +419,13 @@ class setintag(setlocal): class default_linemode(Command): + def execute(self): - import re from ranger.container.fsobject import FileSystemObject if len(self.args) < 2: - self.fm.notify("Usage: default_linemode [path=<regexp> | tag=<tag(s)>] <linemode>", bad=True) + self.fm.notify( + "Usage: default_linemode [path=<regexp> | tag=<tag(s)>] <linemode>", bad=True) # Extract options like "path=..." or "tag=..." from the command line arg1 = self.arg(1) @@ -435,13 +441,16 @@ class default_linemode(Command): self.shift() # Extract and validate the line mode from the command line - linemode = self.rest(1) - if linemode not in FileSystemObject.linemode_dict: - self.fm.notify("Invalid linemode: %s; should be %s" % - (linemode, "/".join(FileSystemObject.linemode_dict)), bad=True) + lmode = self.rest(1) + if lmode not in FileSystemObject.linemode_dict: + self.fm.notify( + "Invalid linemode: %s; should be %s" % ( + lmode, "/".join(FileSystemObject.linemode_dict)), + bad=True, + ) # Add the prepared entry to the fm.default_linemodes - entry = [method, argument, linemode] + entry = [method, argument, lmode] self.fm.default_linemodes.appendleft(entry) # Redraw the columns @@ -450,13 +459,12 @@ class default_linemode(Command): col.need_redraw = True def tab(self, tabnum): - mode = self.arg(1) - return (self.arg(0) + " " + linemode - for linemode in self.fm.thisfile.linemode_dict.keys() - if linemode.startswith(self.arg(1))) + return (self.arg(0) + " " + lmode + for lmode in self.fm.thisfile.linemode_dict.keys() + if lmode.startswith(self.arg(1))) -class quit(Command): +class quit(Command): # pylint: disable=redefined-builtin """:quit Closes the current tab. If there is only one tab, quit the program. @@ -492,6 +500,7 @@ class terminal(Command): Spawns an "x-terminal-emulator" starting in the current directory. """ + def execute(self): from ranger.ext.get_executables import get_term self.fm.run(get_term(), flags='f') @@ -515,36 +524,33 @@ class delete(Command): escape_macros_for_shell = True def execute(self): - import os import shlex from functools import partial - from ranger.container.file import File - def is_directory_with_files(f): - import os.path - return (os.path.isdir(f) and not os.path.islink(f) - and len(os.listdir(f)) > 0) + def is_directory_with_files(path): + return os.path.isdir(path) and not os.path.islink(path) and len(os.listdir(path)) > 0 if self.rest(1): files = shlex.split(self.rest(1)) many_files = (len(files) > 1 or is_directory_with_files(files[0])) else: cwd = self.fm.thisdir - cf = self.fm.thisfile - if not cwd or not cf: + tfile = self.fm.thisfile + if not cwd or not tfile: self.fm.notify("Error: no file selected for deletion!", bad=True) return # relative_path used for a user-friendly output in the confirmation. files = [f.relative_path for f in self.fm.thistab.get_selection()] - many_files = (cwd.marked_items or is_directory_with_files(cf.path)) + many_files = (cwd.marked_items or is_directory_with_files(tfile.path)) confirm = self.fm.settings.confirm_on_delete if confirm != 'never' and (confirm != 'multiple' or many_files): - filename_list = files - self.fm.ui.console.ask("Confirm deletion of: %s (y/N)" % - ', '.join(files), - partial(self._question_callback, files), ('n', 'N', 'y', 'Y')) + self.fm.ui.console.ask( + "Confirm deletion of: %s (y/N)" % ', '.join(files), + partial(self._question_callback, files), + ('n', 'N', 'y', 'Y'), + ) else: # no need for a confirmation, just delete self.fm.delete(files) @@ -586,6 +592,7 @@ class console(Command): Open the console with the given command. """ + def execute(self): position = None if self.arg(1)[0:2] == '-p': @@ -609,13 +616,13 @@ class load_copy_buffer(Command): from os.path import exists try: fname = self.fm.confpath(self.copy_buffer_filename) - f = open(fname, 'r') + fobj = open(fname, 'r') except Exception: - return self.fm.notify("Cannot open %s" % - (fname or self.copy_buffer_filename), bad=True) + return self.fm.notify( + "Cannot open %s" % (fname or self.copy_buffer_filename), bad=True) self.fm.copy_buffer = set(File(g) - for g in f.read().split("\n") if exists(g)) - f.close() + for g in fobj.read().split("\n") if exists(g)) + fobj.close() self.fm.ui.redraw_main_column() @@ -630,12 +637,12 @@ class save_copy_buffer(Command): fname = None try: fname = self.fm.confpath(self.copy_buffer_filename) - f = open(fname, 'w') + fobj = open(fname, 'w') except Exception: return self.fm.notify("Cannot open %s" % - (fname or self.copy_buffer_filename), bad=True) - f.write("\n".join(f.path for f in self.fm.copy_buffer)) - f.close() + (fname or self.copy_buffer_filename), bad=True) + fobj.write("\n".join(fobj.path for fobj in self.fm.copy_buffer)) + fobj.close() class unmark_tag(mark_tag): @@ -724,17 +731,16 @@ class eval_(Command): else: code = self.rest(1) quiet = False - import ranger - global cmd, fm, p, quantifier + global cmd, fm, p, quantifier # pylint: disable=invalid-name,global-variable-undefined fm = self.fm cmd = self.fm.execute_console p = fm.notify quantifier = self.quantifier try: try: - result = eval(code) + result = eval(code) # pylint: disable=eval-used except SyntaxError: - exec(code) + exec(code) # pylint: disable=exec-used else: if result and not quiet: p(result) @@ -756,10 +762,10 @@ class rename(Command): tagged = {} old_name = self.fm.thisfile.relative_path - for f in self.fm.tags.tags: - if str(f).startswith(self.fm.thisfile.path): - tagged[f] = self.fm.tags.tags[f] - self.fm.tags.remove(f) + for fobj in self.fm.tags.tags: + if str(fobj).startswith(self.fm.thisfile.path): + tagged[fobj] = self.fm.tags.tags[fobj] + self.fm.tags.remove(fobj) if not new_name: return self.fm.notify('Syntax: rename <newname>', bad=True) @@ -771,19 +777,20 @@ class rename(Command): return self.fm.notify("Can't rename: file already exists!", bad=True) if self.fm.rename(self.fm.thisfile, new_name): - f = File(new_name) + fobj = File(new_name) # Update bookmarks that were pointing on the previous name obsoletebookmarks = [b for b in self.fm.bookmarks if b[1].path == self.fm.thisfile] if obsoletebookmarks: for key, _ in obsoletebookmarks: - self.fm.bookmarks[key] = f + self.fm.bookmarks[key] = fobj self.fm.bookmarks.update_if_outdated() - self.fm.thisdir.pointed_obj = f - self.fm.thisfile = f - for t in tagged: - self.fm.tags.tags[t.replace(old_name, new_name)] = tagged[t] + self.fm.thisdir.pointed_obj = fobj + self.fm.thisfile = fobj + + for fobj in tagged: + self.fm.tags.tags[fobj.replace(old_name, new_name)] = tagged[fobj] self.fm.tags.dump() def tab(self, tabnum): @@ -793,13 +800,14 @@ class rename(Command): class rename_append(Command): """:rename_append - Creates an open_console for the rename command, automatically placing the cursor before the file extension. + Creates an open_console for the rename command, automatically placing + the cursor before the file extension. """ def execute(self): - cf = self.fm.thisfile - path = cf.relative_path.replace("%", "%%") - if path.find('.') != 0 and path.rfind('.') != -1 and not cf.is_directory: + tfile = self.fm.thisfile + path = tfile.relative_path.replace("%", "%%") + if path.find('.') != 0 and path.rfind('.') != -1 and not tfile.is_directory: self.fm.open_console('rename ' + path, position=(7 + path.rfind('.'))) else: self.fm.open_console('rename ' + path) @@ -818,21 +826,21 @@ class chmod(Command): """ def execute(self): - mode = self.rest(1) - if not mode: - mode = str(self.quantifier) + mode_str = self.rest(1) + if not mode_str: + mode_str = str(self.quantifier) try: - mode = int(mode, 8) + mode = int(mode_str, 8) if mode < 0 or mode > 0o777: raise ValueError except ValueError: self.fm.notify("Need an octal number between 0 and 777!", bad=True) return - for file in self.fm.thistab.get_selection(): + for fobj in self.fm.thistab.get_selection(): try: - os.chmod(file.path, mode) + os.chmod(fobj.path, mode) except Exception as ex: self.fm.notify(ex) @@ -854,7 +862,8 @@ class bulkrename(Command): This shell script is opened in an editor for you to review. After you close it, it will be executed. """ - def execute(self): + + def execute(self): # pylint: disable=too-many-locals,too-many-statements import sys import tempfile from ranger.container.file import File @@ -886,7 +895,7 @@ class bulkrename(Command): script_lines.append("# This file will be executed when you close the editor.\n") script_lines.append("# Please double-check everything, clear the file to abort.\n") script_lines.extend("mv -vi -- %s %s\n" % (esc(old), esc(new)) - for old, new in zip(filenames, new_filenames) if old != new) + for old, new in zip(filenames, new_filenames) if old != new) script_content = "".join(script_lines) if py3: cmdfile.write(script_content.encode("utf-8")) @@ -930,35 +939,32 @@ class relink(Command): """ def execute(self): - from ranger.container.file import File - new_path = self.rest(1) - cf = self.fm.thisfile + tfile = self.fm.thisfile if not new_path: return self.fm.notify('Syntax: relink <newpath>', bad=True) - if not cf.is_link: - return self.fm.notify('%s is not a symlink!' % cf.relative_path, bad=True) + if not tfile.is_link: + return self.fm.notify('%s is not a symlink!' % tfile.relative_path, bad=True) - if new_path == os.readlink(cf.path): + if new_path == os.readlink(tfile.path): return try: - os.remove(cf.path) - os.symlink(new_path, cf.path) + os.remove(tfile.path) + os.symlink(new_path, tfile.path) except OSError as err: self.fm.notify(err) self.fm.reset() - self.fm.thisdir.pointed_obj = cf - self.fm.thisfile = cf + self.fm.thisdir.pointed_obj = tfile + self.fm.thisfile = tfile def tab(self, tabnum): if not self.rest(1): return self.line + os.readlink(self.fm.thisfile.path) - else: - return self._tab_directory_content() + return self._tab_directory_content() class help_(Command): @@ -981,8 +987,11 @@ class help_(Command): elif answer == "s": self.fm.dump_settings() - c = self.fm.ui.console.ask("View [m]an page, [k]ey bindings," - " [c]ommands or [s]ettings? (press q to abort)", callback, list("mkcsq") + [chr(27)]) + self.fm.ui.console.ask( + "View [m]an page, [k]ey bindings, [c]ommands or [s]ettings? (press q to abort)", + callback, + list("mkcsq") + [chr(27)] + ) class copymap(Command): @@ -1133,32 +1142,34 @@ class scout(Command): Multiple flags can be combined. For example, ":scout -gpt" would create a :filter-like command using globbing. """ - AUTO_OPEN = 'a' - OPEN_ON_ENTER = 'e' - FILTER = 'f' - SM_GLOB = 'g' - IGNORE_CASE = 'i' - KEEP_OPEN = 'k' - SM_LETTERSKIP = 'l' - MARK = 'm' - UNMARK = 'M' - PERM_FILTER = 'p' - SM_REGEX = 'r' - SMART_CASE = 's' - AS_YOU_TYPE = 't' - INVERT = 'v' + # pylint: disable=bad-whitespace + AUTO_OPEN = 'a' + OPEN_ON_ENTER = 'e' + FILTER = 'f' + SM_GLOB = 'g' + IGNORE_CASE = 'i' + KEEP_OPEN = 'k' + SM_LETTERSKIP = 'l' + MARK = 'm' + UNMARK = 'M' + PERM_FILTER = 'p' + SM_REGEX = 'r' + SMART_CASE = 's' + AS_YOU_TYPE = 't' + INVERT = 'v' + # pylint: enable=bad-whitespace def __init__(self, *args, **kws): Command.__init__(self, *args, **kws) self._regex = None self.flags, self.pattern = self.parse_flags() - def execute(self): + def execute(self): # pylint: disable=too-many-branches thisdir = self.fm.thisdir - flags = self.flags + flags = self.flags pattern = self.pattern - regex = self._build_regex() - count = self._count(move=True) + regex = self._build_regex() + count = self._count(move=True) self.fm.thistab.last_search = regex self.fm.set_search_method(order="search") @@ -1166,12 +1177,12 @@ class scout(Command): if (self.MARK in flags or self.UNMARK in flags) and thisdir.files: value = flags.find(self.MARK) > flags.find(self.UNMARK) if self.FILTER in flags: - for f in thisdir.files: - thisdir.mark_item(f, value) + for fobj in thisdir.files: + thisdir.mark_item(fobj, value) else: - for f in thisdir.files: - if regex.search(f.relative_path): - thisdir.mark_item(f, value) + for fobj in thisdir.files: + if regex.search(fobj.relative_path): + thisdir.mark_item(fobj, value) if self.PERM_FILTER in flags: thisdir.filter = regex if pattern else None @@ -1219,8 +1230,8 @@ class scout(Command): if self._regex is not None: return self._regex - frmat = "%s" - flags = self.flags + frmat = "%s" + flags = self.flags pattern = self.pattern if pattern == ".": @@ -1251,10 +1262,12 @@ class scout(Command): regex = "^(?:(?!%s).)*$" % regex # Compile Regular Expression + # pylint: disable=no-member options = re.UNICODE if self.IGNORE_CASE in flags or self.SMART_CASE in flags and \ pattern.islower(): options |= re.IGNORECASE + # pylint: enable=no-member try: self._regex = re.compile(regex, options) except Exception: @@ -1262,8 +1275,8 @@ class scout(Command): return self._regex def _count(self, move=False, offset=0): - count = 0 - cwd = self.fm.thisdir + count = 0 + cwd = self.fm.thisdir pattern = self.pattern if not pattern or not cwd.files: @@ -1301,19 +1314,22 @@ class filter_inode_type(Command): f display files l display links """ - + # pylint: disable=bad-whitespace FILTER_DIRS = 'd' FILTER_FILES = 'f' FILTER_LINKS = 'l' + # pylint: enable=bad-whitespace def execute(self): if not self.arg(1): self.fm.thisdir.inode_type_filter = None else: self.fm.thisdir.inode_type_filter = lambda file: ( - True if ((self.FILTER_DIRS in self.arg(1) and file.is_directory) or - (self.FILTER_FILES in self.arg(1) and file.is_file and not file.is_link) or - (self.FILTER_LINKS in self.arg(1) and file.is_link)) else False) + True if ( + (self.FILTER_DIRS in self.arg(1) and file.is_directory) or + (self.FILTER_FILES in self.arg(1) and file.is_file and not file.is_link) or + (self.FILTER_LINKS in self.arg(1) and file.is_link) + ) else False) self.fm.thisdir.refilter() @@ -1343,8 +1359,8 @@ class flat(Command): def execute(self): try: - level = self.rest(1) - level = int(level) + level_str = self.rest(1) + level = int(level_str) except ValueError: level = self.quantifier if level < -1: @@ -1363,6 +1379,7 @@ class stage(Command): Stage selected files for the corresponding version control system """ + def execute(self): from ranger.ext.vcs import VcsError @@ -1383,6 +1400,7 @@ class unstage(Command): Unstage selected files for the corresponding version control system """ + def execute(self): from ranger.ext.vcs import VcsError @@ -1441,12 +1459,11 @@ class meta(prompt_metadata): def execute(self): key = self.arg(1) - value = self.rest(1) update_dict = dict() update_dict[key] = self.rest(2) selection = self.fm.thistab.get_selection() - for f in selection: - self.fm.metadata.set_metadata(f.path, update_dict) + for fobj in selection: + self.fm.metadata.set_metadata(fobj.path, update_dict) self._process_command_stack() def tab(self, tabnum): @@ -1454,9 +1471,8 @@ class meta(prompt_metadata): metadata = self.fm.metadata.get_metadata(self.fm.thisfile.path) if key in metadata and metadata[key]: return [" ".join([self.arg(0), self.arg(1), metadata[key]])] - else: - return [self.arg(0) + " " + key for key in sorted(metadata) - if key.startswith(self.arg(1))] + return [self.arg(0) + " " + k for k in sorted(metadata) + if k.startswith(self.arg(1))] class linemode(default_linemode): @@ -1473,13 +1489,14 @@ class linemode(default_linemode): mode = self.arg(1) if mode == "normal": + from ranger.core.linemode import DEFAULT_LINEMODE mode = DEFAULT_LINEMODE if mode not in self.fm.thisfile.linemode_dict: self.fm.notify("Unhandled linemode: `%s'" % mode, bad=True) return - self.fm.thisdir._set_linemode_of_children(mode) + self.fm.thisdir._set_linemode_of_children(mode) # pylint: disable=protected-access # Ask the browsercolumns to redraw for col in self.fm.ui.browser.columns: diff --git a/ranger/config/commands_sample.py b/ranger/config/commands_sample.py index 1386e84e..8d0e6256 100644 --- a/ranger/config/commands_sample.py +++ b/ranger/config/commands_sample.py @@ -4,19 +4,20 @@ # documentation. Do NOT add them all here, or you may end up with defunct # commands when upgrading ranger. -# You always need to import ranger.api.commands here to get the Command class: -from ranger.api.commands import * - # A simple command for demonstration purposes follows. # ----------------------------------------------------------------------------- +from __future__ import (absolute_import, print_function) + # You can import any python module as needed. import os -# Any class that is a subclass of "Command" will be integrated into ranger as a -# command. Try typing ":my_edit<ENTER>" in ranger! +# You always need to import ranger.api.commands here to get the Command class: +from ranger.api.commands import Command +# Any class that is a subclass of "Command" will be integrated into ranger as a +# command. Try typing ":my_edit<ENTER>" in ranger! class my_edit(Command): # The so-called doc-string of the class will be visible in the built-in # help that is accessible by typing "?c" inside ranger. diff --git a/ranger/config/pylintrc b/ranger/config/pylintrc new file mode 100644 index 00000000..adec41f1 --- /dev/null +++ b/ranger/config/pylintrc @@ -0,0 +1,8 @@ +[BASIC] +good-names=i,j,k,n,ex,Run,_,fm,ui,fg,bg +class-rgx=[a-z][a-z0-9_]{1,30}$ + +[FORMAT] +max-line-length = 99 +max-module-lines=3000 +disable=locally-disabled,locally-enabled,missing-docstring,duplicate-code,fixme,broad-except diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py index cb15efee..964efcae 100644 --- a/ranger/container/bookmarks.py +++ b/ranger/container/bookmarks.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import string import re import os @@ -115,7 +117,7 @@ class Bookmarks(object): except OSError: return - for key in set(self.dct.keys()) | set(real_dict.keys()): + for key in set(self.dct) | set(real_dict): # set some variables if key in self.dct: current = self.dct[key] @@ -151,16 +153,16 @@ class Bookmarks(object): if self.path is None: return if os.access(self.path, os.W_OK): - f = open(self.path + ".new", 'w') + fobj = open(self.path + ".new", 'w') for key, value in self.dct.items(): - if type(key) == str\ + if isinstance(key, str)\ and key in ALLOWED_KEYS: try: - f.write("{0}:{1}\n".format(str(key), str(value))) + fobj.write("{0}:{1}\n".format(str(key), str(value))) except Exception: pass - f.close() + fobj.close() old_perms = os.stat(self.path) try: os.chown(self.path + ".new", old_perms.st_uid, old_perms.st_gid) @@ -178,19 +180,19 @@ class Bookmarks(object): if not os.path.exists(self.path): try: - f = open(self.path, 'w') + fobj = open(self.path, 'w') except Exception: raise OSError('Cannot read the given path') - f.close() + fobj.close() if os.access(self.path, os.R_OK): - f = open(self.path, 'r') - for line in f: + fobj = open(self.path, 'r') + for line in fobj: if self.load_pattern.match(line): key, value = line[0], line[2:-1] if key in ALLOWED_KEYS: dct[key] = self.bookmarktype(value) - f.close() + fobj.close() return dct else: raise OSError('Cannot read the given path') diff --git a/ranger/container/directory.py b/ranger/container/directory.py index f46f9d55..6c34641e 100644 --- a/ranger/container/directory.py +++ b/ranger/container/directory.py @@ -1,12 +1,13 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import locale import os.path +from os import stat as os_stat, lstat as os_lstat import random import re - -from os import stat as os_stat, lstat as os_lstat from collections import deque from time import time @@ -56,17 +57,17 @@ def sort_unicode_wrapper_list(old_sort_func): return sort_unicode -def accept_file(file, filters): +def accept_file(fobj, filters): """ Returns True if file shall be shown, otherwise False. Parameters: - file - an instance of FileSystemObject - filters - an array of lambdas, each expects a file and - returns True if file shall be shown, + fobj - an instance of FileSystemObject + filters - an array of lambdas, each expects a fobj and + returns True if fobj shall be shown, otherwise False. """ - for filter in filters: - if filter and not filter(file): + for filt in filters: + if filt and not filt(fobj): return False return True @@ -85,14 +86,15 @@ def walklevel(some_dir, level): def mtimelevel(path, level): mtime = os.stat(path).st_mtime - for dirpath, dirnames, filenames in walklevel(path, level): + for dirpath, dirnames, _ in walklevel(path, level): dirlist = [os.path.join("/", dirpath, d) for d in dirnames - if level == -1 or dirpath.count(os.path.sep) - path.count(os.path.sep) <= level] + if level == -1 or dirpath.count(os.path.sep) - path.count(os.path.sep) <= level] mtime = max(mtime, max([-1] + [os.stat(d).st_mtime for d in dirlist])) return mtime -class Directory(FileSystemObject, Accumulator, Loadable): +class Directory( # pylint: disable=too-many-instance-attributes,too-many-public-methods + FileSystemObject, Accumulator, Loadable): is_directory = True enterable = False load_generator = None @@ -146,14 +148,13 @@ class Directory(FileSystemObject, Accumulator, Loadable): self.marked_items = list() - for opt in ('sort_directories_first', 'sort', 'sort_reverse', - 'sort_case_insensitive'): - self.settings.signal_bind('setopt.' + opt, - self.request_resort, weak=True, autosort=False) + for opt in ('sort_directories_first', 'sort', 'sort_reverse', 'sort_case_insensitive'): + self.settings.signal_bind('setopt.' + opt, self.request_resort, + weak=True, autosort=False) for opt in ('hidden_filter', 'show_hidden'): - self.settings.signal_bind('setopt.' + opt, - self.refilter, weak=True, autosort=False) + self.settings.signal_bind('setopt.' + opt, self.refilter, + weak=True, autosort=False) self.settings = LocalSettings(path, self.settings) @@ -172,7 +173,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): return self.files def mark_item(self, item, val): - item._mark(val) + item._mark(val) # pylint: disable=protected-access if val: if item in self.files and item not in self.marked_items: self.marked_items.append(item) @@ -207,7 +208,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): def _clear_marked_items(self): for item in self.marked_items: - item._mark(False) + item._mark(False) # pylint: disable=protected-access del self.marked_items[:] def get_selection(self): @@ -215,12 +216,13 @@ class Directory(FileSystemObject, Accumulator, Loadable): self._gc_marked_items() if not self.files: return [] + if self.marked_items: return [item for item in self.files if item.marked] elif self.pointed_obj: return [self.pointed_obj] - else: - return [] + + return [] def refilter(self): if self.files_all is None: @@ -233,15 +235,15 @@ class Directory(FileSystemObject, Accumulator, Loadable): if not self.settings.show_hidden and self.settings.hidden_filter: hidden_filter = re.compile(self.settings.hidden_filter) hidden_filter_search = hidden_filter.search - filters.append(lambda file: not hidden_filter_search(file.basename)) + filters.append(lambda fobj: not hidden_filter_search(fobj.basename)) if self.filter: filter_search = self.filter.search - filters.append(lambda file: filter_search(file.basename)) + filters.append(lambda fobj: filter_search(fobj.basename)) if self.inode_type_filter: filters.append(self.inode_type_filter) if self.temporary_filter: temporary_filter_search = self.temporary_filter.search - filters.append(lambda file: temporary_filter_search(file.basename)) + filters.append(lambda fobj: temporary_filter_search(fobj.basename)) self.files = [f for f in self.files_all if accept_file(f, filters)] @@ -256,6 +258,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): self.move_to_obj(self.pointed_obj) # XXX: Check for possible race conditions + # pylint: disable=too-many-locals,too-many-branches,too-many-statements def load_bit_by_bit(self): """An iterator that loads a part on every next() call @@ -269,7 +272,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): basename_is_rel_to = self.path if self.flat else None - try: + try: # pylint: disable=too-many-nested-blocks if self.runnable: yield mypath = self.path @@ -279,8 +282,12 @@ class Directory(FileSystemObject, Accumulator, Loadable): if self.flat: filelist = [] for dirpath, dirnames, filenames in walklevel(mypath, self.flat): - dirlist = [os.path.join("/", dirpath, d) for d in dirnames - if self.flat == -1 or dirpath.count(os.path.sep) - mypath.count(os.path.sep) <= self.flat] + dirlist = [ + os.path.join("/", dirpath, d) + for d in dirnames + if self.flat == -1 or + (dirpath.count(os.path.sep) - mypath.count(os.path.sep)) <= self.flat + ] filelist += dirlist filelist += [os.path.join("/", dirpath, f) for f in filenames] filenames = filelist @@ -288,7 +295,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): else: filelist = os.listdir(mypath) filenames = [mypath + (mypath == '/' and fname or '/' + fname) - for fname in filelist] + for fname in filelist] self.load_content_mtime = os.stat(mypath).st_mtime if self._cumulative_size_calculated: @@ -347,16 +354,20 @@ class Directory(FileSystemObject, Accumulator, Loadable): if item.vcs.is_root_pointer: has_vcschild = True else: - item.vcsstatus = item.vcs.rootvcs.status_subpath( - os.path.join(self.realpath, item.basename), is_directory=True) + item.vcsstatus = \ + item.vcs.rootvcs.status_subpath( # pylint: disable=no-member + os.path.join(self.realpath, item.basename), + is_directory=True, + ) else: item = File(name, preload=stats, path_is_abs=True, basename_is_rel_to=basename_is_rel_to) item.load() disk_usage += item.size if self.vcs and self.vcs.track: - item.vcsstatus = self.vcs.rootvcs.status_subpath( - os.path.join(self.realpath, item.basename)) + item.vcsstatus = \ + self.vcs.rootvcs.status_subpath( # pylint: disable=no-member + os.path.join(self.realpath, item.basename)) files.append(item) self.percent = 100 * len(files) // len(filenames) @@ -370,10 +381,10 @@ class Directory(FileSystemObject, Accumulator, Loadable): self._clear_marked_items() for item in self.files_all: if item.path in marked_paths: - item._mark(True) + item._mark(True) # pylint: disable=protected-access self.marked_items.append(item) else: - item._mark(False) + item._mark(False) # pylint: disable=protected-access self.sort() @@ -397,6 +408,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): self.fm.signal_emit("finished_loading_dir", directory=self) if self.vcs: self.fm.ui.vcsthread.process(self) + # pylint: enable=too-many-locals,too-many-branches,too-many-statements def unload(self): self.loading = False @@ -475,14 +487,13 @@ class Directory(FileSystemObject, Accumulator, Loadable): return 0 cum = 0 realpath = os.path.realpath - for dirpath, dirnames, filenames in os.walk(self.path, - onerror=lambda _: None): - for file in filenames: + for dirpath, _, filenames in os.walk(self.path, onerror=lambda _: None): + for fname in filenames: try: if dirpath == self.path: - stat = os_stat(realpath(dirpath + "/" + file)) + stat = os_stat(realpath(dirpath + "/" + fname)) else: - stat = os_stat(dirpath + "/" + file) + stat = os_stat(dirpath + "/" + fname) cum += stat.st_size except Exception: pass @@ -491,11 +502,10 @@ class Directory(FileSystemObject, Accumulator, Loadable): def look_up_cumulative_size(self): self._cumulative_size_calculated = True self.size = self._get_cumulative_size() - self.infostring = ('-> ' if self.is_link else ' ') + \ - human_readable(self.size) + self.infostring = ('-> ' if self.is_link else ' ') + human_readable(self.size) @lazy_property - def size(self): + def size(self): # pylint: disable=method-hidden try: if self.fm.settings.automatically_count_files: size = len(os.listdir(self.path)) @@ -516,15 +526,15 @@ class Directory(FileSystemObject, Accumulator, Loadable): return size @lazy_property - def infostring(self): - self.size # trigger the lazy property initializer + def infostring(self): # pylint: disable=method-hidden + self.size # trigger the lazy property initializer pylint: disable=pointless-statement if self.is_link: return '->' + self.infostring return self.infostring @lazy_property - def runnable(self): - self.size # trigger the lazy property initializer + def runnable(self): # pylint: disable=method-hidden + self.size # trigger the lazy property initializer pylint: disable=pointless-statement return self.runnable def sort_if_outdated(self): @@ -535,7 +545,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): return True return False - def move_to_obj(self, arg): + def move_to_obj(self, arg, attr=None): try: arg = arg.path except Exception: @@ -554,10 +564,10 @@ class Directory(FileSystemObject, Accumulator, Loadable): if forward: generator = ((self.pointer + (x + offset)) % length - for x in range(length - 1)) + for x in range(length - 1)) else: generator = ((self.pointer - (x + offset)) % length - for x in range(length - 1)) + for x in range(length - 1)) for i in generator: _file = self.files[i] @@ -638,7 +648,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): return True return self.last_used + seconds < time() - def go(self, history=True): + def go(self, history=True): # pylint: disable=invalid-name """enter the directory if the filemanager is running""" if self.fm: return self.fm.enter_dir(self.path, history=history) @@ -649,8 +659,8 @@ class Directory(FileSystemObject, Accumulator, Loadable): return self.files is None or len(self.files) == 0 def _set_linemode_of_children(self, mode): - for f in self.files: - f._set_linemode(mode) + for fobj in self.files: + fobj._set_linemode(mode) # pylint: disable=protected-access def __nonzero__(self): """Always True""" diff --git a/ranger/container/file.py b/ranger/container/file.py index 3058b9db..f98be1d7 100644 --- a/ranger/container/file.py +++ b/ranger/container/file.py @@ -1,12 +1,15 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import re from ranger.container.fsobject import FileSystemObject N_FIRST_BYTES = 256 -control_characters = set(chr(n) for n in - set(range(0, 9)) | set(range(14, 32))) +# pylint: disable=invalid-name +control_characters = set(chr(n) for n in set(range(0, 9)) | set(range(14, 32))) +# pylint: enable=invalid-name # Don't even try to preview files which match this regular expression: PREVIEW_BLACKLIST = re.compile(r""" @@ -25,7 +28,7 @@ PREVIEW_BLACKLIST = re.compile(r""" # ignore fully numerical file extensions: (\.\d+)*? $ -""", re.VERBOSE | re.IGNORECASE) +""", re.VERBOSE | re.IGNORECASE) # pylint: disable=no-member # Preview these files (almost) always: PREVIEW_WHITELIST = re.compile(r""" @@ -35,7 +38,7 @@ PREVIEW_WHITELIST = re.compile(r""" # ignore filetype-independent suffixes: (\.part|\.bak|~)? $ -""", re.VERBOSE | re.IGNORECASE) +""", re.VERBOSE | re.IGNORECASE) # pylint: disable=no-member class File(FileSystemObject): @@ -45,26 +48,27 @@ class File(FileSystemObject): preview_loading = False _linemode = "filename" + _firstbytes = None @property def firstbytes(self): - try: - return self._firstbytes - except Exception: + if self._firstbytes is None: try: - f = open(self.path, 'r') - self._firstbytes = f.read(N_FIRST_BYTES) - f.close() + fobj = open(self.path, 'r') + self._firstbytes = fobj.read(N_FIRST_BYTES) + fobj.close() return self._firstbytes except Exception: pass + else: + return self._firstbytes def is_binary(self): if self.firstbytes and control_characters & set(self.firstbytes): return True return False - def has_preview(self): + def has_preview(self): # pylint: disable=too-many-return-statements if not self.fm.settings.preview_files: return False if self.is_socket or self.is_fifo or self.is_device: diff --git a/ranger/container/fsobject.py b/ranger/container/fsobject.py index 9b312e6d..36817ff4 100644 --- a/ranger/container/fsobject.py +++ b/ranger/container/fsobject.py @@ -1,23 +1,17 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. -CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio', - 'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar', - 'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip') -DOCUMENT_EXTENSIONS = ('cfg', 'css', 'cvs', 'djvu', 'doc', 'docx', 'gnm', - 'gnumeric', 'htm', 'html', 'md', 'odf', 'odg', 'odp', 'ods', 'odt', 'pdf', - 'pod', 'ps', 'rtf', 'sxc', 'txt', 'xls', 'xlw', 'xml', 'xslx') -DOCUMENT_BASENAMES = ('bugs', 'bugs', 'changelog', 'copying', 'credits', - 'hacking', 'help', 'install', 'license', 'readme', 'todo') - -BAD_INFO = '?' +from __future__ import (absolute_import, print_function) import re from grp import getgrgid -from os import lstat, stat, getcwd +from os import lstat, stat from os.path import abspath, basename, dirname, realpath, splitext, extsep, relpath from pwd import getpwuid -from ranger.core.linemode import * +from ranger.core.linemode import ( + DEFAULT_LINEMODE, DefaultLinemode, TitleLinemode, + PermissionsLinemode, FileInfoLinemode, MtimeLinemode, SizeMtimeLinemode, +) from ranger.core.shared import FileManagerAware, SettingsAware from ranger.ext.shell_escape import shell_escape from ranger.ext import spawn @@ -25,54 +19,71 @@ from ranger.ext.lazy_property import lazy_property from ranger.ext.human_readable import human_readable if hasattr(str, 'maketrans'): - maketrans = str.maketrans + maketrans = str.maketrans # pylint: disable=invalid-name,no-member else: - from string import maketrans + from string import maketrans # pylint: disable=no-name-in-module + + +CONTAINER_EXTENSIONS = ('7z', 'ace', 'ar', 'arc', 'bz', 'bz2', 'cab', 'cpio', + 'cpt', 'deb', 'dgc', 'dmg', 'gz', 'iso', 'jar', 'msi', 'pkg', 'rar', + 'shar', 'tar', 'tbz', 'tgz', 'xar', 'xpi', 'xz', 'zip') +DOCUMENT_EXTENSIONS = ('cfg', 'css', 'cvs', 'djvu', 'doc', 'docx', 'gnm', + 'gnumeric', 'htm', 'html', 'md', 'odf', 'odg', 'odp', 'ods', 'odt', 'pdf', + 'pod', 'ps', 'rtf', 'sxc', 'txt', 'xls', 'xlw', 'xml', 'xslx') +DOCUMENT_BASENAMES = ('bugs', 'bugs', 'changelog', 'copying', 'credits', + 'hacking', 'help', 'install', 'license', 'readme', 'todo') + +BAD_INFO = '?' + + +# pylint: disable=invalid-name _unsafe_chars = '\n' + ''.join(map(chr, range(32))) + ''.join(map(chr, range(128, 256))) _safe_string_table = maketrans(_unsafe_chars, '?' * len(_unsafe_chars)) _extract_number_re = re.compile(r'(\d+|\D)') _integers = set("0123456789") +# pylint: enable=invalid-name def safe_path(path): return path.translate(_safe_string_table) -class FileSystemObject(FileManagerAware, SettingsAware): +class FileSystemObject( # pylint: disable=too-many-instance-attributes + FileManagerAware, SettingsAware): (basename, - relative_path, - relative_path_lower, - dirname, - extension, - infostring, - path, - permissions, - stat) = (None,) * 9 + relative_path, + relative_path_lower, + dirname, + extension, + infostring, + path, + permissions, + stat) = (None,) * 9 (content_loaded, - force_load, - - is_device, - is_directory, - is_file, - is_fifo, - is_link, - is_socket, - - accessible, - exists, # "exists" currently means "link_target_exists" - loaded, - marked, - runnable, - stopped, - tagged, - - audio, - container, - document, - image, - media, - video) = (False,) * 21 + force_load, + + is_device, + is_directory, + is_file, + is_fifo, + is_link, + is_socket, + + accessible, + exists, # "exists" currently means "link_target_exists" + loaded, + marked, + runnable, + stopped, + tagged, + + audio, + container, + document, + image, + media, + video) = (False,) * 21 size = 0 @@ -164,8 +175,8 @@ class FileSystemObject(FileManagerAware, SettingsAware): return str(self.stat.st_gid) for attr in ('video', 'audio', 'image', 'media', 'document', 'container'): - exec("%s = lazy_property(" - "lambda self: self.set_mimetype() or self.%s)" % (attr, attr)) + exec( # pylint: disable=exec-used + "%s = lazy_property(lambda self: self.set_mimetype() or self.%s)" % (attr, attr)) def __str__(self): """returns a string containing the absolute path""" @@ -179,27 +190,31 @@ class FileSystemObject(FileManagerAware, SettingsAware): def set_mimetype(self): """assign attributes such as self.video according to the mimetype""" - basename = self.basename + bname = self.basename if self.extension == 'part': - basename = basename[0:-5] - self._mimetype = self.fm.mimetypes.guess_type(basename, False)[0] + bname = bname[0:-5] + # pylint: disable=attribute-defined-outside-init + self._mimetype = self.fm.mimetypes.guess_type(bname, False)[0] if self._mimetype is None: self._mimetype = '' + # pylint: enable=attribute-defined-outside-init self.video = self._mimetype.startswith('video') self.image = self._mimetype.startswith('image') self.audio = self._mimetype.startswith('audio') self.media = self.video or self.image or self.audio self.document = self._mimetype.startswith('text') \ - or self.extension in DOCUMENT_EXTENSIONS \ - or self.basename.lower() in DOCUMENT_BASENAMES + or self.extension in DOCUMENT_EXTENSIONS \ + or self.basename.lower() in DOCUMENT_BASENAMES self.container = self.extension in CONTAINER_EXTENSIONS + # pylint: disable=attribute-defined-outside-init keys = ('video', 'audio', 'image', 'media', 'document', 'container') self._mimetype_tuple = tuple(key for key in keys if getattr(self, key)) if self._mimetype == '': self._mimetype = None + # pylint: enable=attribute-defined-outside-init @property def mimetype(self): @@ -217,7 +232,7 @@ class FileSystemObject(FileManagerAware, SettingsAware): self.set_mimetype() return self._mimetype_tuple - def mark(self, boolean): + def mark(self, _): directory = self.fm.get_directory(self.dirname) directory.mark_item(self) @@ -249,7 +264,7 @@ class FileSystemObject(FileManagerAware, SettingsAware): self.permissions = None new_stat = None path = self.path - is_link = False + self.is_link = False if self.preload: new_stat = self.preload[1] self.is_link = new_stat.st_mode & 0o170000 == 0o120000 @@ -272,16 +287,16 @@ class FileSystemObject(FileManagerAware, SettingsAware): self.accessible = True if new_stat else False mode = new_stat.st_mode if new_stat else 0 - format = mode & 0o170000 - if format == 0o020000 or format == 0o060000: # stat.S_IFCHR/BLK + fmt = mode & 0o170000 + if fmt == 0o020000 or fmt == 0o060000: # stat.S_IFCHR/BLK self.is_device = True self.size = 0 self.infostring = 'dev' - elif format == 0o010000: # stat.S_IFIFO + elif fmt == 0o010000: # stat.S_IFIFO self.is_fifo = True self.size = 0 self.infostring = 'fifo' - elif format == 0o140000: # stat.S_IFSOCK + elif fmt == 0o140000: # stat.S_IFSOCK self.is_socket = True self.size = 0 self.infostring = 'sock' diff --git a/ranger/container/history.py b/ranger/container/history.py index 4e06d4b2..5a75854e 100644 --- a/ranger/container/history.py +++ b/ranger/container/history.py @@ -3,17 +3,22 @@ # TODO: rewrite to use deque instead of list +from __future__ import (absolute_import, print_function) + class HistoryEmptyException(Exception): pass class History(object): + def __init__(self, maxlen=None, unique=True): assert maxlen is not None, "maxlen cannot be None" if isinstance(maxlen, History): + # pylint: disable=protected-access self._history = list(maxlen._history) self._index = maxlen._index + # pylint: enable=protected-access self.maxlen = maxlen.maxlen self.unique = maxlen.unique else: @@ -67,16 +72,18 @@ class History(object): """ assert isinstance(other_history, History) - if len(self._history) == 0: + if not self._history: self._index = 0 future_length = 0 else: future_length = len(self._history) - self._index - 1 self._history[:self._index] = list( - other_history._history[:other_history._index + 1]) + other_history._history[:other_history._index + 1]) # pylint: disable=protected-access if len(self._history) > self.maxlen: + # pylint: disable=protected-access,invalid-unary-operand-type self._history = self._history[-self.maxlen:] + # pylint: enable=protected-access,invalid-unary-operand-type self._index = len(self._history) - future_length - 1 assert self._index < len(self._history) @@ -118,7 +125,7 @@ class History(object): def search(self, string, n): if n != 0 and string: - step = n > 0 and 1 or -1 + step = 1 if n > 0 else -1 i = self._index steps_left = steps_left_at_start = int(abs(n)) while steps_left: diff --git a/ranger/container/settings.py b/ranger/container/settings.py index 1faf5860..a83192ca 100644 --- a/ranger/container/settings.py +++ b/ranger/container/settings.py @@ -1,20 +1,25 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + +import re +import os.path from inspect import isfunction -from ranger.ext.signals import SignalDispatcher, Signal + +from ranger.ext.signals import SignalDispatcher from ranger.core.shared import FileManagerAware from ranger.gui.colorscheme import _colorscheme_name_to_class -import re -import os.path # Use these priority constants to trigger events at specific points in time # during processing of the signals "setopt" and "setopt.<some_setting_name>" +# pylint: disable=bad-whitespace SIGNAL_PRIORITY_RAW = 2.0 # signal.value will be raw SIGNAL_PRIORITY_SANITIZE = 1.0 # (Internal) post-processing signal.value SIGNAL_PRIORITY_BETWEEN = 0.6 # sanitized signal.value, old fm.settings.XYZ SIGNAL_PRIORITY_SYNC = 0.2 # (Internal) updating fm.settings.XYZ SIGNAL_PRIORITY_AFTER_SYNC = 0.1 # after fm.settings.XYZ was updated +# pylint: enable=bad-whitespace ALLOWED_SETTINGS = { @@ -98,6 +103,7 @@ DEFAULT_VALUES = { class Settings(SignalDispatcher, FileManagerAware): + def __init__(self): SignalDispatcher.__init__(self) self.__dict__['_localsettings'] = dict() @@ -105,12 +111,10 @@ class Settings(SignalDispatcher, FileManagerAware): self.__dict__['_tagsettings'] = dict() self.__dict__['_settings'] = dict() for name in ALLOWED_SETTINGS: - self.signal_bind('setopt.' + name, - self._sanitize, - priority=SIGNAL_PRIORITY_SANITIZE) - self.signal_bind('setopt.' + name, - self._raw_set_with_signal, - priority=SIGNAL_PRIORITY_SYNC) + self.signal_bind('setopt.' + name, self._sanitize, + priority=SIGNAL_PRIORITY_SANITIZE) + self.signal_bind('setopt.' + name, self._raw_set_with_signal, + priority=SIGNAL_PRIORITY_SYNC) def _sanitize(self, signal): name, value = signal.setting, signal.value @@ -122,7 +126,7 @@ class Settings(SignalDispatcher, FileManagerAware): signal.value = [1, 1] else: signal.value = [int(i) if str(i).isdigit() else 1 - for i in value] + for i in value] elif name == 'colorscheme': _colorscheme_name_to_class(signal) @@ -139,7 +143,7 @@ class Settings(SignalDispatcher, FileManagerAware): if self._settings['preview_script'] is None and value \ and self.fm.ui.is_on: self.fm.notify("Preview script undefined or not found!", - bad=True) + bad=True) def set(self, name, value, path=None, tags=None): assert name in ALLOWED_SETTINGS, "No such setting: {0}!".format(name) @@ -151,7 +155,7 @@ class Settings(SignalDispatcher, FileManagerAware): assert not (tags and path), "Can't set a setting for path and tag " \ "at the same time!" kws = dict(setting=name, value=value, previous=previous, - path=path, tags=tags, fm=self.fm) + path=path, tags=tags, fm=self.fm) self.signal_emit('setopt', **kws) self.signal_emit('setopt.' + name, **kws) @@ -170,20 +174,20 @@ class Settings(SignalDispatcher, FileManagerAware): if name in self._localsettings[pattern] and\ regex.search(localpath): return self._localsettings[pattern][name] + if self._tagsettings and path: realpath = os.path.realpath(path) if realpath in self.fm.tags: tag = self.fm.tags.marker(realpath) if tag in self._tagsettings and name in self._tagsettings[tag]: return self._tagsettings[tag][name] - if name in self._settings: - return self._settings[name] - else: + + if name not in self._settings: type_ = self.types_of(name)[0] value = DEFAULT_VALUES[type_] self._raw_set(name, value) self.__setattr__(name, value) - return self._settings[name] + return self._settings[name] def __setattr__(self, name, value): if name.startswith('_'): @@ -194,14 +198,14 @@ class Settings(SignalDispatcher, FileManagerAware): def __getattr__(self, name): if name.startswith('_'): return self.__dict__[name] - else: - return self.get(name, None) + return self.get(name, None) def __iter__(self): - for x in self._settings: - yield x + for setting in self._settings: + yield setting - def types_of(self, name): + @staticmethod + def types_of(name): try: typ = ALLOWED_SETTINGS[name] except KeyError: @@ -209,8 +213,7 @@ class Settings(SignalDispatcher, FileManagerAware): else: if isinstance(typ, tuple): return typ - else: - return (typ, ) + return (typ,) def _check_type(self, name, value): typ = ALLOWED_SETTINGS[name] @@ -258,7 +261,8 @@ class Settings(SignalDispatcher, FileManagerAware): self._raw_set(signal.setting, signal.value, signal.path, signal.tags) -class LocalSettings(): +class LocalSettings(object): # pylint: disable=too-few-public-methods + def __init__(self, path, parent): self.__dict__['_parent'] = parent self.__dict__['_path'] = path @@ -272,12 +276,11 @@ class LocalSettings(): def __getattr__(self, name): if name.startswith('_'): return self.__dict__[name] - else: - return self._parent.get(name, self._path) + return self._parent.get(name, self._path) def __iter__(self): - for x in self._parent._settings: - yield x + for setting in self._parent._settings: # pylint: disable=protected-access + yield setting __getitem__ = __getattr__ __setitem__ = __setattr__ diff --git a/ranger/container/tags.py b/ranger/container/tags.py index cf2f359d..dd13c432 100644 --- a/ranger/container/tags.py +++ b/ranger/container/tags.py @@ -3,6 +3,8 @@ # TODO: add a __getitem__ method to get the tag of a file +from __future__ import (absolute_import, print_function) + from os.path import isdir, exists, dirname, abspath, realpath, expanduser import string import sys @@ -39,7 +41,7 @@ class Tags(object): self.sync() for item in items: try: - del(self.tags[item]) + del self.tags[item] except KeyError: pass self.dump() @@ -56,7 +58,7 @@ class Tags(object): for item in items: try: if item in self and tag in (self.tags[item], self.default_tag): - del(self.tags[item]) + del self.tags[item] else: self.tags[item] = tag except KeyError: @@ -66,41 +68,40 @@ class Tags(object): def marker(self, item): if item in self.tags: return self.tags[item] - else: - return self.default_tag + return self.default_tag def sync(self): try: if sys.version_info[0] >= 3: - f = open(self._filename, 'r', errors='replace') + fobj = open(self._filename, 'r', errors='replace') else: - f = open(self._filename, 'r') + fobj = open(self._filename, 'r') except OSError: pass else: - self.tags = self._parse(f) - f.close() + self.tags = self._parse(fobj) + fobj.close() def dump(self): try: - f = open(self._filename, 'w') + fobj = open(self._filename, 'w') except OSError: pass else: - self._compile(f) - f.close() + self._compile(fobj) + fobj.close() - def _compile(self, f): + def _compile(self, fobj): for path, tag in self.tags.items(): if tag == self.default_tag: # COMPAT: keep the old format if the default tag is used - f.write(path + '\n') + fobj.write(path + '\n') elif tag in ALLOWED_KEYS: - f.write('{0}:{1}\n'.format(tag, path)) + fobj.write('{0}:{1}\n'.format(tag, path)) - def _parse(self, f): + def _parse(self, fobj): result = dict() - for line in f: + for line in fobj: line = line.strip() if len(line) > 2 and line[1] == ':': tag, path = line[0], line[2:] @@ -122,7 +123,7 @@ class TagsDummy(Tags): It acts like there are no tags and avoids writing any changes. """ - def __init__(self, filename): + def __init__(self, filename): # pylint: disable=super-init-not-called self.tags = dict() def __contains__(self, item): @@ -131,7 +132,7 @@ class TagsDummy(Tags): def add(self, *items, **others): pass - def remove(self, *items, **others): + def remove(self, *items): pass def toggle(self, *items, **others): @@ -146,8 +147,8 @@ class TagsDummy(Tags): def dump(self): pass - def _compile(self, f): + def _compile(self, fobj): pass - def _parse(self, f): + def _parse(self, fobj): pass diff --git a/ranger/core/actions.py b/ranger/core/actions.py index 8bbf1b62..070bda8c 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -1,14 +1,18 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +# pylint: disable=too-many-lines,attribute-defined-outside-init + +from __future__ import (absolute_import, print_function) + import codecs import os +from os import link, symlink, getcwd, listdir, stat +from os.path import join, isdir, realpath, exists import re import shutil import string import tempfile -from os.path import join, isdir, realpath, exists -from os import link, symlink, getcwd, listdir, stat from inspect import cleandoc from stat import S_IEXEC from hashlib import sha1 @@ -28,11 +32,10 @@ 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 -from ranger.core.linemode import DEFAULT_LINEMODE MACRO_FAIL = "<\x01\x01MACRO_HAS_NO_VALUE\x01\01>" -log = getLogger(__name__) +LOG = getLogger(__name__) class _MacroTemplate(string.Template): @@ -41,12 +44,15 @@ class _MacroTemplate(string.Template): idpattern = r"[_a-z0-9]*" -class Actions(FileManagerAware, SettingsAware): +class Actions( # pylint: disable=too-many-instance-attributes,too-many-public-methods + FileManagerAware, SettingsAware): + # -------------------------- # -- Basic Commands # -------------------------- - def exit(self): + @staticmethod + def exit(): """:exit Exit the program. @@ -71,17 +77,17 @@ class Actions(FileManagerAware, SettingsAware): Change mode to "visual" (selection) or "normal" mode. """ - if mode == self.mode: + if mode == self.mode: # pylint: disable=access-member-before-definition return if mode == 'visual': - self._visual_start = self.thisdir.pointed_obj - self._visual_start_pos = self.thisdir.pointer + self._visual_start = self.thisdir.pointed_obj + self._visual_start_pos = self.thisdir.pointer self._previous_selection = set(self.thisdir.marked_items) self.mark_files(val=not self._visual_reverse, movedown=False) elif mode == 'normal': - if self.mode == 'visual': - self._visual_start = None - self._visual_start_pos = None + if self.mode == 'visual': # pylint: disable=access-member-before-definition + self._visual_start = None + self._visual_start_pos = None self._previous_selection = None else: return @@ -90,12 +96,12 @@ class Actions(FileManagerAware, SettingsAware): def set_option_from_string(self, option_name, value, localpath=None, tags=None): if option_name not in ALLOWED_SETTINGS: - raise ValueError("The option named `%s' does not exist" % - option_name) + raise ValueError("The option named `%s' does not exist" % option_name) if not isinstance(value, str): raise ValueError("The value for an option needs to be a string.") - self.settings.set(option_name, self._parse_option_value(option_name, value), localpath, tags) + self.settings.set(option_name, self._parse_option_value(option_name, value), + localpath, tags) def _parse_option_value(self, name, value): types = self.fm.settings.types_of(name) @@ -104,7 +110,7 @@ class Actions(FileManagerAware, SettingsAware): return False elif value.lower() in ('true', 'on', '1'): return True - if type(None) in types and value.lower() == 'none': + if isinstance(None, types) and value.lower() == 'none': return None if int in types: try: @@ -149,16 +155,16 @@ class Actions(FileManagerAware, SettingsAware): Display the text in the statusbar. """ if isinstance(text, Exception): - if ranger.arg.debug: - raise + if ranger.args.debug: + raise text bad = True - elif bad is True and ranger.arg.debug: + elif bad is True and ranger.args.debug: raise Exception(str(text)) text = str(text) - log.debug("Command notify invoked: [Bad: {0}, Text: '{1}']".format(bad, text)) + LOG.debug("Command notify invoked: [Bad: %s, Text: '%s']", bad, text) if self.ui and self.ui.is_on: self.ui.status.notify(" ".join(text.split("\n")), - duration=duration, bad=bad) + duration=duration, bad=bad) else: print(text) @@ -176,8 +182,8 @@ class Actions(FileManagerAware, SettingsAware): self.loader.remove(index=0) def get_cumulative_size(self): - for f in self.thistab.get_selection() or (): - f.look_up_cumulative_size() + for fobj in self.thistab.get_selection() or (): + fobj.look_up_cumulative_size() self.ui.status.request_redraw() self.ui.redraw_main_column() @@ -188,7 +194,8 @@ class Actions(FileManagerAware, SettingsAware): """ self.ui.redraw_window() - def open_console(self, string='', prompt=None, position=None): + def open_console(self, string='', # pylint: disable=redefined-outer-name + prompt=None, position=None): """:open_console [string] Open the console. @@ -196,7 +203,8 @@ class Actions(FileManagerAware, SettingsAware): self.change_mode('normal') self.ui.open_console(string, prompt=prompt, position=position) - def execute_console(self, string='', wildcards=[], quantifier=None): + def execute_console(self, string='', # pylint: disable=redefined-outer-name + wildcards=None, quantifier=None): """:execute_console [string] Execute a command for the console @@ -209,28 +217,30 @@ class Actions(FileManagerAware, SettingsAware): cmd = cmd_class(string) if cmd.resolve_macros and _MacroTemplate.delimiter in string: macros = dict(('any%d' % i, key_to_string(char)) - for i, char in enumerate(wildcards)) + for i, char in enumerate(wildcards if wildcards is not None else [])) if 'any0' in macros: macros['any'] = macros['any0'] try: string = self.substitute_macros(string, additional=macros, - escape=cmd.escape_macros_for_shell) - except ValueError as e: - if ranger.arg.debug: + escape=cmd.escape_macros_for_shell) + except ValueError as ex: + if ranger.args.debug: raise else: - return self.notify(e) + return self.notify(ex) try: cmd_class(string, quantifier=quantifier).execute() - except Exception as e: - if ranger.arg.debug: + except Exception as ex: + if ranger.args.debug: raise else: - self.notify(e) + self.notify(ex) - def substitute_macros(self, string, additional=dict(), escape=False): + def substitute_macros(self, string, # pylint: disable=redefined-outer-name + additional=None, escape=False): macros = self._get_macros() - macros.update(additional) + if additional: + macros.update(additional) if escape: for key, value in macros.items(): if isinstance(value, list): @@ -246,7 +256,7 @@ class Actions(FileManagerAware, SettingsAware): raise ValueError("Could not apply macros to `%s'" % string) return result - def _get_macros(self): + def _get_macros(self): # pylint: disable=too-many-branches,too-many-statements macros = {} macros['rangerdir'] = ranger.RANGERDIR @@ -260,7 +270,7 @@ class Actions(FileManagerAware, SettingsAware): 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()] + 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 @@ -273,7 +283,7 @@ class Actions(FileManagerAware, SettingsAware): 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 [])] + if fl.realpath in self.fm.tags or []] else: macros['t'] = MACRO_FAIL @@ -295,7 +305,7 @@ class Actions(FileManagerAware, SettingsAware): 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()] + for fl in tabdir.get_selection()] macros[i + 's'] = [fl.path for fl in tabdir.get_selection()] else: macros[i + 'p'] = MACRO_FAIL @@ -329,7 +339,7 @@ class Actions(FileManagerAware, SettingsAware): macros['F'] = MACRO_FAIL if next_tab_dir.get_selection(): macros['P'] = [os.path.join(next_tab.path, fl.path) - for fl in next_tab.get_selection()] + for fl in next_tab.get_selection()] macros['S'] = [fl.path for fl in next_tab.get_selection()] else: macros['P'] = MACRO_FAIL @@ -347,20 +357,19 @@ class Actions(FileManagerAware, SettingsAware): Load a config file. """ filename = os.path.expanduser(filename) - log.debug("Sourcing config file '{0}'".format(filename)) - with open(filename, 'r') as f: - for line in f: + LOG.debug("Sourcing config file '%s'", filename) + with open(filename, 'r') as fobj: + for line in fobj: line = line.strip(" \r\n") if line.startswith("#") or not line.strip(): continue try: self.execute_console(line) - except Exception as e: - if ranger.arg.debug: + except Exception as ex: + if ranger.args.debug: raise else: - self.notify('Error in line `%s\':\n %s' % - (line, str(e)), bad=True) + self.notify('Error in line `%s\':\n %s' % (line, str(ex)), bad=True) def execute_file(self, files, **kw): """Uses the "rifle" module to open/execute a file @@ -379,19 +388,19 @@ class Actions(FileManagerAware, SettingsAware): # ranger can act as a file chooser when running with --choosefile=... if mode == 0 and 'label' not in kw: - if ranger.arg.choosefile: - open(ranger.arg.choosefile, 'w').write(self.fm.thisfile.path) + if ranger.args.choosefile: + open(ranger.args.choosefile, 'w').write(self.fm.thisfile.path) - if ranger.arg.choosefiles: - open(ranger.arg.choosefiles, 'w').write("".join( - f.path + "\n" for f in self.fm.thistab.get_selection())) + if ranger.args.choosefiles: + open(ranger.args.choosefiles, 'w').write("".join( + fobj.path + "\n" for fobj in self.fm.thistab.get_selection())) - if ranger.arg.choosefile or ranger.arg.choosefiles: + if ranger.args.choosefile or ranger.args.choosefiles: raise SystemExit() if isinstance(files, set): files = list(files) - elif type(files) not in (list, tuple): + elif not isinstance(files, (list, tuple)): files = [files] flags = kw.get('flags', '') @@ -399,7 +408,7 @@ class Actions(FileManagerAware, SettingsAware): files = [self.fm.thisfile] self.signal_emit('execute.before', keywords=kw) - filenames = [f.path for f in files] + filenames = [fobj.path for fobj in files] label = kw.get('label', kw.get('app', None)) try: return self.rifle.execute(filenames, mode, label, flags, None) @@ -410,7 +419,7 @@ class Actions(FileManagerAware, SettingsAware): # -- Moving Around # -------------------------- - def move(self, narg=None, **kw): + def move(self, narg=None, **kw): # pylint: disable=too-many-locals,too-many-branches """A universal movement method. Accepts these parameters: @@ -442,19 +451,19 @@ class Actions(FileManagerAware, SettingsAware): mode = 0 if narg is not None: mode = narg - cf = self.thisfile + tfile = self.thisfile selection = self.thistab.get_selection() - if not self.thistab.enter_dir(cf) and selection: + if not self.thistab.enter_dir(tfile) and selection: result = self.execute_file(selection, mode=mode) if result in (False, ASK_COMMAND): self.open_console('open_with ') elif direction.vertical() and cwd.files: newpos = direction.move( - direction=direction.down(), - override=narg, - maximum=len(cwd), - current=cwd.pointer, - pagesize=self.ui.browser.hei) + direction=direction.down(), + override=narg, + maximum=len(cwd), + current=cwd.pointer, + pagesize=self.ui.browser.hei) cwd.move(to=newpos) if self.mode == 'visual': try: @@ -463,8 +472,7 @@ class Actions(FileManagerAware, SettingsAware): self._visual_start = None startpos = min(self._visual_start_pos, len(cwd)) # The files between here and _visual_start_pos - targets = set(cwd.files[min(startpos, newpos): - max(startpos, newpos) + 1]) + targets = set(cwd.files[min(startpos, newpos):(max(startpos, newpos) + 1)]) # The selection before activating visual mode old = self._previous_selection # The current selection @@ -472,15 +480,15 @@ class Actions(FileManagerAware, SettingsAware): # Set theory anyone? if not self._visual_reverse: - for f in targets - current: - cwd.mark_item(f, True) - for f in current - old - targets: - cwd.mark_item(f, False) + for fobj in targets - current: + cwd.mark_item(fobj, True) + for fobj in current - old - targets: + cwd.mark_item(fobj, False) else: - for f in targets & current: - cwd.mark_item(f, False) - for f in old - current - targets: - cwd.mark_item(f, True) + for fobj in targets & current: + cwd.mark_item(fobj, False) + for fobj in old - current - targets: + cwd.mark_item(fobj, True) if self.ui.pager.visible: self.display_file() @@ -520,8 +528,8 @@ class Actions(FileManagerAware, SettingsAware): cdpath = os.environ.get('CDPATH', None) or os.environ.get('cdpath', None) result = self.thistab.enter_dir(path, history=history) if result is False and cdpath: - for p in cdpath.split(':'): - curpath = os.path.join(p, path) + for comp in cdpath.split(':'): + curpath = os.path.join(comp, path) if os.path.isdir(curpath): result = self.thistab.enter_dir(curpath, history=history) break @@ -531,16 +539,16 @@ class Actions(FileManagerAware, SettingsAware): self.change_mode('normal') return result - def cd(self, path, remember=True): + def cd(self, path, remember=True): # pylint: disable=invalid-name """enter the directory at the given path, remember=True""" self.enter_dir(path, remember=remember) def traverse(self): self.change_mode('normal') - cf = self.thisfile + tfile = self.thisfile cwd = self.thisdir - if cf is not None and cf.is_directory: - self.enter_dir(cf.path) + if tfile is not None and tfile.is_directory: + self.enter_dir(tfile.path) elif cwd.pointer >= len(cwd) - 1: while True: self.move(left=1) @@ -583,7 +591,7 @@ class Actions(FileManagerAware, SettingsAware): def execute_command(self, cmd, **kw): return self.run(cmd, **kw) - def edit_file(self, file=None): + def edit_file(self, file=None): # pylint: disable=redefined-builtin """Calls execute_file with the current file and label='editor'""" if file is None: file = self.thisfile @@ -593,7 +601,7 @@ class Actions(FileManagerAware, SettingsAware): return self.execute_file(file, label='editor') - def toggle_option(self, string): + def toggle_option(self, string): # pylint: disable=redefined-outer-name """:toggle_option <string> Toggle a boolean option named <string>. @@ -603,7 +611,7 @@ class Actions(FileManagerAware, SettingsAware): elif string in ALLOWED_VALUES: current = self.settings[string] allowed = ALLOWED_VALUES[string] - if len(allowed) > 0: + if allowed: if current not in allowed and current == "": current = allowed[0] if current in allowed: @@ -626,7 +634,8 @@ class Actions(FileManagerAware, SettingsAware): if func is not None: self.settings['sort'] = str(func) - def mark_files(self, all=False, toggle=False, val=None, movedown=None, narg=None): + def mark_files(self, all=False, # pylint: disable=redefined-builtin,too-many-arguments + toggle=False, val=None, movedown=None, narg=None): """A wrapper for the directory.mark_xyz functions. Arguments: @@ -680,7 +689,7 @@ class Actions(FileManagerAware, SettingsAware): cwd = self.thisdir direction = Direction(dirarg) pos, selected = direction.select(lst=cwd.files, current=cwd.pointer, - pagesize=self.ui.termsize[0]) + pagesize=self.ui.termsize[0]) cwd.pointer = pos cwd.correct_pointer() for item in selected: @@ -693,7 +702,7 @@ class Actions(FileManagerAware, SettingsAware): def search_file(self, text, offset=1, regexp=True): if isinstance(text, str) and regexp: try: - text = re.compile(text, re.U | re.I) + text = re.compile(text, re.UNICODE | re.IGNORECASE) # pylint: disable=no-member except Exception: return False self.thistab.last_search = text @@ -713,11 +722,14 @@ class Actions(FileManagerAware, SettingsAware): if arg is None: return False if hasattr(arg, 'search'): - fnc = lambda x: arg.search(x.basename) + def fnc(obj): + return arg.search(obj.basename) else: - fnc = lambda x: arg in x.basename + def fnc(obj): + return arg in obj.basename elif order == 'tag': - fnc = lambda x: x.realpath in self.tags + def fnc(obj): + return obj.realpath in self.tags return self.thisdir.search_fnc(fnc=fnc, offset=offset, forward=forward) @@ -726,24 +738,28 @@ class Actions(FileManagerAware, SettingsAware): if original_order is not None or not cwd.cycle_list: lst = list(cwd.files) if order == 'size': - fnc = lambda item: -item.size + def fnc(item): + return -item.size elif order == 'mimetype': - fnc = lambda item: item.mimetype or '' + def fnc(item): + return item.mimetype or '' elif order == 'ctime': - fnc = lambda item: -int(item.stat and item.stat.st_ctime) + def fnc(item): + return -int(item.stat and item.stat.st_ctime) elif order == 'atime': - fnc = lambda item: -int(item.stat and item.stat.st_atime) + def fnc(item): + return -int(item.stat and item.stat.st_atime) elif order == 'mtime': - fnc = lambda item: -int(item.stat and item.stat.st_mtime) + def fnc(item): + return -int(item.stat and item.stat.st_mtime) lst.sort(key=fnc) cwd.set_cycle_list(lst) return cwd.cycle(forward=None) return cwd.cycle(forward=forward) - def set_search_method(self, order, forward=True): - if order in ('search', 'tag', 'size', 'mimetype', 'ctime', - 'mtime', 'atime'): + def set_search_method(self, order, forward=True): # pylint: disable=unused-argument + if order in ('search', 'tag', 'size', 'mimetype', 'ctime', 'mtime', 'atime'): self.search_method = order # -------------------------- @@ -826,12 +842,11 @@ class Actions(FileManagerAware, SettingsAware): except Exception: self.ui.browser.draw_info = [] return - programs = [program for program in self.rifle.list_commands([target.path], - None)] + programs = [program for program in self.rifle.list_commands([target.path], None)] if programs: num_digits = max((len(str(program[0])) for program in programs)) - program_info = ['%s | %s' % (str(program[0]).rjust(num_digits), - program[1]) for program in programs] + program_info = ['%s | %s' % (str(program[0]).rjust(num_digits), program[1]) + for program in programs] self.ui.browser.draw_info = program_info def hide_console_info(self): @@ -844,7 +859,7 @@ class Actions(FileManagerAware, SettingsAware): def display_command_help(self, console_widget): try: - command = console_widget._get_cmd_class() + command = console_widget._get_cmd_class() # pylint: disable=protected-access except Exception: self.notify("Feature not available!", bad=True) return @@ -854,8 +869,7 @@ class Actions(FileManagerAware, SettingsAware): return if not command.__doc__: - self.notify("Command has no docstring. Try using python without -OO", - bad=True) + self.notify("Command has no docstring. Try using python without -OO", bad=True) return pager = self.ui.open_pager() @@ -886,11 +900,11 @@ class Actions(FileManagerAware, SettingsAware): return pager = self.ui.open_pager() - f = self.thisfile.get_preview_source(pager.wid, pager.hei) + fobj = self.thisfile.get_preview_source(pager.wid, pager.hei) if self.thisfile.is_image_preview(): - pager.set_image(f) + pager.set_image(fobj) else: - pager.set_source(f) + pager.set_source(fobj) # -------------------------- # -- Previews @@ -902,19 +916,16 @@ class Actions(FileManagerAware, SettingsAware): except Exception: return False - if version_info[0] == 3: - def sha1_encode(self, path): - return os.path.join(ranger.arg.cachedir, - sha1(path.encode('utf-8', 'backslashreplace')) - .hexdigest()) + '.jpg' - else: - def sha1_encode(self, path): - return os.path.join(ranger.arg.cachedir, - sha1(path).hexdigest()) + '.jpg' - - def get_preview(self, file, width, height): + @staticmethod + def sha1_encode(path): + if version_info[0] < 3: + return os.path.join(ranger.args.cachedir, sha1(path).hexdigest()) + '.jpg' + return os.path.join(ranger.args.cachedir, + sha1(path.encode('utf-8', 'backslashreplace')).hexdigest()) + '.jpg' + + def get_preview(self, fobj, width, height): # pylint: disable=too-many-return-statements pager = self.ui.get_pager() - path = file.realpath + path = fobj.realpath if not path or not os.path.exists(path): return None @@ -936,19 +947,30 @@ class Actions(FileManagerAware, SettingsAware): if data['loading']: return None - found = data.get((-1, -1), data.get((width, -1), - data.get((-1, height), data.get((width, height), False)))) + found = data.get( + (-1, -1), data.get( + (width, -1), data.get( + (-1, height), data.get( + (width, height), False + ) + ) + ) + ) if found is False: try: stat_ = os.stat(self.settings.preview_script) except Exception: - self.fm.notify("Preview Script `%s' doesn't exist!" % - self.settings.preview_script, bad=True) + self.fm.notify( + "Preview Script `%s' doesn't exist!" % self.settings.preview_script, + bad=True, + ) return None if not stat_.st_mode & S_IEXEC: - self.fm.notify("Preview Script `%s' is not executable!" % - self.settings.preview_script, bad=True) + self.fm.notify( + "Preview Script `%s' is not executable!" % self.settings.preview_script, + bad=True, + ) return None data['loading'] = True @@ -960,48 +982,51 @@ class Actions(FileManagerAware, SettingsAware): data['loading'] = False return path - cacheimg = os.path.join(ranger.arg.cachedir, self.sha1_encode(path)) - if (os.path.isfile(cacheimg) and os.path.getmtime(cacheimg) > os.path.getmtime(path)): + cacheimg = os.path.join(ranger.args.cachedir, self.sha1_encode(path)) + if os.path.isfile(cacheimg) and \ + os.path.getmtime(cacheimg) > os.path.getmtime(path): data['foundpreview'] = True data['imagepreview'] = True pager.set_image(cacheimg) data['loading'] = False return cacheimg - loadable = CommandLoader(args=[self.settings.preview_script, - path, str(width), str(height), cacheimg, - str(self.settings.preview_images)], read=True, - silent=True, descr="Getting preview of %s" % path) + loadable = CommandLoader( + args=[self.settings.preview_script, path, str(width), str(height), + cacheimg, str(self.settings.preview_images)], + read=True, + silent=True, + descr="Getting preview of %s" % path, + ) def on_after(signal): - exit = signal.process.poll() + rcode = signal.process.poll() content = signal.loader.stdout_buffer data['foundpreview'] = True - if exit == 0: + if rcode == 0: data[(width, height)] = content - elif exit == 3: + elif rcode == 3: data[(-1, height)] = content - elif exit == 4: + elif rcode == 4: data[(width, -1)] = content - elif exit == 5: + elif rcode == 5: data[(-1, -1)] = content - elif exit == 6: + elif rcode == 6: data['imagepreview'] = True - elif exit == 7: + elif rcode == 7: data['directimagepreview'] = True - elif exit == 1: + elif rcode == 1: data[(-1, -1)] = None data['foundpreview'] = False - elif exit == 2: - f = codecs.open(path, 'r', errors='ignore') + elif rcode == 2: + fobj = codecs.open(path, 'r', errors='ignore') try: - data[(-1, -1)] = f.read(1024 * 32) + data[(-1, -1)] = fobj.read(1024 * 32) except UnicodeDecodeError: - f.close() - f = codecs.open(path, 'r', encoding='latin-1', - errors='ignore') - data[(-1, -1)] = f.read(1024 * 32) - f.close() + fobj.close() + fobj = codecs.open(path, 'r', encoding='latin-1', errors='ignore') + data[(-1, -1)] = fobj.read(1024 * 32) + fobj.close() else: data[(-1, -1)] = None if self.thisfile and self.thisfile.realpath == path: @@ -1019,7 +1044,7 @@ class Actions(FileManagerAware, SettingsAware): pager.set_source(self.thisfile.get_preview_source( pager.wid, pager.hei)) - def on_destroy(signal): + def on_destroy(signal): # pylint: disable=unused-argument try: del self.previews[path] except Exception: @@ -1094,8 +1119,7 @@ class Actions(FileManagerAware, SettingsAware): tab.enter_dir(tab.path, history=False) self.thistab = tab self.change_mode('normal') - self.signal_emit('tab.change', old=previous_tab, - new=self.thistab) + self.signal_emit('tab.change', old=previous_tab, new=self.thistab) break def tab_move(self, offset, narg=None): @@ -1125,7 +1149,8 @@ class Actions(FileManagerAware, SettingsAware): file_selection = None if create_directory: try: - os.makedirs(path, exist_ok=True) + if not os.path.isdir(path): + os.makedirs(path) except OSError as err: self.fm.notify(err, bad=True) return @@ -1159,7 +1184,7 @@ class Actions(FileManagerAware, SettingsAware): self.fm.select_file(file_selection) def _get_tab_list(self): - assert len(self.tabs) > 0, "There must be >=1 tabs at all times" + assert self.tabs, "There must be at least 1 tab at all times" return sorted(self.tabs) # -------------------------- @@ -1172,7 +1197,7 @@ class Actions(FileManagerAware, SettingsAware): temporary_file = tempfile.NamedTemporaryFile() - def write(string): + def write(string): # pylint: disable=redefined-outer-name temporary_file.write(string.encode('utf-8')) def recurse(before, pointer): @@ -1198,7 +1223,7 @@ class Actions(FileManagerAware, SettingsAware): def dump_commands(self): temporary_file = tempfile.NamedTemporaryFile() - def write(string): + def write(string): # pylint: disable=redefined-outer-name temporary_file.write(string.encode('utf-8')) undocumented = [] @@ -1224,7 +1249,7 @@ class Actions(FileManagerAware, SettingsAware): def dump_settings(self): temporary_file = tempfile.NamedTemporaryFile() - def write(string): + def write(string): # pylint: disable=redefined-outer-name temporary_file.write(string.encode('utf-8')) for setting in sorted(ALLOWED_SETTINGS): @@ -1256,7 +1281,7 @@ class Actions(FileManagerAware, SettingsAware): assert mode in ('set', 'add', 'remove', 'toggle') cwd = self.thisdir if not narg and not dirarg: - selected = (f for f in self.thistab.get_selection() if f in cwd.files) + selected = (fobj for fobj in self.thistab.get_selection() if fobj in cwd.files) else: if not dirarg and narg: direction = Direction(down=1) @@ -1264,9 +1289,8 @@ class Actions(FileManagerAware, SettingsAware): else: direction = Direction(dirarg) offset = 1 - pos, selected = direction.select( - override=narg, lst=cwd.files, current=cwd.pointer, - pagesize=self.ui.termsize[0], offset=offset) + pos, selected = direction.select(override=narg, lst=cwd.files, current=cwd.pointer, + pagesize=self.ui.termsize[0], offset=offset) cwd.pointer = pos cwd.correct_pointer() if mode == 'set': @@ -1292,32 +1316,32 @@ class Actions(FileManagerAware, SettingsAware): def paste_symlink(self, relative=False): copied_files = self.copy_buffer - for f in copied_files: - self.notify(next_available_filename(f.basename)) + for fobj in copied_files: + self.notify(next_available_filename(fobj.basename)) try: - new_name = next_available_filename(f.basename) + new_name = next_available_filename(fobj.basename) if relative: - relative_symlink(f.path, join(getcwd(), new_name)) + relative_symlink(fobj.path, join(getcwd(), new_name)) else: - symlink(f.path, join(getcwd(), new_name)) - except Exception as x: - self.notify(x) + symlink(fobj.path, join(getcwd(), new_name)) + except Exception as ex: + self.notify(ex) def paste_hardlink(self): - for f in self.copy_buffer: + for fobj in self.copy_buffer: try: - new_name = next_available_filename(f.basename) - link(f.path, join(getcwd(), new_name)) - except Exception as x: - self.notify(x) + new_name = next_available_filename(fobj.basename) + link(fobj.path, join(getcwd(), new_name)) + except Exception as ex: + self.notify(ex) def paste_hardlinked_subtree(self): - for f in self.copy_buffer: + for fobj in self.copy_buffer: try: - target_path = join(getcwd(), f.basename) - self._recurse_hardlinked_tree(f.path, target_path) - except Exception as x: - self.notify(x) + target_path = join(getcwd(), fobj.basename) + self._recurse_hardlinked_tree(fobj.path, target_path) + except Exception as ex: + self.notify(ex) def _recurse_hardlinked_tree(self, source_path, target_path): if isdir(source_path): @@ -1329,9 +1353,9 @@ class Actions(FileManagerAware, SettingsAware): join(target_path, item)) else: if not exists(target_path) \ - or stat(source_path).st_ino != stat(target_path).st_ino: + or stat(source_path).st_ino != stat(target_path).st_ino: link(source_path, - next_available_filename(target_path)) + next_available_filename(target_path)) def paste(self, overwrite=False, append=False): """:paste @@ -1347,23 +1371,23 @@ class Actions(FileManagerAware, SettingsAware): self.notify("Deleting!") # COMPAT: old command.py use fm.delete() without arguments if files is None: - files = (f.path for f in self.thistab.get_selection()) - files = [os.path.abspath(f) for f in files] - for f in files: + files = (fobj.path for fobj in self.thistab.get_selection()) + files = [os.path.abspath(path) for path in files] + for path in files: # Untag the deleted files. for tag in self.fm.tags.tags: - if str(tag).startswith(f): + if str(tag).startswith(path): self.fm.tags.remove(tag) - self.copy_buffer = set(filter(lambda f: f.path not in files, self.copy_buffer)) - for f in files: - if isdir(f) and not os.path.islink(f): + self.copy_buffer = set(fobj for fobj in self.copy_buffer if fobj.path not in files) + for path in files: + if isdir(path) and not os.path.islink(path): try: - shutil.rmtree(f) + shutil.rmtree(path) except OSError as err: self.notify(err) else: try: - os.remove(f) + os.remove(path) except OSError as err: self.notify(err) self.thistab.ensure_correct_pointer() diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 5d630464..9b7c8fae 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -3,6 +3,8 @@ """The File Manager, putting the pieces together""" +from __future__ import (absolute_import, print_function) + from time import time from collections import deque import logging @@ -21,7 +23,8 @@ from ranger.container.tags import Tags, TagsDummy from ranger.gui.ui import UI from ranger.container.bookmarks import Bookmarks from ranger.core.runner import Runner -from ranger.ext.img_display import * +from ranger.ext.img_display import (W3MImageDisplayer, ITerm2ImageDisplayer, + URXVTImageDisplayer, URXVTImageFSDisplayer, ImageDisplayer) from ranger.core.metadata import MetadataManager from ranger.ext.rifle import Rifle from ranger.container.directory import Directory @@ -29,10 +32,12 @@ from ranger.ext.signals import SignalDispatcher from ranger.core.loader import Loader from ranger.ext import logutils -log = logging.getLogger(__name__) + +LOG = logging.getLogger(__name__) -class FM(Actions, SignalDispatcher): +class FM(Actions, # pylint: disable=too-many-instance-attributes + SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = 'normal' # either 'normal' or 'visual'. @@ -43,15 +48,12 @@ class FM(Actions, SignalDispatcher): _visual_start = None _visual_start_pos = None - def __init__(self, ui=None, bookmarks=None, tags=None, paths=['.']): + def __init__(self, ui=None, bookmarks=None, tags=None, paths=None): """Initialize FM.""" Actions.__init__(self) SignalDispatcher.__init__(self) - if ui is None: - self.ui = UI() - else: - self.ui = ui - self.start_paths = paths + self.ui = ui if ui is not None else UI() + self.start_paths = paths if paths is not None else ['.'] self.directories = dict() self.bookmarks = bookmarks self.current_tab = 1 @@ -65,6 +67,10 @@ class FM(Actions, SignalDispatcher): self.copy_buffer = set() self.do_cut = False self.metadata = MetadataManager() + self.image_displayer = None + self.run = None + self.rifle = None + self.thistab = None try: self.username = pwd.getpwuid(os.geteuid()).pw_name @@ -80,8 +86,7 @@ class FM(Actions, SignalDispatcher): def initialize(self): """If ui/bookmarks are None, they will be initialized here.""" - self.tabs = dict((n + 1, Tab(path)) for n, path in - enumerate(self.start_paths)) + self.tabs = dict((n + 1, Tab(path)) for n, path in enumerate(self.start_paths)) tab_list = self._get_tab_list() if tab_list: self.current_tab = tab_list[0] @@ -90,7 +95,7 @@ class FM(Actions, SignalDispatcher): self.current_tab = 1 self.tabs[self.current_tab] = self.thistab = Tab('.') - if not ranger.arg.clean and os.path.isfile(self.confpath('rifle.conf')): + if not ranger.args.clean and os.path.isfile(self.confpath('rifle.conf')): rifleconf = self.confpath('rifle.conf') else: rifleconf = self.relpath('config/rifle.conf') @@ -100,24 +105,23 @@ class FM(Actions, SignalDispatcher): def set_image_displayer(): self.image_displayer = self._get_image_displayer() set_image_displayer() - self.settings.signal_bind('setopt.preview_images_method', - set_image_displayer, - priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) + self.settings.signal_bind('setopt.preview_images_method', set_image_displayer, + priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) - if not ranger.arg.clean and self.tags is None: + if not ranger.args.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) - elif ranger.arg.clean: + elif ranger.args.clean: self.tags = TagsDummy("") if self.bookmarks is None: - if ranger.arg.clean: + if ranger.args.clean: bookmarkfile = None else: bookmarkfile = self.confpath('bookmarks') self.bookmarks = Bookmarks( - bookmarkfile=bookmarkfile, - bookmarktype=Directory, - autosave=self.settings.autosave_bookmarks) + bookmarkfile=bookmarkfile, + bookmarktype=Directory, + autosave=self.settings.autosave_bookmarks) self.bookmarks.load() self.ui.setup_curses() @@ -141,12 +145,11 @@ class FM(Actions, SignalDispatcher): from ranger.ext.shell_escape import shell_quote if self.settings.open_all_images and \ - len(self.thisdir.marked_items) == 0 and \ + not self.thisdir.marked_items and \ re.match(r'^(feh|sxiv|imv|pqiv) ', command): images = [f.relative_path for f in self.thisdir.files if f.image] - escaped_filenames = " ".join(shell_quote(f) - for f in images if "\x00" not in f) + escaped_filenames = " ".join(shell_quote(f) for f in images if "\x00" not in f) if images and self.thisfile.relative_path in images and \ "$@" in command: @@ -154,28 +157,26 @@ class FM(Actions, SignalDispatcher): if command[0:5] == 'sxiv ': number = images.index(self.thisfile.relative_path) + 1 - new_command = command.replace("sxiv ", - "sxiv -n %d " % number, 1) + new_command = command.replace("sxiv ", "sxiv -n %d " % number, 1) if command[0:4] == 'feh ': - new_command = command.replace("feh ", - "feh --start-at %s " % - shell_quote(self.thisfile.relative_path), 1) + new_command = command.replace( + "feh ", + "feh --start-at %s " % shell_quote(self.thisfile.relative_path), + 1, + ) if command[0:4] == 'imv ': number = images.index(self.thisfile.relative_path) + 1 - new_command = command.replace("imv ", - "imv -n %d " % number, 1) + new_command = command.replace("imv ", "imv -n %d " % number, 1) if command[0:5] == 'pqiv ': number = images.index(self.thisfile.relative_path) - new_command = command.replace("pqiv ", - "pqiv --action \"goto_file_byindex(%d)\" " % - number, 1) + new_command = command.replace( + "pqiv ", "pqiv --action \"goto_file_byindex(%d)\" " % number, 1) if new_command: - command = "set -- %s; %s" % (escaped_filenames, - new_command) + command = "set -- %s; %s" % (escaped_filenames, new_command) return old_preprocessing_hook(command) self.rifle.hook_command_preprocessing = sxiv_workaround_hook @@ -184,12 +185,13 @@ class FM(Actions, SignalDispatcher): self.notify(text, bad=True) self.run = Runner(ui=self.ui, logfunc=mylogfunc, fm=self) - self.settings.signal_bind('setopt.metadata_deep_search', - lambda signal: setattr(signal.fm.metadata, 'deep_search', - signal.value)) + self.settings.signal_bind( + 'setopt.metadata_deep_search', + lambda signal: setattr(signal.fm.metadata, 'deep_search', signal.value) + ) def destroy(self): - debug = ranger.arg.debug + debug = ranger.args.debug if self.ui: try: self.ui.destroy() @@ -203,7 +205,8 @@ class FM(Actions, SignalDispatcher): if debug: raise - def get_log(self): + @staticmethod + def get_log(): """Return the current log The log is returned as a list of string @@ -221,8 +224,7 @@ class FM(Actions, SignalDispatcher): return URXVTImageDisplayer() elif self.settings.preview_images_method == "urxvt-full": return URXVTImageFSDisplayer() - else: - return ImageDisplayer() + return ImageDisplayer() def _get_thisfile(self): return self.thistab.thisfile @@ -237,7 +239,7 @@ class FM(Actions, SignalDispatcher): self.thistab.thisdir = obj thisfile = property(_get_thisfile, _set_thisfile) - thisdir = property(_get_thisdir, _set_thisdir) + thisdir = property(_get_thisdir, _set_thisdir) def block_input(self, sec=0): self.input_blocked = sec != 0 @@ -249,30 +251,30 @@ class FM(Actions, SignalDispatcher): return self.input_blocked def copy_config_files(self, which): - if ranger.arg.clean: + if ranger.args.clean: sys.stderr.write("refusing to copy config files in clean mode\n") return import shutil from errno import EEXIST - def copy(_from, to): - if os.path.exists(self.confpath(to)): - sys.stderr.write("already exists: %s\n" % self.confpath(to)) + def copy(src, dest): + if os.path.exists(self.confpath(dest)): + sys.stderr.write("already exists: %s\n" % self.confpath(dest)) else: - sys.stderr.write("creating: %s\n" % self.confpath(to)) + sys.stderr.write("creating: %s\n" % self.confpath(dest)) try: - os.makedirs(ranger.arg.confdir) + os.makedirs(ranger.args.confdir) except OSError as err: if err.errno != EEXIST: # EEXIST means it already exists print("This configuration directory could not be created:") - print(ranger.arg.confdir) + print(ranger.args.confdir) print("To run ranger without the need for configuration") print("files, use the --clean option.") raise SystemExit() try: - shutil.copy(self.relpath(_from), self.confpath(to)) - except Exception as e: - sys.stderr.write(" ERROR: %s\n" % str(e)) + shutil.copy(self.relpath(src), self.confpath(dest)) + except Exception as ex: + sys.stderr.write(" ERROR: %s\n" % str(ex)) if which == 'rifle' or which == 'all': copy('config/rifle.conf', 'rifle.conf') if which == 'commands' or which == 'all': @@ -284,28 +286,30 @@ class FM(Actions, SignalDispatcher): if which == 'scope' or which == 'all': copy('data/scope.sh', 'scope.sh') os.chmod(self.confpath('scope.sh'), - os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) + os.stat(self.confpath('scope.sh')).st_mode | stat.S_IXUSR) if which in ('all', 'rifle', 'scope', 'commands', 'commands_full', 'rc'): sys.stderr.write("\n> Please note that configuration files may " - "change as ranger evolves.\n It's completely up to you to " - "keep them up to date.\n") + "change as ranger evolves.\n It's completely up to you to " + "keep them up to date.\n") if os.environ.get('RANGER_LOAD_DEFAULT_RC', 0) != 'FALSE': sys.stderr.write("\n> To stop ranger from loading " - "\033[1mboth\033[0m the default and your custom rc.conf,\n" - " please set the environment variable " - "\033[1mRANGER_LOAD_DEFAULT_RC\033[0m to " - "\033[1mFALSE\033[0m.\n") + "\033[1mboth\033[0m the default and your custom rc.conf,\n" + " please set the environment variable " + "\033[1mRANGER_LOAD_DEFAULT_RC\033[0m to " + "\033[1mFALSE\033[0m.\n") else: sys.stderr.write("Unknown config file `%s'\n" % which) - def confpath(self, *paths): + @staticmethod + def confpath(*paths): """returns the path relative to rangers configuration directory""" - if ranger.arg.clean: + if ranger.args.clean: assert 0, "Should not access relpath_conf in clean mode!" else: - return os.path.join(ranger.arg.confdir, *paths) + return os.path.join(ranger.args.confdir, *paths) - def relpath(self, *paths): + @staticmethod + def relpath(*paths): """returns the path relative to rangers library directory""" return os.path.join(ranger.RANGERDIR, *paths) @@ -319,7 +323,9 @@ class FM(Actions, SignalDispatcher): self.directories[path] = obj return obj - def garbage_collect(self, age, tabs=None): # tabs=None is for COMPATibility + def garbage_collect( + self, age, + tabs=None): # tabs=None is for COMPATibility pylint: disable=unused-argument """Delete unused directory objects""" for key in tuple(self.directories): value = self.directories[key] @@ -346,8 +352,6 @@ class FM(Actions, SignalDispatcher): self.enter_dir(self.thistab.path) - gc_tick = 0 - # for faster lookup: ui = self.ui throbber = ui.throbber @@ -357,7 +361,7 @@ class FM(Actions, SignalDispatcher): ranger.api.hook_ready(self) - try: + try: # pylint: disable=too-many-nested-blocks while True: loader.work() if has_throbber: @@ -379,10 +383,10 @@ class FM(Actions, SignalDispatcher): if zombie.poll() is not None: zombies.remove(zombie) - #gc_tick += 1 - #if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: - #gc_tick = 0 - #self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) + # gc_tick += 1 + # if gc_tick > ranger.TICKS_BEFORE_COLLECTING_GARBAGE: + # gc_tick = 0 + # self.garbage_collect(ranger.TIME_BEFORE_FILE_BECOMES_GARBAGE) except KeyboardInterrupt: # this only happens in --debug mode. By default, interrupts @@ -391,9 +395,9 @@ class FM(Actions, SignalDispatcher): finally: self.image_displayer.quit() - if ranger.arg.choosedir and self.thisdir and self.thisdir.path: + if ranger.args.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed - open(ranger.arg.choosedir, 'w').write(self.thisdir.path) + open(ranger.args.choosedir, 'w').write(self.thisdir.path) self.bookmarks.remember(self.thisdir) self.bookmarks.save() diff --git a/ranger/core/linemode.py b/ranger/core/linemode.py index c7839723..8b225787 100644 --- a/ranger/core/linemode.py +++ b/ranger/core/linemode.py @@ -3,8 +3,11 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. # Author: Wojciech Siewierski <wojciech.siewierski@onet.pl>, 2015 +from __future__ import (absolute_import, print_function) + import sys -from abc import * + +from abc import ABCMeta, abstractproperty, abstractmethod from datetime import datetime from ranger.ext.human_readable import human_readable from ranger.ext import spawn @@ -32,11 +35,11 @@ class LinemodeBase(object): name = abstractproperty() @abstractmethod - def filetitle(self, file, metadata): + def filetitle(self, fobj, metadata): """The left-aligned part of the line.""" raise NotImplementedError - def infostring(self, file, metadata): + def infostring(self, fobj, metadata): """The right-aligned part of the line. If `NotImplementedError' is raised (e.g. this method is just @@ -51,11 +54,11 @@ class LinemodeBase(object): raise NotImplementedError -class DefaultLinemode(LinemodeBase): +class DefaultLinemode(LinemodeBase): # pylint: disable=abstract-method name = "filename" - def filetitle(self, file, metadata): - return file.relative_path + def filetitle(self, fobj, metadata): + return fobj.relative_path class TitleLinemode(LinemodeBase): @@ -63,14 +66,13 @@ class TitleLinemode(LinemodeBase): uses_metadata = True required_metadata = ["title"] - def filetitle(self, file, metadata): + def filetitle(self, fobj, metadata): name = metadata.title if metadata.year: return "%s - %s" % (metadata.year, name) - else: - return name + return name - def infostring(self, file, metadata): + def infostring(self, fobj, metadata): if metadata.authors: authorstring = metadata.authors if ',' in authorstring: @@ -82,25 +84,25 @@ class TitleLinemode(LinemodeBase): class PermissionsLinemode(LinemodeBase): name = "permissions" - def filetitle(self, file, metadata): - return "%s %s %s %s" % (file.get_permission_string(), - file.user, file.group, file.relative_path) + def filetitle(self, fobj, metadata): + return "%s %s %s %s" % ( + fobj.get_permission_string(), fobj.user, fobj.group, fobj.relative_path) - def infostring(self, file, metadata): + def infostring(self, fobj, metadata): return "" class FileInfoLinemode(LinemodeBase): name = "fileinfo" - def filetitle(self, file, metadata): - return file.relative_path + def filetitle(self, fobj, metadata): + return fobj.relative_path - def infostring(self, file, metadata): - if not file.is_directory: - from subprocess import Popen, PIPE, CalledProcessError + def infostring(self, fobj, metadata): + if not fobj.is_directory: + from subprocess import CalledProcessError try: - fileinfo = spawn.check_output(["file", "-bL", file.path]).strip() + fileinfo = spawn.check_output(["file", "-bL", fobj.path]).strip() except CalledProcessError: return "unknown" if sys.version_info[0] >= 3: @@ -113,19 +115,19 @@ class FileInfoLinemode(LinemodeBase): class MtimeLinemode(LinemodeBase): name = "mtime" - def filetitle(self, file, metadata): - return file.relative_path + def filetitle(self, fobj, metadata): + return fobj.relative_path - def infostring(self, file, metadata): - return datetime.fromtimestamp(file.stat.st_mtime).strftime("%Y-%m-%d %H:%M") + def infostring(self, fobj, metadata): + return datetime.fromtimestamp(fobj.stat.st_mtime).strftime("%Y-%m-%d %H:%M") class SizeMtimeLinemode(LinemodeBase): name = "sizemtime" - def filetitle(self, file, metadata): - return file.relative_path + def filetitle(self, fobj, metadata): + return fobj.relative_path - def infostring(self, file, metadata): - return "%s %s" % (human_readable(file.size), - datetime.fromtimestamp(file.stat.st_mtime).strftime("%Y-%m-%d %H:%M")) + def infostring(self, fobj, metadata): + return "%s %s" % (human_readable(fobj.size), + datetime.fromtimestamp(fobj.stat.st_mtime).strftime("%Y-%m-%d %H:%M")) diff --git a/ranger/core/loader.py b/ranger/core/loader.py index 0cdc5368..db1f0651 100644 --- a/ranger/core/loader.py +++ b/ranger/core/loader.py @@ -1,18 +1,22 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from collections import deque -from time import time, sleep from subprocess import Popen, PIPE -from ranger.core.shared import FileManagerAware -from ranger.ext.signals import SignalDispatcher -from ranger.ext.human_readable import human_readable +from time import time, sleep import math import os.path -import sys import select +import sys +import errno + +from ranger.core.shared import FileManagerAware +from ranger.ext.signals import SignalDispatcher +from ranger.ext.human_readable import human_readable try: - import chardet + import chardet # pylint: disable=import-error HAVE_CHARDET = True except Exception: HAVE_CHARDET = False @@ -43,7 +47,7 @@ class Loadable(object): pass -class CopyLoader(Loadable, FileManagerAware): +class CopyLoader(Loadable, FileManagerAware): # pylint: disable=too-many-instance-attributes progressbar_supported = True def __init__(self, copy_buffer, do_cut=False, overwrite=False): @@ -60,7 +64,7 @@ class CopyLoader(Loadable, FileManagerAware): def _calculate_size(self, step): from os.path import join size = 0 - stack = [f.path for f in self.copy_buffer] + stack = [fobj.path for fobj in self.copy_buffer] while stack: fname = stack.pop() if os.path.islink(fname): @@ -89,49 +93,51 @@ class CopyLoader(Loadable, FileManagerAware): self.description = "moving: " + self.one_file.path + size_str else: self.description = "moving files from: " + self.one_file.dirname + size_str - for f in self.copy_buffer: - for tf in self.fm.tags.tags: - if tf == f.path or str(tf).startswith(f.path): - tag = self.fm.tags.tags[tf] - self.fm.tags.remove(tf) - self.fm.tags.tags[tf.replace(f.path, self.original_path - + '/' + f.basename)] = tag + for fobj in self.copy_buffer: + for path in self.fm.tags.tags: + if path == fobj.path or str(path).startswith(fobj.path): + tag = self.fm.tags.tags[path] + self.fm.tags.remove(path) + self.fm.tags.tags[ + path.replace(fobj.path, self.original_path + '/' + fobj.basename) + ] = tag self.fm.tags.dump() - d = 0 - for d in shutil_g.move(src=f.path, - dst=self.original_path, - overwrite=self.overwrite): - self.percent = float(done + d) / size * 100. + n = 0 + for n in shutil_g.move(src=fobj.path, dst=self.original_path, + overwrite=self.overwrite): + self.percent = float(done + n) / size * 100. yield - done += d + done += n else: if len(self.copy_buffer) == 1: self.description = "copying: " + self.one_file.path + size_str else: self.description = "copying files from: " + self.one_file.dirname + size_str - for f in self.copy_buffer: - if os.path.isdir(f.path) and not os.path.islink(f.path): - d = 0 - for d in shutil_g.copytree(src=f.path, - dst=os.path.join(self.original_path, f.basename), + for fobj in self.copy_buffer: + if os.path.isdir(fobj.path) and not os.path.islink(fobj.path): + n = 0 + for n in shutil_g.copytree( + src=fobj.path, + dst=os.path.join(self.original_path, fobj.basename), symlinks=True, - overwrite=self.overwrite): - self.percent = float(done + d) / size * 100. + overwrite=self.overwrite, + ): + self.percent = float(done + n) / size * 100. yield - done += d + done += n else: - d = 0 - for d in shutil_g.copy2(f.path, self.original_path, - symlinks=True, - overwrite=self.overwrite): - self.percent = float(done + d) / size * 100. + n = 0 + for n in shutil_g.copy2(fobj.path, self.original_path, + symlinks=True, overwrite=self.overwrite): + self.percent = float(done + n) / size * 100. yield - done += d + done += n cwd = self.fm.get_directory(self.original_path) cwd.load_content() -class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): +class CommandLoader( # pylint: disable=too-many-instance-attributes + Loadable, SignalDispatcher, FileManagerAware): """Run an external command with the loader. Output from stderr will be reported. Ensure that the process doesn't @@ -141,8 +147,9 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): finished = False process = None - def __init__(self, args, descr, silent=False, read=False, input=None, - kill_on_pause=False, popenArgs=None): + def __init__(self, args, descr, # pylint: disable=too-many-arguments + silent=False, read=False, input=None, # pylint: disable=redefined-builtin + kill_on_pause=False, popenArgs=None): SignalDispatcher.__init__(self) Loadable.__init__(self, self.generate(), descr) self.args = args @@ -151,18 +158,14 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): self.stdout_buffer = "" self.input = input self.kill_on_pause = kill_on_pause - self.popenArgs = popenArgs + self.popenArgs = popenArgs # pylint: disable=invalid-name - def generate(self): + def generate(self): # pylint: disable=too-many-branches,too-many-statements py3 = sys.version_info[0] >= 3 - if self.input: - stdin = PIPE - else: - stdin = open(os.devnull, 'r') - popenArgs = {} if self.popenArgs is None else self.popenArgs - popenArgs['stdout'] = popenArgs['stderr'] = PIPE - popenArgs['stdin'] = stdin - self.process = process = Popen(self.args, **popenArgs) + popenargs = {} if self.popenArgs is None else self.popenArgs + popenargs['stdout'] = popenargs['stderr'] = PIPE + popenargs['stdin'] = PIPE if self.input else open(os.devnull, 'r') + self.process = process = Popen(self.args, **popenargs) self.signal_emit('before', process=process, loader=self) if self.input: if py3: @@ -172,11 +175,11 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): stdin = process.stdin try: stdin.write(self.input) - except IOError as e: - if e.errno != errno.EPIPE and e.errno != errno.EINVAL: + except IOError as ex: + if ex.errno != errno.EPIPE and ex.errno != errno.EINVAL: raise stdin.close() - if self.silent and not self.read: + if self.silent and not self.read: # pylint: disable=too-many-nested-blocks while process.poll() is None: yield if self.finished: @@ -193,17 +196,17 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): if self.finished: break try: - rd, _, __ = select.select(selectlist, [], [], 0.03) - if rd: - rd = rd[0] - if rd == process.stderr: - read = rd.readline() + robjs, _, _ = select.select(selectlist, [], [], 0.03) + if robjs: + robjs = robjs[0] + if robjs == process.stderr: + read = robjs.readline() if py3: read = safeDecode(read) if read: self.fm.notify(read, bad=True) - elif rd == process.stdout: - read = rd.read(512) + elif robjs == process.stdout: + read = robjs.read(512) if py3: read = safeDecode(read) if read: @@ -259,15 +262,14 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): pass -def safeDecode(string): +def safeDecode(string): # pylint: disable=invalid-name try: return string.decode("utf-8") - except (UnicodeDecodeError): + except UnicodeDecodeError: if HAVE_CHARDET: codec = chardet.detect(string)["encoding"] return string.decode(codec, 'ignore') - else: - return "" + return "" class Loader(FileManagerAware): @@ -286,6 +288,7 @@ class Loader(FileManagerAware): self.throbber_status = 0 self.rotate() self.old_item = None + self.status = None def rotate(self): """Rotate the throbber""" @@ -314,19 +317,19 @@ class Loader(FileManagerAware): else: obj.unpause() - def move(self, _from, to): + def move(self, pos_src, pos_dest): try: - item = self.queue[_from] + item = self.queue[pos_src] except IndexError: return - del self.queue[_from] + del self.queue[pos_src] - if to == 0: + if pos_dest == 0: self.queue.appendleft(item) - if _from != 0: + if pos_src != 0: self.queue[1].pause() - elif to == -1: + elif pos_dest == -1: self.queue.append(item) else: raise NotImplementedError diff --git a/ranger/core/main.py b/ranger/core/main.py index b0e3b600..b3173da2 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -3,16 +3,23 @@ """The main function responsible to initialize the FM object and stuff.""" +from __future__ import (absolute_import, print_function) + import os.path import sys import tempfile -from ranger import __version__ from logging import getLogger -log = getLogger(__name__) +from ranger import __version__ + + +LOG = getLogger(__name__) -def main(): +def main( + # pylint: disable=too-many-locals,too-many-return-statements + # pylint: disable=too-many-branches,too-many-statements +): """initialize objects and run the filemanager""" import locale import ranger.api @@ -20,13 +27,15 @@ def main(): from ranger.core.shared import FileManagerAware, SettingsAware from ranger.core.fm import FM from ranger.ext.logutils import setup_logging + from ranger.ext.openstruct import OpenStruct - ranger.arg = arg = parse_arguments() - setup_logging(debug=arg.debug, logfile=arg.logfile) + ranger.args = args = parse_arguments() + ranger.arg = OpenStruct(args.__dict__) # COMPAT + setup_logging(debug=args.debug, logfile=args.logfile) - log.info("Ranger version {0}".format(__version__)) - log.info('Running on Python ' + sys.version.replace('\n', '')) - log.info("Process ID is {0}".format(os.getpid())) + LOG.info("Ranger version %s", __version__) + LOG.info('Running on Python ' + sys.version.replace('\n', '')) + LOG.info("Process ID is %s", os.getpid()) try: locale.setlocale(locale.LC_ALL, '') @@ -43,40 +52,40 @@ def main(): if 'SHELL' not in os.environ: os.environ['SHELL'] = 'sh' - log.debug("config dir: '{0}'".format(arg.confdir)) - log.debug("cache dir: '{0}'".format(arg.cachedir)) + LOG.debug("config dir: '%s'", args.confdir) + LOG.debug("cache dir: '%s'", args.cachedir) - if arg.copy_config is not None: + if args.copy_config is not None: fm = FM() - fm.copy_config_files(arg.copy_config) - return 1 if arg.fail_unless_cd else 0 # COMPAT - if arg.list_tagged_files: + fm.copy_config_files(args.copy_config) + return 1 if args.fail_unless_cd else 0 # COMPAT + if args.list_tagged_files: fm = FM() try: if sys.version_info[0] >= 3: - f = open(fm.confpath('tagged'), 'r', errors='replace') + fobj = open(fm.confpath('tagged'), 'r', errors='replace') else: - f = open(fm.confpath('tagged'), 'r') + fobj = open(fm.confpath('tagged'), 'r') except Exception: pass else: - for line in f.readlines(): + for line in fobj.readlines(): if len(line) > 2 and line[1] == ':': - if line[0] in arg.list_tagged_files: + if line[0] in args.list_tagged_files: sys.stdout.write(line[2:]) - elif len(line) > 0 and '*' in arg.list_tagged_files: + elif line and '*' in args.list_tagged_files: sys.stdout.write(line) - return 1 if arg.fail_unless_cd else 0 # COMPAT + return 1 if args.fail_unless_cd else 0 # COMPAT - SettingsAware._setup(Settings()) + SettingsAware._setup(Settings()) # pylint: disable=protected-access - if arg.selectfile: - arg.selectfile = os.path.abspath(arg.selectfile) - arg.targets.insert(0, os.path.dirname(arg.selectfile)) + if args.selectfile: + args.selectfile = os.path.abspath(args.selectfile) + args.targets.insert(0, os.path.dirname(args.selectfile)) - targets = arg.targets or ['.'] + targets = args.targets or ['.'] target = targets[0] - if arg.targets: # COMPAT + if args.targets: # COMPAT if target.startswith('file://'): target = target[7:] if not os.access(target, os.F_OK): @@ -84,38 +93,38 @@ def main(): return 1 elif os.path.isfile(target): sys.stderr.write("Warning: Using ranger as a file launcher is " - "deprecated.\nPlease use the standalone file launcher " - "'rifle' instead.\n") + "deprecated.\nPlease use the standalone file launcher " + "'rifle' instead.\n") from ranger.ext.rifle import Rifle fm = FM() - if not arg.clean and os.path.isfile(fm.confpath('rifle.conf')): + if not args.clean and os.path.isfile(fm.confpath('rifle.conf')): rifleconf = fm.confpath('rifle.conf') else: rifleconf = fm.relpath('config/rifle.conf') rifle = Rifle(rifleconf) rifle.reload_config() - rifle.execute(targets, number=ranger.arg.mode, flags=ranger.arg.flags) - return 1 if arg.fail_unless_cd else 0 # COMPAT + rifle.execute(targets, number=ranger.args.mode, flags=ranger.args.flags) + return 1 if args.fail_unless_cd else 0 # COMPAT crash_traceback = None try: # Initialize objects fm = FM(paths=targets) - FileManagerAware._setup(fm) - load_settings(fm, arg.clean) + FileManagerAware._setup(fm) # pylint: disable=protected-access + load_settings(fm, args.clean) - if arg.list_unused_keys: + if args.list_unused_keys: from ranger.ext.keybinding_parser import (special_keys, - reversed_special_keys) + reversed_special_keys) maps = fm.ui.keymaps['browser'] - for key in sorted(special_keys.values(), key=lambda x: str(x)): + for key in sorted(special_keys.values(), key=str): if key not in maps: print("<%s>" % reversed_special_keys[key]) for key in range(33, 127): if key not in maps: print(chr(key)) - return 1 if arg.fail_unless_cd else 0 # COMPAT + return 1 if args.fail_unless_cd else 0 # COMPAT if not sys.stdin.isatty(): sys.stderr.write("Error: Must run ranger from terminal\n") @@ -124,33 +133,33 @@ def main(): if fm.username == 'root': fm.settings.preview_files = False fm.settings.use_preview_script = False - log.info("Running as root, disabling the file previews.") - if not arg.debug: + LOG.info("Running as root, disabling the file previews.") + if not args.debug: from ranger.ext import curses_interrupt_handler curses_interrupt_handler.install_interrupt_handler() # Create cache directory if fm.settings.preview_images and fm.settings.use_preview_script: - if not os.path.exists(arg.cachedir): - os.makedirs(arg.cachedir) + if not os.path.exists(args.cachedir): + os.makedirs(args.cachedir) # Run the file manager fm.initialize() ranger.api.hook_init(fm) fm.ui.initialize() - if arg.selectfile: - fm.select_file(arg.selectfile) + if args.selectfile: + fm.select_file(args.selectfile) - if arg.cmd: - for command in arg.cmd: + if args.cmd: + for command in args.cmd: fm.execute_console(command) - if ranger.arg.profile: + if ranger.args.profile: import cProfile import pstats profile = None - ranger.__fm = fm + ranger.__fm = fm # pylint: disable=protected-access cProfile.run('ranger.__fm.loop()', tempfile.gettempdir() + '/ranger_profile') profile = pstats.Stats(tempfile.gettempdir() + '/ranger_profile', stream=sys.stderr) else: @@ -170,11 +179,11 @@ def main(): fm.ui.destroy() except (AttributeError, NameError): pass - if ranger.arg.profile and profile: + if ranger.args.profile and profile: profile.strip_dirs().sort_stats('cumulative').print_callees() if crash_traceback: print("ranger version: %s, executed with python %s" % - (ranger.__version__, sys.version.split()[0])) + (ranger.__version__, sys.version.split()[0])) print("Locale: %s" % '.'.join(str(s) for s in locale.getlocale())) try: print("Current file: %s" % filepath) @@ -182,18 +191,17 @@ def main(): pass print(crash_traceback) print("ranger crashed. " - "Please report this traceback at:") + "Please report this traceback at:") print("https://github.com/hut/ranger/issues") - return 1 - return 0 + return 1 # pylint: disable=lost-exception + return 0 # pylint: disable=lost-exception def parse_arguments(): """Parse the program arguments""" - from optparse import OptionParser, SUPPRESS_HELP + from optparse import OptionParser, SUPPRESS_HELP # pylint: disable=deprecated-module from os.path import expanduser from ranger import CONFDIR, CACHEDIR, USAGE, VERSION - from ranger.ext.openstruct import OpenStruct if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']: default_confdir = os.environ['XDG_CONFIG_HOME'] + '/ranger' @@ -208,75 +216,78 @@ def parse_arguments(): parser = OptionParser(usage=USAGE, version=VERSION) parser.add_option('-d', '--debug', action='store_true', - help="activate debug mode") + help="activate debug mode") parser.add_option('-c', '--clean', action='store_true', - help="don't touch/require any config files. ") + help="don't touch/require any config files. ") parser.add_option('--logfile', type='string', metavar='file', - help="log file to use, '-' for stderr") + help="log file to use, '-' for stderr") parser.add_option('-r', '--confdir', type='string', - metavar='dir', default=default_confdir, - help="change the configuration directory. (%default)") + metavar='dir', default=default_confdir, + help="change the configuration directory. (%default)") parser.add_option('--copy-config', type='string', metavar='which', - help="copy the default configs to the local config directory. " - "Possible values: all, rc, rifle, commands, commands_full, scope") + help="copy the default configs to the local config directory. " + "Possible values: all, rc, rifle, commands, commands_full, scope") parser.add_option('--fail-unless-cd', action='store_true', - help=SUPPRESS_HELP) # COMPAT + help=SUPPRESS_HELP) # COMPAT parser.add_option('-m', '--mode', type='int', default=0, metavar='n', - help=SUPPRESS_HELP) # COMPAT + help=SUPPRESS_HELP) # COMPAT parser.add_option('-f', '--flags', type='string', default='', - metavar='string', help=SUPPRESS_HELP) # COMPAT + metavar='string', help=SUPPRESS_HELP) # COMPAT parser.add_option('--choosefile', type='string', metavar='TARGET', - help="Makes ranger act like a file chooser. When opening " - "a file, it will quit and write the name of the selected " - "file to TARGET.") + help="Makes ranger act like a file chooser. When opening " + "a file, it will quit and write the name of the selected " + "file to TARGET.") parser.add_option('--choosefiles', type='string', metavar='TARGET', - help="Makes ranger act like a file chooser for multiple files " - "at once. When opening a file, it will quit and write the name " - "of all selected files to TARGET.") + help="Makes ranger act like a file chooser for multiple files " + "at once. When opening a file, it will quit and write the name " + "of all selected files to TARGET.") parser.add_option('--choosedir', type='string', metavar='TARGET', - help="Makes ranger act like a directory chooser. When ranger quits" - ", it will write the name of the last visited directory to TARGET") + help="Makes ranger act like a directory chooser. When ranger quits" + ", it will write the name of the last visited directory to TARGET") parser.add_option('--selectfile', type='string', metavar='filepath', - help="Open ranger with supplied file selected.") + help="Open ranger with supplied file selected.") parser.add_option('--list-unused-keys', action='store_true', - help="List common keys which are not bound to any action.") + help="List common keys which are not bound to any action.") parser.add_option('--list-tagged-files', type='string', default=None, - metavar='tag', - help="List all files which are tagged with the given tag, default: *") + metavar='tag', + help="List all files which are tagged with the given tag, default: *") parser.add_option('--profile', action='store_true', - help="Print statistics of CPU usage on exit.") + help="Print statistics of CPU usage on exit.") parser.add_option('--cmd', action='append', type='string', metavar='COMMAND', - help="Execute COMMAND after the configuration has been read. " - "Use this option multiple times to run multiple commands.") + help="Execute COMMAND after the configuration has been read. " + "Use this option multiple times to run multiple commands.") - options, positional = parser.parse_args() - arg = OpenStruct(options.__dict__, targets=positional) - arg.confdir = expanduser(arg.confdir) - arg.cachedir = expanduser(default_cachedir) + args, positional = parser.parse_args() + args.targets = positional + args.confdir = expanduser(args.confdir) + args.cachedir = expanduser(default_cachedir) - if arg.fail_unless_cd: # COMPAT + if args.fail_unless_cd: # COMPAT sys.stderr.write("Warning: The option --fail-unless-cd is deprecated.\n" - "It was used to facilitate using ranger as a file launcher.\n" - "Now, please use the standalone file launcher 'rifle' instead.\n") + "It was used to facilitate using ranger as a file launcher.\n" + "Now, please use the standalone file launcher 'rifle' instead.\n") + + return args + - return arg +COMMANDS_EXCLUDE = ['settings', 'notify'] -def load_settings(fm, clean): +def load_settings( # pylint: disable=too-many-locals,too-many-branches,too-many-statements + fm, clean): from ranger.core.actions import Actions import ranger.core.shared import ranger.api.commands - from ranger.config import commands + from ranger.config import commands as commands_default # Load default commands fm.commands = ranger.api.commands.CommandContainer() - exclude = ['settings', 'notify'] - include = [name for name in dir(Actions) if name not in exclude] + include = [name for name in dir(Actions) if name not in COMMANDS_EXCLUDE] fm.commands.load_commands_from_object(fm, include) - fm.commands.load_commands_from_module(commands) + fm.commands.load_commands_from_module(commands_default) if not clean: - allow_access_to_confdir(ranger.arg.confdir, True) + allow_access_to_confdir(ranger.args.confdir, True) # Load custom commands custom_comm_path = fm.confpath('commands.py') @@ -284,16 +295,16 @@ def load_settings(fm, clean): old_bytecode_setting = sys.dont_write_bytecode sys.dont_write_bytecode = True try: - import commands - fm.commands.load_commands_from_module(commands) - except ImportError as e: - log.debug("Failed to import custom commands from '{0}'".format(custom_comm_path)) - log.exception(e) + import commands as commands_custom + fm.commands.load_commands_from_module(commands_custom) + except ImportError as ex: + LOG.debug("Failed to import custom commands from '%s'", custom_comm_path) + LOG.exception(ex) else: - log.debug("Loaded custom commands from '{0}'".format(custom_comm_path)) + LOG.debug("Loaded custom commands from '%s'", custom_comm_path) sys.dont_write_bytecode = old_bytecode_setting - allow_access_to_confdir(ranger.arg.confdir, False) + allow_access_to_confdir(ranger.args.confdir, False) # Load rc.conf custom_conf = fm.confpath('rc.conf') @@ -304,20 +315,20 @@ def load_settings(fm, clean): if os.access(custom_conf, os.R_OK): fm.source(custom_conf) - allow_access_to_confdir(ranger.arg.confdir, True) + allow_access_to_confdir(ranger.args.confdir, True) # XXX Load plugins (experimental) try: plugindir = fm.confpath('plugins') plugins = [p[:-3] for p in os.listdir(plugindir) - if p.endswith('.py') and not p.startswith('_')] + if p.endswith('.py') and not p.startswith('_')] except Exception: pass else: if not os.path.exists(fm.confpath('plugins', '__init__.py')): - log.debug("Creating missing '__init__.py' file in plugin folder") - f = open(fm.confpath('plugins', '__init__.py'), 'w') - f.close() + LOG.debug("Creating missing '__init__.py' file in plugin folder") + fobj = open(fm.confpath('plugins', '__init__.py'), 'w') + fobj.close() ranger.fm = fm for plugin in sorted(plugins): @@ -332,12 +343,12 @@ def load_settings(fm, clean): else: module = importlib.import_module('plugins.' + plugin) fm.commands.load_commands_from_module(module) - log.debug("Loaded plugin '{0}'".format(plugin)) - except Exception as e: - mex = "Error while loading plugin '{0}'".format(plugin) - log.error(mex) - log.exception(e) - fm.notify(mex, bad=True) + LOG.debug("Loaded plugin '%s'", plugin) + except Exception as ex: + ex_msg = "Error while loading plugin '{0}'".format(plugin) + LOG.error(ex_msg) + LOG.exception(ex) + fm.notify(ex_msg, bad=True) ranger.fm = None # COMPAT: Load the outdated options.py @@ -350,7 +361,7 @@ def load_settings(fm, clean): fm.settings[setting] = getattr(module, setting) sys.stderr.write( -"""****************************** + """****************************** Warning: The configuration file 'options.py' is deprecated. Please move all settings to the file 'rc.conf', converting lines like "preview_files = False" @@ -361,13 +372,12 @@ copy & paste it to a .py file in ~/.config/ranger/plugins/. Remove the options.py or discard stderr to get rid of this warning. ******************************\n""") - allow_access_to_confdir(ranger.arg.confdir, False) + allow_access_to_confdir(ranger.args.confdir, False) else: fm.source(fm.relpath('config', 'rc.conf')) def allow_access_to_confdir(confdir, allow): - import sys from errno import EEXIST if allow: @@ -381,7 +391,7 @@ def allow_access_to_confdir(confdir, allow): print("files, use the --clean option.") raise SystemExit() else: - log.debug("Created config directory '{0}'".format(confdir)) + LOG.debug("Created config directory '%s'", confdir) if confdir not in sys.path: sys.path[0:0] = [confdir] else: diff --git a/ranger/core/metadata.py b/ranger/core/metadata.py index 299d2b85..76ff2bb9 100644 --- a/ranger/core/metadata.py +++ b/ranger/core/metadata.py @@ -11,15 +11,19 @@ The database is contained in a local .metadata.json file. # TODO: Update metadata keys if a file gets renamed/moved # TODO: A global metadata file, maybe as a replacement for tags -METADATA_FILE_NAME = ".metadata.json" -DEEP_SEARCH_DEFAULT = False +from __future__ import (absolute_import, print_function) import copy from os.path import join, dirname, exists, basename from ranger.ext.openstruct import DefaultOpenStruct as ostruct +METADATA_FILE_NAME = ".metadata.json" +DEEP_SEARCH_DEFAULT = False + + class MetadataManager(object): + def __init__(self): # metadata_cache maps filenames to dicts containing their metadata self.metadata_cache = dict() @@ -41,10 +45,6 @@ class MetadataManager(object): return ostruct() def set_metadata(self, filename, update_dict): - import json - result = None - found = False - if not self.deep_search: metafile = next(self._get_metafile_names(filename)) return self._set_metadata_raw(filename, update_dict, metafile) @@ -54,7 +54,6 @@ class MetadataManager(object): def _set_metadata_raw(self, filename, update_dict, metafile): import json - valid = (filename, basename(filename)) entries = self._get_metafile_content(metafile) try: @@ -85,14 +84,13 @@ class MetadataManager(object): self.metadata_cache[filename] = entry self.metafile_cache[metafile] = entries - with open(metafile, "w") as f: - json.dump(entries, f, check_circular=True, indent=2) + with open(metafile, "w") as fobj: + json.dump(entries, fobj, check_circular=True, indent=2) def _get_entry(self, filename): if filename in self.metadata_cache: return self.metadata_cache[filename] else: - valid = (filename, basename(filename)) # Try to find an entry for this file in any of # the applicable .metadata.json files @@ -115,20 +113,20 @@ class MetadataManager(object): def _get_metafile_content(self, metafile): import json + if metafile in self.metafile_cache: return self.metafile_cache[metafile] - else: - if exists(metafile): - with open(metafile, "r") as f: - try: - entries = json.load(f) - except ValueError: - raise ValueError("Failed decoding JSON file %s" % - metafile) - self.metafile_cache[metafile] = entries - return entries - else: - return {} + + if exists(metafile): + with open(metafile, "r") as fobj: + try: + entries = json.load(fobj) + except ValueError: + raise ValueError("Failed decoding JSON file %s" % metafile) + self.metafile_cache[metafile] = entries + return entries + + return {} def _get_metafile_names(self, path): # Iterates through the paths of all .metadata.json files that could diff --git a/ranger/core/runner.py b/ranger/core/runner.py index 78d52764..ee182c29 100644 --- a/ranger/core/runner.py +++ b/ranger/core/runner.py @@ -22,6 +22,8 @@ t: run application in a new terminal window (An uppercase key negates the respective lower case flag) """ +from __future__ import (absolute_import, print_function) + import os import sys from subprocess import Popen, PIPE @@ -30,7 +32,7 @@ from ranger.ext.popen_forked import Popen_forked # TODO: Remove unused parts of runner.py -#ALLOWED_FLAGS = 'sdpwcrtSDPWCRT' +# ALLOWED_FLAGS = 'sdpwcrtSDPWCRT' ALLOWED_FLAGS = 'cfrtCFRT' @@ -45,7 +47,7 @@ def press_enter(): waitfnc() -class Context(object): +class Context(object): # pylint: disable=too-many-instance-attributes """A context object contains data on how to run a process. The attributes are: @@ -62,8 +64,18 @@ class Context(object): popen_kws -- keyword arguments which are directly passed to Popen """ - def __init__(self, **keywords): - self.__dict__ = keywords + def __init__( # pylint: disable=redefined-builtin,too-many-arguments + self, action=None, app=None, mode=None, flags=None, + files=None, file=None, fm=None, wait=None, popen_kws=None): + self.action = action + self.app = app + self.mode = mode + self.flags = flags + self.files = files + self.file = file + self.fm = fm + self.wait = wait + self.popen_kws = popen_kws @property def filepaths(self): @@ -85,7 +97,8 @@ class Context(object): self.flags = ''.join(c for c in self.flags if c not in bad) -class Runner(object): +class Runner(object): # pylint: disable=too-few-public-methods + def __init__(self, ui=None, logfunc=None, fm=None): self.ui = ui self.fm = fm @@ -112,7 +125,10 @@ class Runner(object): except Exception: self._log("Failed to suspend UI") - def __call__(self, action=None, try_app_first=False, + def __call__( + # pylint: disable=too-many-branches,too-many-statements + # pylint: disable=too-many-arguments,too-many-locals + 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. @@ -128,8 +144,8 @@ class Runner(object): # an Application object. context = Context(app=app, files=files, mode=mode, fm=self.fm, - flags=flags, wait=wait, popen_kws=popen_kws, - file=files and files[0] or None) + flags=flags, wait=wait, popen_kws=popen_kws, + file=files and files[0] or None) if action is None: return self._log("No way of determining the action!") @@ -211,7 +227,7 @@ class Runner(object): error = None process = None self.fm.signal_emit('runner.execute.before', - popen_kws=popen_kws, context=context) + popen_kws=popen_kws, context=context) try: if 'f' in context.flags: # This can fail and return False if os.fork() is not @@ -219,9 +235,9 @@ class Runner(object): Popen_forked(**popen_kws) else: process = Popen(**popen_kws) - except Exception as e: - error = e - self._log("Failed to run: %s\n%s" % (str(action), str(e))) + except Exception as ex: + error = ex + self._log("Failed to run: %s\n%s" % (str(action), str(ex))) else: if context.wait: process.wait() @@ -231,12 +247,12 @@ class Runner(object): press_enter() finally: self.fm.signal_emit('runner.execute.after', - popen_kws=popen_kws, context=context, error=error) + popen_kws=popen_kws, context=context, error=error) if devnull: devnull.close() 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 + return self(action='less', app='pager', # pylint: disable=lost-exception + try_app_first=True, stdin=process.stdout) + return process # pylint: disable=lost-exception diff --git a/ranger/core/shared.py b/ranger/core/shared.py index 38b0d35a..3595aede 100644 --- a/ranger/core/shared.py +++ b/ranger/core/shared.py @@ -3,17 +3,19 @@ """Shared objects contain singletons for shared use.""" -from ranger.ext.lazy_property import lazy_property +from __future__ import (absolute_import, print_function) +from ranger.ext.lazy_property import lazy_property # NOQA pylint: disable=unused-import -class FileManagerAware(object): + +class FileManagerAware(object): # pylint: disable=too-few-public-methods """Subclass this to gain access to the global "FM" object.""" @staticmethod def _setup(fm): FileManagerAware.fm = fm -class SettingsAware(object): +class SettingsAware(object): # pylint: disable=too-few-public-methods """Subclass this to gain access to the global "SettingObject" object.""" @staticmethod def _setup(settings): diff --git a/ranger/core/tab.py b/ranger/core/tab.py index fa40a12b..60097ced 100644 --- a/ranger/core/tab.py +++ b/ranger/core/tab.py @@ -1,17 +1,19 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import os -import sys from os.path import abspath, normpath, join, expanduser, isdir +import sys from ranger.container import settings from ranger.container.history import History from ranger.core.shared import FileManagerAware, SettingsAware -from ranger.ext.signals import SignalDispatcher -class Tab(FileManagerAware, SettingsAware): +class Tab(FileManagerAware, SettingsAware): # pylint: disable=too-many-instance-attributes + def __init__(self, path): self.thisdir = None # Current Working Directory self._thisfile = None # Current File @@ -24,10 +26,10 @@ class Tab(FileManagerAware, SettingsAware): # weak references are not equal to the original object when tested with # "==", and this breaks _set_thisfile_from_signal and _on_tab_change. self.fm.signal_bind('move', self._set_thisfile_from_signal, - priority=settings.SIGNAL_PRIORITY_AFTER_SYNC, - weak=(sys.version_info[0] >= 3)) + priority=settings.SIGNAL_PRIORITY_AFTER_SYNC, + weak=(sys.version_info[0] >= 3)) self.fm.signal_bind('tab.change', self._on_tab_change, - weak=(sys.version_info[0] >= 3)) + weak=(sys.version_info[0] >= 3)) def _set_thisfile_from_signal(self, signal): if self == signal.tab: @@ -65,7 +67,7 @@ class Tab(FileManagerAware, SettingsAware): return None else: directory = self.thisdir - for i in range(level): + for _ in range(level): if directory is None: return None if directory.is_directory: @@ -82,7 +84,7 @@ class Tab(FileManagerAware, SettingsAware): return [self._thisfile] return [] - def assign_cursor_positions_for_subdirs(self): + def assign_cursor_positions_for_subdirs(self): # pylint: disable=invalid-name """Assign correct cursor positions for subdirectories""" last_path = None for path in reversed(self.pathway): @@ -142,8 +144,8 @@ class Tab(FileManagerAware, SettingsAware): else: pathway = [] currentpath = '/' - for dir in path.split('/'): - currentpath = join(currentpath, dir) + for comp in path.split('/'): + currentpath = join(currentpath, comp) pathway.append(self.fm.get_directory(currentpath)) self.pathway = tuple(pathway) diff --git a/ranger/ext/accumulator.py b/ranger/ext/accumulator.py index 863a59df..d75363ca 100644 --- a/ranger/ext/accumulator.py +++ b/ranger/ext/accumulator.py @@ -1,10 +1,13 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from ranger.ext.direction import Direction class Accumulator(object): + def __init__(self): self.pointer = 0 self.pointed_obj = None @@ -15,11 +18,11 @@ class Accumulator(object): if not lst: return self.pointer pointer = direction.move( - direction=direction.down(), - maximum=len(lst), - override=narg, - pagesize=self.get_height(), - current=self.pointer) + direction=direction.down(), + maximum=len(lst), + override=narg, + pagesize=self.get_height(), + current=self.pointer) self.pointer = pointer self.correct_pointer() return pointer @@ -88,10 +91,12 @@ class Accumulator(object): def sync_index(self, **kw): self.move_to_obj(self.pointed_obj, **kw) - def get_list(self): + @staticmethod + def get_list(): """OVERRIDE THIS""" return [] - def get_height(self): + @staticmethod + def get_height(): """OVERRIDE THIS""" return 25 diff --git a/ranger/ext/cached_function.py b/ranger/ext/cached_function.py index 6c1c7769..c5a4cb47 100644 --- a/ranger/ext/cached_function.py +++ b/ranger/ext/cached_function.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + def cached_function(fnc): cache = {} @@ -12,5 +14,5 @@ def cached_function(fnc): value = fnc(*args) cache[args] = value return value - inner_cached_function._cache = cache + inner_cached_function._cache = cache # pylint: disable=protected-access return inner_cached_function diff --git a/ranger/ext/curses_interrupt_handler.py b/ranger/ext/curses_interrupt_handler.py index 606cbd62..e31503b8 100644 --- a/ranger/ext/curses_interrupt_handler.py +++ b/ranger/ext/curses_interrupt_handler.py @@ -8,15 +8,17 @@ rise a KeyboardInterrupt exception and handle it by pushing a Ctrl+C (ASCII value 3) to the curses getch stack. """ +from __future__ import (absolute_import, print_function) + import curses import signal -_do_catch_interrupt = True +_do_catch_interrupt = True # pylint: disable=invalid-name def catch_interrupt(boolean=True): """Should interrupts be caught and simulate a ^C press in curses?""" - global _do_catch_interrupt + global _do_catch_interrupt # pylint: disable=global-statement,invalid-name old_value = _do_catch_interrupt _do_catch_interrupt = bool(boolean) return old_value @@ -24,15 +26,14 @@ def catch_interrupt(boolean=True): # The handler which will be used in signal.signal() -def _interrupt_handler(a1, a2): - global _do_catch_interrupt +def _interrupt_handler(signum, frame): # if a keyboard-interrupt occurs... if _do_catch_interrupt: # push a Ctrl+C (ascii value 3) to the curses getch stack curses.ungetch(3) else: # use the default handler - signal.default_int_handler(a1, a2) + signal.default_int_handler(signum, frame) def install_interrupt_handler(): diff --git a/ranger/ext/direction.py b/ranger/ext/direction.py index 787da1bf..5d2341c2 100644 --- a/ranger/ext/direction.py +++ b/ranger/ext/direction.py @@ -18,8 +18,11 @@ has been defined. False """ +from __future__ import (absolute_import, print_function) + class Direction(dict): + def __init__(self, dictionary=None, **keywords): if dictionary is not None: dict.__init__(self, dictionary) @@ -50,8 +53,8 @@ class Direction(dict): except Exception: return fallback - def up(self): - return -Direction.down(self) + def up(self): # pylint: disable=invalid-name + return -Direction.down(self) # pylint: disable=invalid-unary-operand-type def down(self): return Direction._get_direction(self, 'down', 'up') @@ -63,7 +66,7 @@ class Direction(dict): return Direction._get_bool(self, 'absolute', 'relative') def left(self): - return -Direction.right(self) + return -Direction.right(self) # pylint: disable=invalid-unary-operand-type def relative(self): return not Direction.absolute(self) @@ -103,8 +106,8 @@ class Direction(dict): if key in self: self[key] = n - def move(self, direction, override=None, minimum=0, maximum=9999, - current=0, pagesize=1, offset=0): + def move(self, direction, override=None, minimum=0, # pylint: disable=too-many-arguments + maximum=9999, current=0, pagesize=1, offset=0): """Calculates the new position in a given boundary. Example: @@ -142,10 +145,11 @@ class Direction(dict): def select(self, lst, current, pagesize, override=None, offset=1): dest = self.move(direction=self.down(), override=override, - current=current, pagesize=pagesize, minimum=0, maximum=len(lst) + 1) + current=current, pagesize=pagesize, minimum=0, maximum=len(lst) + 1) selection = lst[min(current, dest):max(current, dest) + offset] return dest + offset - 1, selection + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/ranger/ext/get_executables.py b/ranger/ext/get_executables.py index f2a31345..e077d149 100644 --- a/ranger/ext/get_executables.py +++ b/ranger/ext/get_executables.py @@ -1,18 +1,21 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from stat import S_IXOTH, S_IFREG -from ranger.ext.iter_tools import unique from os import listdir, environ, stat import shlex +from ranger.ext.iter_tools import unique + -_cached_executables = None +_cached_executables = None # pylint: disable=invalid-name def get_executables(): """Return all executable files in $PATH. Cached version.""" - global _cached_executables + global _cached_executables # pylint: disable=global-statement,invalid-name if _cached_executables is None: _cached_executables = get_executables_uncached() return _cached_executables diff --git a/ranger/ext/human_readable.py b/ranger/ext/human_readable.py index 31d5d279..71bc29b8 100644 --- a/ranger/ext/human_readable.py +++ b/ranger/ext/human_readable.py @@ -1,8 +1,10 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) -def human_readable(byte, separator=' '): + +def human_readable(byte, separator=' '): # pylint: disable=too-many-return-statements """Convert a large number of bytes to an easily readable format. >>> human_readable(54) @@ -19,7 +21,7 @@ def human_readable(byte, separator=' '): if byte <= 0: return '0' if byte < 2**10: - return '%d%sB' % (byte, separator) + return '%d%sB' % (byte, separator) if byte < 2**10 * 999: return '%.3g%sK' % (byte / 2**10.0, separator) if byte < 2**20: @@ -42,6 +44,7 @@ def human_readable(byte, separator=' '): return '%.4g%sP' % (byte / 2**50.0, separator) return '>9000' + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py index 0e6b08fe..7c75214e 100644 --- a/ranger/ext/img_display.py +++ b/ranger/ext/img_display.py @@ -9,18 +9,21 @@ This module provides functions to draw images in the terminal using supported implementations, which are currently w3m, iTerm2 and urxvt. """ +from __future__ import (absolute_import, print_function) + import base64 import curses import errno import fcntl import imghdr import os -import select import struct import sys +from subprocess import Popen, PIPE + import termios + from ranger.core.shared import FileManagerAware -from subprocess import Popen, PIPE W3MIMGDISPLAY_ENV = "W3MIMGDISPLAY_PATH" W3MIMGDISPLAY_OPTIONS = [] @@ -38,6 +41,7 @@ class ImgDisplayUnsupportedException(Exception): class ImageDisplayer(object): """Image display provider functions for drawing images in the terminal""" + def draw(self, path, start_x, start_y, width, height): """Draw an image at the given coordinates.""" pass @@ -61,35 +65,39 @@ class W3MImageDisplayer(ImageDisplayer): """ is_initialized = False + def __init__(self): + self.binary_path = None + self.process = None + def initialize(self): """start w3mimgdisplay""" self.binary_path = None self.binary_path = self._find_w3mimgdisplay_executable() # may crash self.process = Popen([self.binary_path] + W3MIMGDISPLAY_OPTIONS, - stdin=PIPE, stdout=PIPE, universal_newlines=True) + stdin=PIPE, stdout=PIPE, universal_newlines=True) self.is_initialized = True - def _find_w3mimgdisplay_executable(self): + @staticmethod + def _find_w3mimgdisplay_executable(): paths = [os.environ.get(W3MIMGDISPLAY_ENV, None)] + W3MIMGDISPLAY_PATHS for path in paths: if path is not None and os.path.exists(path): return path raise RuntimeError("No w3mimgdisplay executable found. Please set " - "the path manually by setting the %s environment variable. (see " - "man page)" % W3MIMGDISPLAY_ENV) + "the path manually by setting the %s environment variable. (see " + "man page)" % W3MIMGDISPLAY_ENV) def _get_font_dimensions(self): # Get the height and width of a character displayed in the terminal in # pixels. if self.binary_path is None: self.binary_path = self._find_w3mimgdisplay_executable() - s = struct.pack("HHHH", 0, 0, 0, 0) + farg = struct.pack("HHHH", 0, 0, 0, 0) fd_stdout = sys.stdout.fileno() - x = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, s) - rows, cols, xpixels, ypixels = struct.unpack("HHHH", x) + fretint = fcntl.ioctl(fd_stdout, termios.TIOCGWINSZ, farg) + rows, cols, xpixels, ypixels = struct.unpack("HHHH", fretint) if xpixels == 0 and ypixels == 0: - process = Popen([self.binary_path, "-test"], - stdout=PIPE, universal_newlines=True) + process = Popen([self.binary_path, "-test"], stdout=PIPE, universal_newlines=True) output, _ = process.communicate() output = output.split() xpixels, ypixels = int(output[0]), int(output[1]) @@ -102,8 +110,7 @@ class W3MImageDisplayer(ImageDisplayer): def draw(self, path, start_x, start_y, width, height): if not self.is_initialized or self.process.poll() is not None: self.initialize() - self.process.stdin.write(self._generate_w3m_input(path, start_x, - start_y, width, height)) + self.process.stdin.write(self._generate_w3m_input(path, start_x, start_y, width, height)) self.process.stdin.flush() self.process.stdout.readline() @@ -114,20 +121,20 @@ class W3MImageDisplayer(ImageDisplayer): fontw, fonth = self._get_font_dimensions() cmd = "6;{x};{y};{w};{h}\n4;\n3;\n".format( - x=int((start_x - 0.2) * fontw), - y=start_y * fonth, - # y = int((start_y + 1) * fonth), # (for tmux top status bar) - w=int((width + 0.4) * fontw), - h=height * fonth + 1) - # h = (height - 1) * fonth + 1) # (for tmux top status bar) + x=int((start_x - 0.2) * fontw), + y=start_y * fonth, + # y = int((start_y + 1) * fonth), # (for tmux top status bar) + w=int((width + 0.4) * fontw), + h=height * fonth + 1, + # h = (height - 1) * fonth + 1, # (for tmux top status bar) + ) try: self.process.stdin.write(cmd) - except IOError as e: - if e.errno == errno.EPIPE: + except IOError as ex: + if ex.errno == errno.EPIPE: return - else: - raise e + raise self.process.stdin.flush() self.process.stdout.readline() @@ -168,12 +175,13 @@ class W3MImageDisplayer(ImageDisplayer): height = max_height_pixels return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;\n".format( - x=int((start_x - 0.2) * fontw), - y=start_y * fonth, - # y = (start_y + 1) * fonth, # (for tmux top status bar) - w=width, - h=height, - filename=path) + x=int((start_x - 0.2) * fontw), + y=start_y * fonth, + # y = (start_y + 1) * fonth, # (for tmux top status bar) + w=width, + h=height, + filename=path, + ) def quit(self): if self.is_initialized and self.process and self.process.poll() is None: @@ -235,33 +243,34 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware): min_scale = min(width_scale, height_scale) max_scale = max(width_scale, height_scale) if width * max_scale <= max_width and height * max_scale <= max_height: - return (width * max_scale) - else: - return (width * min_scale) - else: - scale = max_height / float(height) - return (width * scale) + return width * max_scale + return width * min_scale + + scale = max_height / float(height) + return width * scale elif width > max_width: scale = max_width / float(width) - return (width * scale) - else: - return width + return width * scale + + return width - def _encode_image_content(self, path): + @staticmethod + def _encode_image_content(path): """Read and encode the contents of path""" - file = open(path, 'rb') + fobj = open(path, 'rb') try: - return base64.b64encode(file.read()) + return base64.b64encode(fobj.read()) except Exception: return "" finally: - file.close() + fobj.close() - def _get_image_dimensions(self, path): + @staticmethod + def _get_image_dimensions(path): """Determine image size using imghdr""" file_handle = open(path, 'rb') file_header = file_handle.read(24) - image_type = imghdr.what(path) + image_type = imghdr.what(path) if len(file_header) != 24: file_handle.close() return 0, 0 @@ -305,17 +314,19 @@ class URXVTImageDisplayer(ImageDisplayer, FileManagerAware): """ - def _get_max_sizes(self): + @staticmethod + def _get_max_sizes(): """Use the whole terminal.""" - w = 100 - h = 100 - return w, h + pct_width = 100 + pct_height = 100 + return pct_width, pct_height - def _get_centered_offsets(self): + @staticmethod + def _get_centered_offsets(): """Center the image.""" - x = 50 - y = 50 - return x, y + pct_x = 50 + pct_y = 50 + return pct_x, pct_y def _get_sizes(self): """Return the width and height of the preview pane in relation to the @@ -327,18 +338,18 @@ class URXVTImageDisplayer(ImageDisplayer, FileManagerAware): total_columns_ratio = sum(self.fm.settings.column_ratios) preview_column_ratio = self.fm.settings.column_ratios[-1] - w = int((100 * preview_column_ratio) / total_columns_ratio) - h = 100 # As much as possible while preserving the aspect ratio. - return w, h + pct_width = int((100 * preview_column_ratio) / total_columns_ratio) + pct_height = 100 # As much as possible while preserving the aspect ratio. + return pct_width, pct_height def _get_offsets(self): """Return the offsets of the image center.""" if self.fm.ui.pager.visible: return self._get_centered_offsets() - x = 100 # Right-aligned. - y = 2 # TODO: Use the font size to calculate this offset. - return x, y + pct_x = 100 # Right-aligned. + pct_y = 2 # TODO: Use the font size to calculate this offset. + return pct_x, pct_y def draw(self, path, start_x, start_y, width, height): # The coordinates in the arguments are ignored as urxvt takes @@ -346,10 +357,15 @@ class URXVTImageDisplayer(ImageDisplayer, FileManagerAware): # image center as a percentage of the terminal size. As a # result all values below are in percents. - x, y = self._get_offsets() - w, h = self._get_sizes() + pct_x, pct_y = self._get_offsets() + pct_width, pct_height = self._get_sizes() - sys.stdout.write("\033]20;{path};{w}x{h}+{x}+{y}:op=keep-aspect\a".format(**vars())) + sys.stdout.write( + "\033]20;{path};{pct_width}x{pct_height}+{pct_x}+{pct_y}:op=keep-aspect\a".format( + path=path, pct_width=pct_width, pct_height=pct_height, + pct_x=pct_x, pct_y=pct_y, + ) + ) sys.stdout.flush() def clear(self, start_x, start_y, width, height): diff --git a/ranger/ext/iter_tools.py b/ranger/ext/iter_tools.py index 838d8aff..70b74c39 100644 --- a/ranger/ext/iter_tools.py +++ b/ranger/ext/iter_tools.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from collections import deque @@ -42,6 +44,7 @@ def unique(iterable): already_seen.append(item) return type(iterable)(already_seen) + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/ranger/ext/keybinding_parser.py b/ranger/ext/keybinding_parser.py index 6a4cbde9..b7816cf3 100644 --- a/ranger/ext/keybinding_parser.py +++ b/ranger/ext/keybinding_parser.py @@ -1,17 +1,19 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import sys import copy import curses.ascii PY3 = sys.version_info[0] >= 3 -digits = set(range(ord('0'), ord('9') + 1)) +digits = set(range(ord('0'), ord('9') + 1)) # pylint: disable=invalid-name # Arbitrary numbers which are not used with curses.KEY_XYZ ANYKEY, PASSIVE_ACTION, ALT_KEY, QUANT_KEY = range(9001, 9005) -special_keys = { +special_keys = { # pylint: disable=invalid-name 'bs': curses.KEY_BACKSPACE, 'backspace': curses.KEY_BACKSPACE, 'backspace2': curses.ascii.DEL, @@ -38,33 +40,39 @@ special_keys = { 'gt': ord('>'), } -very_special_keys = { +very_special_keys = { # pylint: disable=invalid-name 'any': ANYKEY, 'alt': ALT_KEY, 'bg': PASSIVE_ACTION, 'allow_quantifiers': QUANT_KEY, } -for key, val in tuple(special_keys.items()): - special_keys['a-' + key] = (ALT_KEY, val) -for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!{}': - special_keys['a-' + char] = (ALT_KEY, ord(char)) +def special_keys_init(): + for key, val in tuple(special_keys.items()): + special_keys['a-' + key] = (ALT_KEY, val) + + for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!{}': + special_keys['a-' + char] = (ALT_KEY, ord(char)) + + for char in 'abcdefghijklmnopqrstuvwxyz_': + special_keys['c-' + char] = ord(char) - 96 -for char in 'abcdefghijklmnopqrstuvwxyz_': - special_keys['c-' + char] = ord(char) - 96 + special_keys['c-space'] = 0 -special_keys['c-space'] = 0 + for n in range(64): + special_keys['f' + str(n)] = curses.KEY_F0 + n -for n in range(64): - special_keys['f' + str(n)] = curses.KEY_F0 + n + +special_keys_init() special_keys.update(very_special_keys) del very_special_keys -reversed_special_keys = dict((v, k) for k, v in special_keys.items()) +reversed_special_keys = dict( # pylint: disable=invalid-name + (v, k) for k, v in special_keys.items()) -def parse_keybinding(obj): +def parse_keybinding(obj): # pylint: disable=too-many-branches """Translate a keybinding to a sequence of integers >>> tuple(parse_keybinding("lol<CR>")) @@ -86,9 +94,9 @@ def parse_keybinding(obj): yield char elif isinstance(obj, int): yield obj - elif isinstance(obj, str): + elif isinstance(obj, str): # pylint: disable=too-many-nested-blocks in_brackets = False - bracket_content = None + bracket_content = [] for char in obj: if in_brackets: if char == '>': @@ -103,8 +111,8 @@ def parse_keybinding(obj): yield int(string) else: yield ord('<') - for c in bracket_content: - yield ord(c) + for bracket_char in bracket_content: + yield ord(bracket_char) yield ord('>') except TypeError: yield keys # it was no tuple, just an int @@ -118,8 +126,8 @@ def parse_keybinding(obj): yield ord(char) if in_brackets: yield ord('<') - for c in bracket_content: - yield ord(c) + for char in bracket_content: + yield ord(char) def construct_keybinding(iterable): @@ -151,6 +159,7 @@ def _unbind_traverse(pointer, keys, pos=0): class KeyMaps(dict): + def __init__(self, keybuffer=None): dict.__init__(self) self.keybuffer = keybuffer @@ -195,7 +204,7 @@ class KeyMaps(dict): pointer = pointer[key] except Exception: raise KeyError("Tried to copy the keybinding `%s'," - " but it was not found." % source) + " but it was not found." % source) self.bind(context, target, copy.deepcopy(pointer)) def unbind(self, context, keys): @@ -205,17 +214,14 @@ class KeyMaps(dict): _unbind_traverse(pointer, keys) -class KeyBuffer(object): - any_key = ANYKEY - passive_key = PASSIVE_ACTION - quantifier_key = QUANT_KEY +class KeyBuffer(object): # pylint: disable=too-many-instance-attributes + any_key = ANYKEY + passive_key = PASSIVE_ACTION + quantifier_key = QUANT_KEY exclude_from_anykey = [27] def __init__(self, keymap=None): self.keymap = keymap - self.clear() - - def clear(self): self.keys = [] self.wildcards = [] self.pointer = self.keymap @@ -229,6 +235,9 @@ class KeyBuffer(object): if self.keymap[self.quantifier_key] == 'false': self.finished_parsing_quantifier = True + def clear(self): + self.__init__(self.keymap) + def add(self, key): self.keys.append(key) self.result = None @@ -263,6 +272,7 @@ class KeyBuffer(object): def __str__(self): return "".join(key_to_string(c) for c in self.keys) + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/ranger/ext/lazy_property.py b/ranger/ext/lazy_property.py index 4bdcda1e..2fbe0cfd 100644 --- a/ranger/ext/lazy_property.py +++ b/ranger/ext/lazy_property.py @@ -1,7 +1,9 @@ # From http://blog.pythonisito.com/2008/08/lazy-descriptors.html +from __future__ import (absolute_import, print_function) -class lazy_property(object): + +class lazy_property(object): # pylint: disable=invalid-name,too-few-public-methods """A @property-like decorator with lazy evaluation >>> class Foo: @@ -29,6 +31,7 @@ class lazy_property(object): obj.__dict__[self.__name__] = result return result + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/ranger/ext/logutils.py b/ranger/ext/logutils.py index 0de6c333..ff27b420 100644 --- a/ranger/ext/logutils.py +++ b/ranger/ext/logutils.py @@ -1,3 +1,5 @@ +from __future__ import (absolute_import, print_function) + import logging from collections import deque @@ -28,9 +30,11 @@ class QueueHandler(logging.Handler): self.enqueue(self.format(record)) +# pylint: disable=invalid-name log_queue = deque(maxlen=1000) concise_formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=LOG_DATA_FORMAT) extended_formatter = logging.Formatter(fmt=LOG_FORMAT_EXT, datefmt=LOG_DATA_FORMAT) +# pylint: enable=invalid-name def setup_logging(debug=True, logfile=None): @@ -65,7 +69,7 @@ def setup_logging(debug=True, logfile=None): handlers = [] handlers.append(QueueHandler(log_queue)) if logfile: - if logfile is '-': + if logfile == '-': handlers.append(logging.StreamHandler()) else: handlers.append(logging.FileHandler(logfile)) diff --git a/ranger/ext/mount_path.py b/ranger/ext/mount_path.py index 69a277af..6c59b107 100644 --- a/ranger/ext/mount_path.py +++ b/ranger/ext/mount_path.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from os.path import realpath, abspath, dirname, ismount diff --git a/ranger/ext/next_available_filename.py b/ranger/ext/next_available_filename.py index c408b35d..4ff9be24 100644 --- a/ranger/ext/next_available_filename.py +++ b/ranger/ext/next_available_filename.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import os.path diff --git a/ranger/ext/openstruct.py b/ranger/ext/openstruct.py index 6479d158..7528ee92 100644 --- a/ranger/ext/openstruct.py +++ b/ranger/ext/openstruct.py @@ -1,11 +1,14 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import collections class OpenStruct(dict): """The fusion of dict and struct""" + def __init__(self, *args, **keywords): dict.__init__(self, *args, **keywords) self.__dict__ = self @@ -13,6 +16,7 @@ class OpenStruct(dict): class DefaultOpenStruct(collections.defaultdict): """The fusion of dict and struct, with default values""" + def __init__(self, *args, **keywords): collections.defaultdict.__init__(self, None, *args, **keywords) self.__dict__ = self @@ -20,5 +24,4 @@ class DefaultOpenStruct(collections.defaultdict): def __getattr__(self, name): if name not in self.__dict__: return None - else: - return self.__dict__[name] + return self.__dict__[name] diff --git a/ranger/ext/popen_forked.py b/ranger/ext/popen_forked.py index d4a75a48..119ce46a 100644 --- a/ranger/ext/popen_forked.py +++ b/ranger/ext/popen_forked.py @@ -1,11 +1,13 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import os import subprocess -def Popen_forked(*args, **kwargs): +def Popen_forked(*args, **kwargs): # pylint: disable=invalid-name """Forks process and runs Popen with the given args and kwargs. Returns True if forking succeeded, otherwise False. @@ -19,7 +21,7 @@ def Popen_forked(*args, **kwargs): kwargs['stdin'] = open(os.devnull, 'r') kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w') subprocess.Popen(*args, **kwargs) - os._exit(0) + os._exit(0) # pylint: disable=protected-access else: os.wait() return True diff --git a/ranger/ext/relative_symlink.py b/ranger/ext/relative_symlink.py index 247d1318..85eb746e 100644 --- a/ranger/ext/relative_symlink.py +++ b/ranger/ext/relative_symlink.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from os import symlink, sep diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py index 7d1bd59e..6c5d70be 100755 --- a/ranger/ext/rifle.py +++ b/ranger/ext/rifle.py @@ -14,6 +14,8 @@ Example usage: rifle.execute(["file1", "file2"]) """ +from __future__ import (absolute_import, print_function) + import os.path import re from subprocess import Popen, PIPE @@ -32,11 +34,11 @@ ENCODING = 'utf-8' try: from ranger.ext.get_executables import get_executables except ImportError: - _cached_executables = None + _cached_executables = None # pylint: disable=invalid-name def get_executables(): """Return all executable files in $PATH + Cache them.""" - global _cached_executables + global _cached_executables # pylint: disable=global-statement,invalid-name if _cached_executables is not None: return _cached_executables @@ -70,7 +72,7 @@ except ImportError: try: from ranger.ext.popen_forked import Popen_forked except ImportError: - def Popen_forked(*args, **kwargs): + def Popen_forked(*args, **kwargs): # pylint: disable=invalid-name """Forks process and runs Popen with the given args and kwargs.""" try: pid = os.fork() @@ -81,7 +83,7 @@ except ImportError: kwargs['stdin'] = open(os.devnull, 'r') kwargs['stdout'] = kwargs['stderr'] = open(os.devnull, 'w') Popen(*args, **kwargs) - os._exit(0) + os._exit(0) # pylint: disable=protected-access return True @@ -111,7 +113,7 @@ def squash_flags(flags): return ''.join(f for f in flags if f not in exclude) -class Rifle(object): +class Rifle(object): # pylint: disable=too-many-instance-attributes delimiter1 = '=' delimiter2 = ',' @@ -122,16 +124,20 @@ class Rifle(object): def hook_after_executing(self, command, mimetype, flags): pass - def hook_command_preprocessing(self, command): + @staticmethod + def hook_command_preprocessing(command): return command - def hook_command_postprocessing(self, command): + @staticmethod + def hook_command_postprocessing(command): return command - def hook_environment(self, env): + @staticmethod + def hook_environment(env): return env - def hook_logger(self, string): + @staticmethod + def hook_logger(string): sys.stderr.write(string + "\n") def __init__(self, config_file): @@ -139,23 +145,25 @@ class Rifle(object): self._app_flags = '' self._app_label = None self._initialized_mimetypes = False + self._mimetype = None + self._skip = None + self.rules = None # get paths for mimetype files - self._mimetype_known_files = [ - os.path.expanduser("~/.mime.types")] + self._mimetype_known_files = [os.path.expanduser("~/.mime.types")] if __file__.endswith("ranger/ext/rifle.py"): # Add ranger's default mimetypes when run from ranger directory self._mimetype_known_files.append( - __file__.replace("ext/rifle.py", "data/mime.types")) + __file__.replace("ext/rifle.py", "data/mime.types")) 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') + fobj = open(config_file, 'r') self.rules = [] lineno = 0 - for line in f: + for line in fobj: lineno += 1 line = line.strip() if line.startswith('#') or line == '': @@ -168,10 +176,10 @@ class Rifle(object): tests = tuple(tuple(f.strip().split(None, 1)) for f in tests) command = command.strip() self.rules.append((command, tests)) - except Exception as e: - self.hook_logger("Syntax error in %s line %d (%s)" % - (config_file, lineno, str(e))) - f.close() + except Exception as ex: + self.hook_logger( + "Syntax error in %s line %d (%s)" % (config_file, lineno, str(ex))) + fobj.close() def _eval_condition(self, condition, files, label): # Handle the negation of conditions starting with an exclamation mark, @@ -184,7 +192,8 @@ class Rifle(object): return not self._eval_condition2(new_condition, files, label) return self._eval_condition2(condition, files, label) - def _eval_condition2(self, rule, files, label): + def _eval_condition2( # pylint: disable=too-many-return-statements,too-many-branches + self, rule, files, label): # This function evaluates the condition, after _eval_condition() handled # negation of conditions starting with a "!". @@ -249,11 +258,10 @@ class Rifle(object): for path in self._mimetype_known_files: if path not in mimetypes.knownfiles: mimetypes.knownfiles.append(path) - self._mimetype, encoding = mimetypes.guess_type(fname) + self._mimetype, _ = mimetypes.guess_type(fname) if not self._mimetype: - process = Popen(["file", "--mime-type", "-Lb", fname], - stdout=PIPE, stderr=PIPE) + process = Popen(["file", "--mime-type", "-Lb", fname], stdout=PIPE, stderr=PIPE) mimetype, _ = process.communicate() self._mimetype = mimetype.decode(ENCODING).strip() return self._mimetype @@ -263,8 +271,7 @@ class Rifle(object): if isinstance(flags, str): self._app_flags += flags self._app_flags = squash_flags(self._app_flags) - filenames = "' '".join(f.replace("'", "'\\\''") for f in files - if "\x00" not in f) + filenames = "' '".join(f.replace("'", "'\\\''") for f in files if "\x00" not in f) return "set -- '%s'; %s" % (filenames, action) def list_commands(self, files, mimetype=None): @@ -292,7 +299,8 @@ class Rifle(object): count = self._skip yield (count, cmd, self._app_label, self._app_flags) - def execute(self, files, number=0, label=None, flags="", mimetype=None): + def execute(self, files, # pylint: disable=too-many-branches,too-many-statements + number=0, label=None, flags="", mimetype=None): """Executes the given list of files. By default, this executes the first command where all conditions apply, @@ -327,7 +335,7 @@ class Rifle(object): command = self._build_command(files, cmd, flags) # Execute command - if command is None: + if command is None: # pylint: disable=too-many-nested-blocks if found_at_least_one: if label: self.hook_logger("Label '%s' is undefined" % label) @@ -362,7 +370,7 @@ class Rifle(object): term = 'urxvt' if term not in get_executables(): self.hook_logger("Can not determine terminal command. " - "Please set $TERMCMD manually.") + "Please set $TERMCMD manually.") # A fallback terminal that is likely installed: term = 'xterm' os.environ['TERMCMD'] = term @@ -370,15 +378,14 @@ class Rifle(object): if 'f' in flags or 't' in flags: Popen_forked(cmd, env=self.hook_environment(os.environ)) else: - p = Popen(cmd, env=self.hook_environment(os.environ)) - p.wait() + process = Popen(cmd, env=self.hook_environment(os.environ)) + process.wait() finally: self.hook_after_executing(command, self._mimetype, self._app_flags) -def main(): +def main(): # pylint: disable=too-many-locals """The main function which is run when you start this program direectly.""" - import sys # Find configuration file path if 'XDG_CONFIG_HOME' in os.environ and os.environ['XDG_CONFIG_HOME']: @@ -388,7 +395,7 @@ def main(): default_conf_path = conf_path if not os.path.isfile(conf_path): conf_path = os.path.normpath(os.path.join(os.path.dirname(__file__), - '../config/rifle.conf')) + '../config/rifle.conf')) if not os.path.isfile(conf_path): try: # if ranger is installed, get the configuration from ranger @@ -399,19 +406,19 @@ def main(): conf_path = os.path.join(ranger.__path__[0], "config", "rifle.conf") # Evaluate arguments - from optparse import OptionParser + from optparse import OptionParser # pylint: disable=deprecated-module parser = OptionParser(usage="%prog [-fhlpw] [files]", version=__version__) parser.add_option('-f', type="string", default="", metavar="FLAGS", - help="use additional flags: f=fork, r=root, t=terminal. " - "Uppercase flag negates respective lowercase flags.") + help="use additional flags: f=fork, r=root, t=terminal. " + "Uppercase flag negates respective lowercase flags.") parser.add_option('-l', action="store_true", - help="list possible ways to open the files (id:label:flags:command)") + help="list possible ways to open the files (id:label:flags:command)") parser.add_option('-p', type='string', default='0', metavar="KEYWORD", - help="pick a method to open the files. KEYWORD is either the " - "number listed by 'rifle -l' or a string that matches a label in " - "the configuration file") + help="pick a method to open the files. KEYWORD is either the " + "number listed by 'rifle -l' or a string that matches a label in " + "the configuration file") parser.add_option('-w', type='string', default=None, metavar="PROGRAM", - help="open the files with PROGRAM") + help="open the files with PROGRAM") options, positional = parser.parse_args() if not positional: parser.print_help() @@ -419,7 +426,7 @@ def main(): if not os.path.isfile(conf_path): sys.stderr.write("Could not find a configuration file.\n" - "Please create one at %s.\n" % default_conf_path) + "Please create one at %s.\n" % default_conf_path) raise SystemExit(1) if options.p.isdigit(): @@ -430,22 +437,23 @@ def main(): label = options.p if options.w is not None and not options.l: - p = Popen([options.w] + list(positional)) - p.wait() + process = Popen([options.w] + list(positional)) + process.wait() else: # Start up rifle rifle = Rifle(conf_path) rifle.reload_config() - #print(rifle.list_commands(sys.argv[1:])) + # print(rifle.list_commands(sys.argv[1:])) if options.l: for count, cmd, label, flags in rifle.list_commands(positional): print("%d:%s:%s:%s" % (count, label or '', flags, cmd)) else: - result = rifle.execute(positional, number=number, label=label, - flags=options.f) + result = rifle.execute(positional, number=number, label=label, flags=options.f) if result == ASK_COMMAND: # TODO: implement interactive asking for file type? - print("Unknown file type: %s" % rifle._get_mimetype(positional[0])) + print("Unknown file type: %s" % + rifle._get_mimetype(positional[0])) # pylint: disable=protected-access + if __name__ == '__main__': if 'RANGER_DOCTEST' in os.environ: diff --git a/ranger/ext/shell_escape.py b/ranger/ext/shell_escape.py index d57ff339..64ddb086 100644 --- a/ranger/ext/shell_escape.py +++ b/ranger/ext/shell_escape.py @@ -3,10 +3,12 @@ """Functions to escape metacharacters of arguments for shell commands.""" +from __future__ import (absolute_import, print_function) + + META_CHARS = (' ', "'", '"', '`', '&', '|', ';', '#', - '$', '!', '(', ')', '[', ']', '<', '>', '\t') -UNESCAPABLE = set(map(chr, list(range(9)) + list(range(10, 32)) - + list(range(127, 256)))) + '$', '!', '(', ')', '[', ']', '<', '>', '\t') +UNESCAPABLE = set(map(chr, list(range(9)) + list(range(10, 32)) + list(range(127, 256)))) META_DICT = dict([(mc, '\\' + mc) for mc in META_CHARS]) @@ -21,6 +23,6 @@ def shell_escape(arg): if UNESCAPABLE & set(arg): return shell_quote(arg) arg = arg.replace('\\', '\\\\') # make sure this comes at the start - for k, v in META_DICT.items(): - arg = arg.replace(k, v) + for key, value in META_DICT.items(): + arg = arg.replace(key, value) return arg diff --git a/ranger/ext/shutil_generatorized.py b/ranger/ext/shutil_generatorized.py index b990aded..85d1d698 100644 --- a/ranger/ext/shutil_generatorized.py +++ b/ranger/ext/shutil_generatorized.py @@ -5,10 +5,12 @@ XXX The functions here don't copy the resource fork or other metadata on Mac. """ +from __future__ import (absolute_import, print_function) + import os +from os.path import abspath import sys import stat -from os.path import abspath __all__ = ["copyfileobj", "copyfile", "copystat", "copy2", "BLOCK_SIZE", "copytree", "move", "rmtree", "Error", "SpecialFileError"] @@ -25,16 +27,17 @@ class SpecialFileError(EnvironmentError): """Raised when trying to do a kind of operation (e.g. copying) which is not supported on a special file (e.g. a named pipe)""" + try: WindowsError except NameError: - WindowsError = None + WindowsError = None # pylint: disable=invalid-name def copyfileobj(fsrc, fdst, length=BLOCK_SIZE): """copy data from file-like object fsrc to file-like object fdst""" done = 0 - while 1: + while True: buf = fsrc.read(length) if not buf: break @@ -63,16 +66,16 @@ def copyfile(src, dst): fsrc = None fdst = None - for fn in [src, dst]: + for path in [src, dst]: try: - st = os.stat(fn) + fstat = os.stat(path) except OSError: # File most likely does not exist pass else: # XXX What about other special files? (sockets, devices...) - if stat.S_ISFIFO(st.st_mode): - raise SpecialFileError("`%s` is a named pipe" % fn) + if stat.S_ISFIFO(fstat.st_mode): + raise SpecialFileError("`%s` is a named pipe" % path) try: fsrc = open(src, 'rb') fdst = open(dst, 'wb') @@ -87,11 +90,11 @@ def copyfile(src, dst): def copystat(src, dst): """Copy all stat info (mode bits, atime, mtime, flags) from src to dst""" - st = os.lstat(src) - mode = stat.S_IMODE(st.st_mode) + fstat = os.lstat(src) + mode = stat.S_IMODE(fstat.st_mode) if hasattr(os, 'utime'): try: - os.utime(dst, (st.st_atime, st.st_mtime)) + os.utime(dst, (fstat.st_atime, fstat.st_mtime)) except Exception: pass if hasattr(os, 'chmod'): @@ -99,9 +102,9 @@ def copystat(src, dst): os.chmod(dst, mode) except Exception: pass - if hasattr(os, 'chflags') and hasattr(st, 'st_flags'): + if hasattr(os, 'chflags') and hasattr(fstat, 'st_flags'): try: - os.chflags(dst, st.st_flags) + os.chflags(dst, fstat.st_flags) # pylint: disable=no-member except Exception: pass @@ -143,7 +146,8 @@ def get_safe_path(dst): return test_dst -def copytree(src, dst, symlinks=False, ignore=None, overwrite=False): +def copytree(src, dst, # pylint: disable=too-many-locals,too-many-branches + symlinks=False, ignore=None, overwrite=False): """Recursively copy a directory tree using copy2(). The destination directory must not already exist. @@ -196,18 +200,16 @@ def copytree(src, dst, symlinks=False, ignore=None, overwrite=False): os.symlink(linkto, dstname) copystat(srcname, dstname) elif os.path.isdir(srcname): - d = 0 - for d in copytree(srcname, dstname, symlinks, - ignore, overwrite): - yield done + d - done += d + n = 0 + for n in copytree(srcname, dstname, symlinks, ignore, overwrite): + yield done + n + done += n else: # Will raise a SpecialFileError for unsupported file types - d = 0 - for d in copy2(srcname, dstname, - overwrite=overwrite, symlinks=symlinks): - yield done + d - done += d + n = 0 + for n in copy2(srcname, dstname, overwrite=overwrite, symlinks=symlinks): + yield done + n + done += n # catch the Error from the recursive copytree so that we can # continue with other files except Error as err: @@ -238,11 +240,11 @@ def rmtree(path, ignore_errors=False, onerror=None): """ if ignore_errors: - def onerror(*args): + def onerror(*_): # pylint: disable=function-redefined pass elif onerror is None: - def onerror(*args): - raise + def onerror(*_): # pylint: disable=function-redefined + raise # pylint: disable=misplaced-bare-raise try: if os.path.islink(path): # symlinks to directories are forbidden, see bug #1669 @@ -254,7 +256,7 @@ def rmtree(path, ignore_errors=False, onerror=None): names = [] try: names = os.listdir(path) - except os.error as err: + except os.error: onerror(os.listdir, path, sys.exc_info()) for name in names: fullname = os.path.join(path, name) @@ -267,7 +269,7 @@ def rmtree(path, ignore_errors=False, onerror=None): else: try: os.remove(fullname) - except os.error as err: + except os.error: onerror(os.remove, fullname, sys.exc_info()) try: os.rmdir(path) diff --git a/ranger/ext/signals.py b/ranger/ext/signals.py index 4167277f..83adf367 100644 --- a/ranger/ext/signals.py +++ b/ranger/ext/signals.py @@ -57,6 +57,8 @@ True True """ +from __future__ import (absolute_import, print_function) + import weakref from types import MethodType @@ -82,7 +84,7 @@ class Signal(dict): self.stopped = True -class SignalHandler: +class SignalHandler(object): # pylint: disable=too-few-public-methods """Signal Handlers contain information about a signal binding. They are returned by signal_bind() and have to be passed to signal_unbind() @@ -102,6 +104,7 @@ class SignalHandler: class SignalDispatcher(object): """This abstract class handles the binding and emitting of signals.""" + def __init__(self): self._signals = dict() @@ -109,7 +112,7 @@ class SignalDispatcher(object): """Remove all signals.""" for handler_list in self._signals.values(): for handler in handler_list: - handler._function = None + handler._function = None # pylint: disable=protected-access self._signals = dict() def signal_bind(self, signal_name, function, priority=0.5, weak=False, autosort=True): @@ -148,7 +151,8 @@ class SignalDispatcher(object): handler = SignalHandler(signal_name, function, priority, nargs > 0) handlers.append(handler) if autosort: - handlers.sort(key=lambda handler: -handler._priority) + handlers.sort( + key=lambda handler: -handler._priority) # pylint: disable=protected-access return handler def signal_force_sort(self, signal_name=None): @@ -159,9 +163,11 @@ class SignalDispatcher(object): """ if signal_name is None: for handlers in self._signals.values(): - handlers.sort(key=lambda handler: -handler._priority) + handlers.sort( + key=lambda handler: -handler._priority) # pylint: disable=protected-access elif signal_name in self._signals: - self._signals[signal_name].sort(key=lambda handler: -handler._priority) + self._signals[signal_name].sort( + key=lambda handler: -handler._priority) # pylint: disable=protected-access else: return False @@ -172,12 +178,13 @@ class SignalDispatcher(object): signal_bind(). """ try: - handlers = self._signals[signal_handler._signal_name] + handlers = self._signals[ + signal_handler._signal_name] # pylint: disable=protected-access except Exception: pass else: try: - signal_handler._function = None + signal_handler._function = None # pylint: disable=protected-access handlers.remove(signal_handler) except Exception: pass @@ -219,14 +226,16 @@ class SignalDispatcher(object): while i: i -= 1 handler = handler_list[i] + # pylint: disable=protected-access try: if isinstance(handler._function, tuple): - handler._function[1].__class__ + handler._function[1].__class__ # pylint: disable=pointless-statement else: - handler._function.__class__ + handler._function.__class__ # pylint: disable=pointless-statement except ReferenceError: handler._function = None del handler_list[i] + # pylint: enable=protected-access def signal_emit(self, signal_name, **kw): """Emits a signal and call every function that was bound to that signal. @@ -250,6 +259,7 @@ class SignalDispatcher(object): # propagate for handler in tuple(handlers): if handler.active: + # pylint: disable=protected-access try: if isinstance(handler._function, tuple): fnc = MethodType(*handler._function) @@ -262,6 +272,7 @@ class SignalDispatcher(object): except ReferenceError: handler._function = None handlers.remove(handler) + # pylint: enable=protected-access if signal.stopped: return False return True diff --git a/ranger/ext/spawn.py b/ranger/ext/spawn.py index 04abfbd2..ffb4d94b 100644 --- a/ranger/ext/spawn.py +++ b/ranger/ext/spawn.py @@ -1,6 +1,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from os import devnull from subprocess import Popen, PIPE, CalledProcessError ENCODING = 'utf-8' @@ -66,5 +68,4 @@ def spawn(*popenargs, **kwargs): """ if len(popenargs) == 1: return check_output(popenargs[0], **kwargs) - else: - return check_output(list(popenargs), **kwargs) + return check_output(list(popenargs), **kwargs) diff --git a/ranger/ext/vcs/__init__.py b/ranger/ext/vcs/__init__.py index 26a32800..90f9a713 100644 --- a/ranger/ext/vcs/__init__.py +++ b/ranger/ext/vcs/__init__.py @@ -3,6 +3,8 @@ """VCS Extension""" +from __future__ import (absolute_import, print_function) + from .vcs import Vcs, VcsError, VcsThread __all__ = ['Vcs', 'VcsError', 'VcsThread'] diff --git a/ranger/ext/vcs/bzr.py b/ranger/ext/vcs/bzr.py index d0db1faf..753b5d05 100644 --- a/ranger/ext/vcs/bzr.py +++ b/ranger/ext/vcs/bzr.py @@ -3,6 +3,8 @@ """GNU Bazaar module""" +from __future__ import (absolute_import, print_function) + from datetime import datetime import os import re @@ -41,7 +43,9 @@ class Bzr(Vcs): output = self._run(args) except VcsError: return None + # pylint: disable=no-member entries = re.findall(r'-+\n(.+?)\n(?:-|\Z)', output, re.MULTILINE | re.DOTALL) + # pylint: enable=no-member log = [] for entry in entries: diff --git a/ranger/ext/vcs/git.py b/ranger/ext/vcs/git.py index 06e066d2..c6cb2bf3 100644 --- a/ranger/ext/vcs/git.py +++ b/ranger/ext/vcs/git.py @@ -3,6 +3,8 @@ """Git module""" +from __future__ import (absolute_import, print_function) + from datetime import datetime import os import re @@ -159,12 +161,13 @@ class Git(Vcs): return 'none' output = self._run(['rev-list', '--left-right', '{0:s}...{1:s}'.format(remote, head)]) + # pylint: disable=no-member ahead = re.search(r'^>', output, flags=re.MULTILINE) behind = re.search(r'^<', output, flags=re.MULTILINE) + # pylint: enable=no-member if ahead: return 'diverged' if behind else 'ahead' - else: - return 'behind' if behind else 'sync' + return 'behind' if behind else 'sync' def data_branch(self): try: diff --git a/ranger/ext/vcs/hg.py b/ranger/ext/vcs/hg.py index 21460975..94d0f749 100644 --- a/ranger/ext/vcs/hg.py +++ b/ranger/ext/vcs/hg.py @@ -3,6 +3,8 @@ """Mercurial module""" +from __future__ import (absolute_import, print_function) + from datetime import datetime import json import os diff --git a/ranger/ext/vcs/svn.py b/ranger/ext/vcs/svn.py index 1f09cd52..ceac84cf 100644 --- a/ranger/ext/vcs/svn.py +++ b/ranger/ext/vcs/svn.py @@ -3,6 +3,8 @@ """Subversion module""" +from __future__ import (absolute_import, print_function) + from datetime import datetime import os from xml.etree import ElementTree as etree diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py index fa00777a..dbc2eace 100644 --- a/ranger/ext/vcs/vcs.py +++ b/ranger/ext/vcs/vcs.py @@ -3,6 +3,8 @@ """VCS module""" +from __future__ import (absolute_import, print_function) + import os import subprocess import threading @@ -378,6 +380,7 @@ class VcsRoot(Vcs): # pylint: disable=abstract-method class VcsThread(threading.Thread): # pylint: disable=too-many-instance-attributes """VCS thread""" + def __init__(self, ui): super(VcsThread, self).__init__() self.daemon = True diff --git a/ranger/ext/widestring.py b/ranger/ext/widestring.py index 4890c20b..caab1c80 100644 --- a/ranger/ext/widestring.py +++ b/ranger/ext/widestring.py @@ -2,6 +2,8 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import sys from unicodedata import east_asian_width @@ -32,9 +34,9 @@ def string_to_charlist(string): return list(string) result = [] if PY3: - for c in string: - result.append(c) - if east_asian_width(c) in WIDE_SYMBOLS: + for char in string: + result.append(char) + if east_asian_width(char) in WIDE_SYMBOLS: result.append('') else: try: @@ -45,14 +47,15 @@ def string_to_charlist(string): string = string.decode('utf-8', 'ignore') except UnicodeEncodeError: return [] - for c in string: - result.append(c.encode('utf-8')) - if east_asian_width(c) in WIDE_SYMBOLS: + for char in string: + result.append(char.encode('utf-8')) + if east_asian_width(char) in WIDE_SYMBOLS: result.append('') return result -class WideString(object): +class WideString(object): # pylint: disable=too-few-public-methods + def __init__(self, string, chars=None): try: self.string = str(string) @@ -80,8 +83,7 @@ class WideString(object): if isinstance(string, str): return WideString(self.string + string) elif isinstance(string, WideString): - return WideString(self.string + string.string, - self.chars + string.chars) + return WideString(self.string + string.string, self.chars + string.chars) def __radd__(self, string): """ @@ -91,8 +93,7 @@ class WideString(object): if isinstance(string, str): return WideString(string + self.string) elif isinstance(string, WideString): - return WideString(string.string + self.string, - string.chars + self.chars) + return WideString(string.string + self.string, string.chars + self.chars) def __str__(self): return self.string @@ -100,7 +101,7 @@ class WideString(object): def __repr__(self): return '<' + self.__class__.__name__ + " '" + self.string + "'>" - def __getslice__(self, a, z): + def __getslice__(self, start, stop): """ >>> WideString("asdf")[1:3] <WideString 'sd'> @@ -123,21 +124,21 @@ class WideString(object): >>> WideString("aモ")[0:1] <WideString 'a'> """ - if z is None or z > len(self.chars): - z = len(self.chars) - if z < 0: - z = len(self.chars) + z - if z < 0: + if stop is None or stop > len(self.chars): + stop = len(self.chars) + if stop < 0: + stop = len(self.chars) + stop + if stop < 0: return WideString("") - if a is None or a < 0: - a = 0 - if z < len(self.chars) and self.chars[z] == '': - if self.chars[a] == '': - return WideString(' ' + ''.join(self.chars[a:z - 1]) + ' ') - return WideString(''.join(self.chars[a:z - 1]) + ' ') - if self.chars[a] == '': - return WideString(' ' + ''.join(self.chars[a:z - 1])) - return WideString(''.join(self.chars[a:z])) + if start is None or start < 0: + start = 0 + if stop < len(self.chars) and self.chars[stop] == '': + if self.chars[start] == '': + return WideString(' ' + ''.join(self.chars[start:stop - 1]) + ' ') + return WideString(''.join(self.chars[start:stop - 1]) + ' ') + if self.chars[start] == '': + return WideString(' ' + ''.join(self.chars[start:stop - 1])) + return WideString(''.join(self.chars[start:stop])) def __getitem__(self, i): """ diff --git a/ranger/gui/ansi.py b/ranger/gui/ansi.py index 091406e9..e68e780a 100644 --- a/ranger/gui/ansi.py +++ b/ranger/gui/ansi.py @@ -4,12 +4,18 @@ """A library to help to convert ANSI codes to curses instructions.""" -from ranger.gui import color +from __future__ import (absolute_import, print_function) + import re +from ranger.gui import color + + +# pylint: disable=invalid-name ansi_re = re.compile('(\x1b' + r'\[\d*(?:;\d+)*?[a-zA-Z])') codesplit_re = re.compile(r'38;5;(\d+);|48;5;(\d+);|(\d*);') reset = '\x1b[0m' +# pylint: enable=invalid-name def split_ansi_from_text(ansi_text): @@ -19,7 +25,7 @@ def split_ansi_from_text(ansi_text): # githttp://en.wikipedia.org/wiki/ANSI_escape_code -def text_with_fg_bg_attr(ansi_text): +def text_with_fg_bg_attr(ansi_text): # pylint: disable=too-many-branches,too-many-statements fg, bg, attr = -1, -1, 0 for chunk in split_ansi_from_text(ansi_text): if chunk and chunk[0] == '\x1b': @@ -35,13 +41,13 @@ def text_with_fg_bg_attr(ansi_text): for x256fg, x256bg, arg in codesplit_re.findall(attr_args + ';'): # first handle xterm256 codes try: - if len(x256fg) > 0: # xterm256 foreground + if x256fg: # xterm256 foreground fg = int(x256fg) continue - elif len(x256bg) > 0: # xterm256 background + elif x256bg: # xterm256 background bg = int(x256bg) continue - elif len(arg) > 0: # usual ansi code + elif arg: # usual ansi code n = int(arg) else: # empty code means reset n = 0 @@ -82,7 +88,8 @@ def text_with_fg_bg_attr(ansi_text): elif n == 49: bg = -1 - elif n >= 90 and n <= 97: # 8 aixterm high intensity colors (light but not bold) + # 8 aixterm high intensity colors (light but not bold) + elif n >= 90 and n <= 97: fg = n - 90 + 8 elif n == 99: fg = -1 @@ -163,6 +170,7 @@ def char_slice(ansi_text, start, length): break return ''.join(chunks) + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/ranger/gui/bar.py b/ranger/gui/bar.py index 12c44488..730b7e27 100644 --- a/ranger/gui/bar.py +++ b/ranger/gui/bar.py @@ -1,8 +1,13 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. -from ranger.ext.widestring import WideString, utf_char_width +from __future__ import (absolute_import, print_function) + import sys + +from ranger.ext.widestring import WideString, utf_char_width + + PY3 = sys.version_info[0] >= 3 @@ -35,7 +40,7 @@ class Bar(object): # remove elemets from the left until it fits if sumsize > wid: - while len(self.left) > 0: + while self.left: leftsize -= len(self.left.pop(-1)) if leftsize + rightsize <= wid: break @@ -43,7 +48,7 @@ class Bar(object): # remove elemets from the right until it fits if sumsize > wid: - while len(self.right) > 0: + while self.right: rightsize -= len(self.right.pop(0)) if leftsize + rightsize <= wid: break @@ -87,13 +92,14 @@ class Bar(object): class BarSide(list): - def __init__(self, base_color_tag): + + def __init__(self, base_color_tag): # pylint: disable=super-init-not-called self.base_color_tag = base_color_tag def add(self, string, *lst, **kw): - cs = ColoredString(string, self.base_color_tag, *lst) - cs.__dict__.update(kw) - self.append(cs) + colorstr = ColoredString(string, self.base_color_tag, *lst) + colorstr.__dict__.update(kw) + self.append(colorstr) def add_space(self, n=1): self.add(' ' * n, 'space') @@ -112,11 +118,12 @@ class BarSide(list): class ColoredString(object): + def __init__(self, string, *lst): self.string = WideString(string) self.lst = lst self.fixed = False - if not len(string) or not len(self.string.chars): + if not string or not self.string.chars: self.min_size = 0 elif PY3: self.min_size = utf_char_width(string[0]) diff --git a/ranger/gui/color.py b/ranger/gui/color.py index aa3b931c..06ed4d3a 100644 --- a/ranger/gui/color.py +++ b/ranger/gui/color.py @@ -13,6 +13,8 @@ attr ^= reverse bool(attr & reverse) # => False """ +from __future__ import (absolute_import, print_function) + import curses DEFAULT_FOREGROUND = curses.COLOR_WHITE @@ -46,15 +48,17 @@ def get_color(fg, bg): return COLOR_PAIRS[key] -black = curses.COLOR_BLACK -blue = curses.COLOR_BLUE -cyan = curses.COLOR_CYAN -green = curses.COLOR_GREEN -magenta = curses.COLOR_MAGENTA -red = curses.COLOR_RED -white = curses.COLOR_WHITE -yellow = curses.COLOR_YELLOW -default = -1 + +# pylint: disable=invalid-name,bad-whitespace +black = curses.COLOR_BLACK +blue = curses.COLOR_BLUE +cyan = curses.COLOR_CYAN +green = curses.COLOR_GREEN +magenta = curses.COLOR_MAGENTA +red = curses.COLOR_RED +white = curses.COLOR_WHITE +yellow = curses.COLOR_YELLOW +default = -1 normal = curses.A_NORMAL bold = curses.A_BOLD @@ -64,3 +68,4 @@ underline = curses.A_UNDERLINE invisible = curses.A_INVIS default_colors = (default, default, normal) +# pylint: enable=invalid-name,bad-whitespace diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py index c861a8e8..9bd96b17 100644 --- a/ranger/gui/colorscheme.py +++ b/ranger/gui/colorscheme.py @@ -24,6 +24,8 @@ Define which colorscheme in your settings (e.g. ~/.config/ranger/rc.conf): set colorscheme yourschemename """ +from __future__ import (absolute_import, print_function) + import os.path from curses import color_pair @@ -51,10 +53,9 @@ class ColorScheme(object): """ context = Context(keys) color = self.use(context) - if len(color) != 3 or not all(isinstance(value, int) - for value in color): + if len(color) != 3 or not all(isinstance(value, int) for value in color): raise ValueError("Bad Value from colorscheme. Need " - "a tuple of (foreground_color, background_color, attribute).") + "a tuple of (foreground_color, background_color, attribute).") return color @cached_function @@ -66,7 +67,8 @@ class ColorScheme(object): fg, bg, attr = self.get(*flatten(keys)) return attr | color_pair(get_color(fg, bg)) - def use(self, context): + @staticmethod + def use(_): """Use the colorscheme to determine the (fg, bg, attr) tuple. Override this method in your own colorscheme. @@ -74,7 +76,7 @@ class ColorScheme(object): return (-1, -1, 0) -def _colorscheme_name_to_class(signal): +def _colorscheme_name_to_class(signal): # pylint: disable=too-many-branches # Find the colorscheme. First look in ~/.config/ranger/colorschemes, # then at RANGERDIR/colorschemes. If the file contains a class # named Scheme, it is used. Otherwise, an arbitrary other class @@ -86,14 +88,14 @@ def _colorscheme_name_to_class(signal): signal.value = 'default' scheme_name = signal.value - usecustom = not ranger.arg.clean + usecustom = not ranger.args.clean def exists(colorscheme): return os.path.exists(colorscheme + '.py') or os.path.exists(colorscheme + '.pyc') - def is_scheme(x): + def is_scheme(cls): try: - return issubclass(x, ColorScheme) + return issubclass(cls, ColorScheme) except Exception: return False @@ -121,11 +123,11 @@ def _colorscheme_name_to_class(signal): raise Exception("Cannot locate colorscheme `%s'" % scheme_name) else: if usecustom: - allow_access_to_confdir(ranger.arg.confdir, True) - scheme_module = getattr(__import__(scheme_supermodule, - globals(), locals(), [scheme_name], 0), scheme_name) + allow_access_to_confdir(ranger.args.confdir, True) + scheme_module = getattr( + __import__(scheme_supermodule, globals(), locals(), [scheme_name], 0), scheme_name) if usecustom: - allow_access_to_confdir(ranger.arg.confdir, False) + allow_access_to_confdir(ranger.args.confdir, False) if hasattr(scheme_module, 'Scheme') \ and is_scheme(scheme_module.Scheme): signal.value = scheme_module.Scheme() diff --git a/ranger/gui/context.py b/ranger/gui/context.py index e577d2be..dec00892 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -1,35 +1,46 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. -CONTEXT_KEYS = ['reset', 'error', 'badinfo', - 'in_browser', 'in_statusbar', 'in_titlebar', 'in_console', - 'in_pager', 'in_taskview', - 'active_pane', 'inactive_pane', - 'directory', 'file', 'hostname', - 'executable', 'media', 'link', 'fifo', 'socket', 'device', - 'video', 'audio', 'image', 'media', 'document', 'container', - 'selected', 'empty', 'main_column', 'message', 'background', - 'good', 'bad', - 'space', 'permissions', 'owner', 'group', 'mtime', 'nlink', - 'scroll', 'all', 'bot', 'top', 'percentage', 'filter', - 'flat', 'marked', 'tagged', 'tag_marker', 'cut', 'copied', - 'help_markup', # COMPAT - 'seperator', 'key', 'special', 'border', # COMPAT - 'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'loaded', - 'keybuffer', - 'infostring', - 'vcsfile', 'vcsremote', 'vcsinfo', 'vcscommit', 'vcsdate', - 'vcsconflict', 'vcschanged', 'vcsunknown', 'vcsignored', - 'vcsstaged', 'vcssync', 'vcsnone', 'vcsbehind', 'vcsahead', 'vcsdiverged'] - - -class Context(object): +from __future__ import (absolute_import, print_function) + + +CONTEXT_KEYS = [ + 'reset', 'error', 'badinfo', + 'in_browser', 'in_statusbar', 'in_titlebar', 'in_console', + 'in_pager', 'in_taskview', + 'active_pane', 'inactive_pane', + 'directory', 'file', 'hostname', + 'executable', 'media', 'link', 'fifo', 'socket', 'device', + 'video', 'audio', 'image', 'media', 'document', 'container', + 'selected', 'empty', 'main_column', 'message', 'background', + 'good', 'bad', + 'space', 'permissions', 'owner', 'group', 'mtime', 'nlink', + 'scroll', 'all', 'bot', 'top', 'percentage', 'filter', + 'flat', 'marked', 'tagged', 'tag_marker', 'cut', 'copied', + 'help_markup', # COMPAT + 'seperator', 'key', 'special', 'border', # COMPAT + 'title', 'text', 'highlight', 'bars', 'quotes', 'tab', 'loaded', + 'keybuffer', + 'infostring', + 'vcsfile', 'vcsremote', 'vcsinfo', 'vcscommit', 'vcsdate', + 'vcsconflict', 'vcschanged', 'vcsunknown', 'vcsignored', + 'vcsstaged', 'vcssync', 'vcsnone', 'vcsbehind', 'vcsahead', 'vcsdiverged' +] + + +class Context(object): # pylint: disable=too-few-public-methods + def __init__(self, keys): # set all given keys to True - d = self.__dict__ + dictionary = self.__dict__ for key in keys: - d[key] = True + dictionary[key] = True + + +def _context_init(): + # set all keys to False + for key in CONTEXT_KEYS: + setattr(Context, key, False) + -# set all keys to False -for key in CONTEXT_KEYS: - setattr(Context, key, False) +_context_init() diff --git a/ranger/gui/curses_shortcuts.py b/ranger/gui/curses_shortcuts.py index ed762c9e..25a9d947 100644 --- a/ranger/gui/curses_shortcuts.py +++ b/ranger/gui/curses_shortcuts.py @@ -1,9 +1,11 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + +import sys import curses import _curses -import sys from ranger.gui.color import get_color from ranger.core.shared import SettingsAware @@ -26,6 +28,9 @@ class CursesShortcuts(SettingsAware): addstr(*args) -- failsafe version of self.win.addstr(*args) """ + def __init__(self): + self.win = None + def addstr(self, *args): y, x = self.win.getyx() diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index 62eb5300..16c78a91 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -1,11 +1,14 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from ranger.core.shared import FileManagerAware from ranger.gui.curses_shortcuts import CursesShortcuts -class Displayable(FileManagerAware, CursesShortcuts): +class Displayable( # pylint: disable=too-many-instance-attributes + FileManagerAware, CursesShortcuts): """Displayables are objects which are displayed on the screen. This is just the abstract class, defining basic operations @@ -40,8 +43,10 @@ class Displayable(FileManagerAware, CursesShortcuts): settings, fm -- inherited shared variables """ - def __init__(self, win, env=None, fm=None, settings=None): + def __init__(self, win, # pylint: disable=super-init-not-called + env=None, fm=None, settings=None): from ranger.gui.ui import UI + if env is not None: self.env = env if fm is not None: @@ -105,7 +110,7 @@ class Displayable(FileManagerAware, CursesShortcuts): x and y should be absolute coordinates. """ return (x >= self.x and x < self.x + self.wid) and \ - (y >= self.y and y < self.y + self.hei) + (y >= self.y and y < self.y + self.hei) def click(self, event): """Called when a mouse key is pressed and self.focused is True. @@ -153,17 +158,19 @@ class Displayable(FileManagerAware, CursesShortcuts): if x < 0 or y < 0: self.fm.notify("Warning: Subwindow origin below zero for <%s> " - "(x = %d, y = %d)" % (self, x, y), bad=True) + "(x = %d, y = %d)" % (self, x, y), bad=True) if x + wid > maxx or y + hei > maxy: - self.fm.notify("Warning: Subwindow size out of bounds for <%s> " - "(x = %d, y = %d, hei = %d, wid = %d)" % (self, - x, y, hei, wid), bad=True) + self.fm.notify( + "Warning: Subwindow size out of bounds for <%s> " + "(x = %d, y = %d, hei = %d, wid = %d)" % (self, x, y, hei, wid), + bad=True, + ) window_is_cleared = False if hei != self.hei or wid != self.wid: - #log("resizing " + str(self)) + # log("resizing " + str(self)) self.win.erase() self.need_redraw = True window_is_cleared = True @@ -177,7 +184,7 @@ class Displayable(FileManagerAware, CursesShortcuts): self.win.resize(hei, wid) except Exception: pass - #raise ValueError("Resizing Failed!") + # raise ValueError("Resizing Failed!") self.hei, self.wid = self.win.getmaxyx() @@ -185,7 +192,7 @@ class Displayable(FileManagerAware, CursesShortcuts): if not window_is_cleared: self.win.erase() self.need_redraw = True - #log("moving " + str(self)) + # log("moving " + str(self)) try: self.win.mvderwin(y, x) except Exception: @@ -311,7 +318,7 @@ class DisplayableContainer(Displayable): if displayable.focused: return displayable try: - obj = displayable._get_focused_obj() + obj = displayable._get_focused_obj() # pylint: disable=protected-access except AttributeError: pass else: diff --git a/ranger/gui/mouse_event.py b/ranger/gui/mouse_event.py index 1d3ec970..5a5f687f 100644 --- a/ranger/gui/mouse_event.py +++ b/ranger/gui/mouse_event.py @@ -1,15 +1,19 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import curses class MouseEvent(object): - PRESSED = [0, - curses.BUTTON1_PRESSED, - curses.BUTTON2_PRESSED, - curses.BUTTON3_PRESSED, - curses.BUTTON4_PRESSED] + PRESSED = [ + 0, + curses.BUTTON1_PRESSED, + curses.BUTTON2_PRESSED, + curses.BUTTON3_PRESSED, + curses.BUTTON4_PRESSED, + ] CTRL_SCROLLWHEEL_MULTIPLIER = 5 def __init__(self, getmouse): @@ -39,13 +43,12 @@ class MouseEvent(object): # Recently it seems to have been fixed, as 2**21 was introduced as # the code for the "scroll down" button. if self.bstate & curses.BUTTON4_PRESSED: - return self.ctrl() and -self.CTRL_SCROLLWHEEL_MULTIPLIER or -1 + return -self.CTRL_SCROLLWHEEL_MULTIPLIER if self.ctrl() else -1 elif self.bstate & curses.BUTTON2_PRESSED \ or self.bstate & 2**21 \ or self.bstate > curses.ALL_MOUSE_EVENTS: - return self.ctrl() and self.CTRL_SCROLLWHEEL_MULTIPLIER or 1 - else: - return 0 + return self.CTRL_SCROLLWHEEL_MULTIPLIER if self.ctrl() else 1 + return 0 def ctrl(self): return self.bstate & curses.BUTTON_CTRL diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py index f10bc0f2..97938453 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -1,18 +1,22 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + import os import sys +import threading import curses import _curses -import threading -from .displayable import DisplayableContainer -from .mouse_event import MouseEvent from ranger.ext.keybinding_parser import KeyBuffer, KeyMaps, ALT_KEY from ranger.ext.lazy_property import lazy_property from ranger.ext.signals import Signal +from .displayable import DisplayableContainer +from .mouse_event import MouseEvent + + MOUSEMASK = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION _ASCII = ''.join(chr(c) for c in range(32, 127)) @@ -38,7 +42,8 @@ def _setup_mouse(signal): curses.mousemask(0) -class UI(DisplayableContainer): +class UI( # pylint: disable=too-many-instance-attributes,too-many-public-methods + DisplayableContainer): ALLOWED_VIEWMODES = 'miller', 'multipane' is_set_up = False @@ -46,12 +51,21 @@ class UI(DisplayableContainer): is_on = False termsize = None - def __init__(self, env=None, fm=None): + def __init__(self, env=None, fm=None): # pylint: disable=super-init-not-called self.keybuffer = KeyBuffer() self.keymaps = KeyMaps(self.keybuffer) self.redrawlock = threading.Event() self.redrawlock.set() + self.titlebar = None + self._viewmode = None + self.taskview = None + self.status = None + self.console = None + self.pager = None + self._draw_title = None + self.browser = None + if fm is not None: self.fm = fm @@ -59,8 +73,8 @@ class UI(DisplayableContainer): os.environ['ESCDELAY'] = '25' # don't know a cleaner way try: self.win = curses.initscr() - except _curses.error as e: - if e.args[0] == "setupterm: could not find terminal": + except _curses.error as ex: + if ex.args[0] == "setupterm: could not find terminal": os.environ['TERM'] = 'linux' self.win = curses.initscr() self.keymaps.use_keymap('browser') @@ -172,13 +186,15 @@ class UI(DisplayableContainer): keybuffer.add(key) self.fm.hide_bookmarks() self.browser.draw_hints = not keybuffer.finished_parsing \ - and keybuffer.finished_parsing_quantifier + and keybuffer.finished_parsing_quantifier if keybuffer.result is not None: try: - self.fm.execute_console(keybuffer.result, - wildcards=keybuffer.wildcards, - quantifier=keybuffer.quantifier) + self.fm.execute_console( + keybuffer.result, + wildcards=keybuffer.wildcards, + quantifier=keybuffer.quantifier, + ) finally: if keybuffer.finished_parsing: keybuffer.clear() @@ -193,12 +209,12 @@ class UI(DisplayableContainer): def handle_input(self): key = self.win.getch() - if key is 27 or key >= 128 and key < 256: + if key == 27 or (key >= 128 and key < 256): # Handle special keys like ALT+X or unicode here: keys = [key] previous_load_mode = self.load_mode self.set_load_mode(True) - for n in range(4): + for _ in range(4): getkey = self.win.getch() if getkey is not -1: keys.append(getkey) @@ -231,7 +247,6 @@ class UI(DisplayableContainer): def setup(self): """Build up the UI by initializing widgets.""" - from ranger.gui.widgets.view_miller import ViewMiller from ranger.gui.widgets.titlebar import TitleBar from ranger.gui.widgets.console import Console from ranger.gui.widgets.statusbar import StatusBar @@ -330,10 +345,12 @@ class UI(DisplayableContainer): cwd = os.sep.join(split[1:]) try: fixed_cwd = cwd.encode('utf-8', 'surrogateescape'). \ - decode('utf-8', 'replace') - sys.stdout.write("%sranger:%s%s" % - (curses.tigetstr('tsl').decode('latin-1'), fixed_cwd, - curses.tigetstr('fsl').decode('latin-1'))) + decode('utf-8', 'replace') + sys.stdout.write("%sranger:%s%s" % ( + curses.tigetstr('tsl').decode('latin-1'), + fixed_cwd, + curses.tigetstr('fsl').decode('latin-1'), + )) sys.stdout.flush() except Exception: pass @@ -423,8 +440,7 @@ class UI(DisplayableContainer): def get_pager(self): if hasattr(self.browser, 'pager') and self.browser.pager.visible: return self.browser.pager - else: - return self.pager + return self.pager def _get_viewmode(self): return self._viewmode @@ -439,23 +455,24 @@ class UI(DisplayableContainer): self._viewmode = value new_browser = self._viewmode_to_class(value)(self.win) - if hasattr(self, 'browser'): + if self.browser is None: + self.add_child(new_browser) + else: old_size = self.browser.y, self.browser.x, self.browser.hei, self.browser.wid self.replace_child(self.browser, new_browser) self.browser.destroy() new_browser.resize(*old_size) - else: - self.add_child(new_browser) self.browser = new_browser self.redraw_window() else: raise ValueError("Attempting to set invalid viewmode `%s`, should " - "be one of `%s`." % (value, "`, `".join(self.ALLOWED_VIEWMODES))) + "be one of `%s`." % (value, "`, `".join(self.ALLOWED_VIEWMODES))) viewmode = property(_get_viewmode, _set_viewmode) - def _viewmode_to_class(self, viewmode): + @staticmethod + def _viewmode_to_class(viewmode): if viewmode == 'miller': from ranger.gui.widgets.view_miller import ViewMiller return ViewMiller diff --git a/ranger/gui/widgets/__init__.py b/ranger/gui/widgets/__init__.py index 9a95509c..7fa796f6 100644 --- a/ranger/gui/widgets/__init__.py +++ b/ranger/gui/widgets/__init__.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import (absolute_import, print_function) + from ranger.gui.displayable import Displayable @@ -7,24 +9,39 @@ class Widget(Displayable): """A class for classification of widgets.""" vcsstatus_symb = { - 'conflict': ('X', ['vcsconflict']), - 'untracked': ('+', ['vcschanged']), - 'deleted': ('-', ['vcschanged']), - 'changed': ('*', ['vcschanged']), - 'staged': ('*', ['vcsstaged']), - 'ignored': ('·', ['vcsignored']), - 'sync': ('√', ['vcssync']), - 'none': (' ', []), - 'unknown': ('?', ['vcsunknown']), + 'conflict': ( + 'X', ['vcsconflict']), + 'untracked': ( + '+', ['vcschanged']), + 'deleted': ( + '-', ['vcschanged']), + 'changed': ( + '*', ['vcschanged']), + 'staged': ( + '*', ['vcsstaged']), + 'ignored': ( + '·', ['vcsignored']), + 'sync': ( + '√', ['vcssync']), + 'none': ( + ' ', []), + 'unknown': ( + '?', ['vcsunknown']), } vcsremotestatus_symb = { - 'diverged': ('Y', ['vcsdiverged']), - 'ahead': ('>', ['vcsahead']), - 'behind': ('<', ['vcsbehind']), - 'sync': ('=', ['vcssync']), - 'none': ('⌂', ['vcsnone']), - 'unknown': ('?', ['vcsunknown']), + 'diverged': ( + 'Y', ['vcsdiverged']), + 'ahead': ( + '>', ['vcsahead']), + 'behind': ( + '<', ['vcsbehind']), + 'sync': ( + '=', ['vcssync']), + 'none': ( + '⌂', ['vcsnone']), + 'unknown': ( + '?', ['vcsunknown']), } ellipsis = {False: '~', True: '…'} diff --git a/ranger/gui/widgets/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 829809c0..222457c2 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -3,24 +3,23 @@ """The BrowserColumn widget displays the contents of a directory or file.""" -import curses +from __future__ import (absolute_import, print_function) + import stat from time import time from os.path import splitext -from . import Widget -from .pager import Pager from ranger.ext.widestring import WideString - from ranger.core import linemode -from ranger.gui.color import * +from . import Widget +from .pager import Pager -class BrowserColumn(Pager): +class BrowserColumn(Pager): # pylint: disable=too-many-instance-attributes main_column = False display_infostring = False - display_vcsstate = True + display_vcsstate = True scroll_begin = 0 target = None last_redraw_time = -1 @@ -39,13 +38,13 @@ class BrowserColumn(Pager): level <0 => parent directories """ Pager.__init__(self, win) - Widget.__init__(self, win) + Widget.__init__(self, win) # pylint: disable=non-parent-init-called self.level = level self.tab = tab self.original_level = level self.settings.signal_bind('setopt.display_size_in_main_column', - self.request_redraw, weak=True) + self.request_redraw, weak=True) def request_redraw(self): self.need_redraw = True @@ -182,14 +181,14 @@ class BrowserColumn(Pager): Pager.close(self) return - f = self.target.get_preview_source(self.wid, self.hei) - if f is None: + path = self.target.get_preview_source(self.wid, self.hei) + if path is None: Pager.close(self) else: if self.target.is_image_preview(): - self.set_image(f) + self.set_image(path) else: - self.set_source(f) + self.set_source(path) Pager.draw(self) def _format_line_number(self, linum_format, i, selected_i): @@ -201,7 +200,8 @@ class BrowserColumn(Pager): return linum_format.format(line_number) - def _draw_directory(self): + def _draw_directory( # pylint: disable=too-many-locals,too-many-branches,too-many-statements + self): """Draw the contents of a directory""" if self.image: self.image = None @@ -273,7 +273,8 @@ class BrowserColumn(Pager): # Extract linemode-related information from the drawn object metadata = None - current_linemode = drawn.linemode_dict[drawn._linemode] + current_linemode = \ + drawn.linemode_dict[drawn._linemode] # pylint: disable=protected-access if current_linemode.uses_metadata: metadata = self.fm.metadata.get_metadata(drawn.path) if not all(getattr(metadata, tag) @@ -303,7 +304,7 @@ class BrowserColumn(Pager): text = current_linemode.filetitle(drawn, metadata) if drawn.marked and (self.main_column or - self.settings.display_tags_in_all_columns): + self.settings.display_tags_in_all_columns): text = " " + text # Computing predisplay data. predisplay contains a list of lists @@ -366,13 +367,13 @@ class BrowserColumn(Pager): predisplay_left.append([' ' * space, []]) elif space < 0: raise Exception("Error: there is not enough space to write " - "the text. I have computed spaces wrong.") + "the text. I have computed spaces wrong.") # Computing display data. Now we compute the display_data list # ready to display in curses. It is a list of lists [string, attr] this_color = base_color + list(drawn.mimetype_tuple) + \ - self._draw_directory_color(i, drawn, copied) + self._draw_directory_color(i, drawn, copied) display_data = [] drawn.display_data[key] = display_data @@ -387,11 +388,11 @@ class BrowserColumn(Pager): def _get_index_of_selected_file(self): if self.fm.ui.viewmode == 'multipane' and hasattr(self, 'tab'): return self.tab.pointer - else: - return self.target.pointer + return self.target.pointer - def _total_len(self, predisplay): - return sum([len(WideString(s)) for s, L in predisplay]) + @staticmethod + def _total_len(predisplay): + return sum([len(WideString(s)) for s, _ in predisplay]) def _draw_text_display(self, text, space): wtext = WideString(text) @@ -479,7 +480,7 @@ class BrowserColumn(Pager): return this_color - def _get_scroll_begin(self): + def _get_scroll_begin(self): # pylint: disable=too-many-return-statements """Determines scroll_begin (the position of the first displayed file)""" offset = self.settings.scroll_offset dirsize = len(self.target) @@ -509,12 +510,10 @@ class BrowserColumn(Pager): return original if projected > upper_limit: - return min(dirsize - winsize, - original + (projected - upper_limit)) + return min(dirsize - winsize, original + (projected - upper_limit)) if projected < upper_limit: - return max(0, - original - (lower_limit - projected)) + return max(0, original - (lower_limit - projected)) return original diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index db04a9c8..3c056aa9 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -3,6 +3,8 @@ """The Console widget implements a vim-like console""" +from __future__ import (absolute_import, print_function) + import curses import re from collections import deque @@ -14,7 +16,7 @@ from ranger.container.history import History, HistoryEmptyException import ranger -class Console(Widget): +class Console(Widget): # pylint: disable=too-many-instance-attributes,too-many-public-methods visible = False last_cursor_mode = None history_search_pattern = None @@ -32,20 +34,20 @@ class Console(Widget): def __init__(self, win): Widget.__init__(self, win) - self.clear() + self.pos = 0 + self.line = '' self.history = History(self.settings.max_console_history_size) # load history from files - if not ranger.arg.clean: + if not ranger.args.clean: self.historypath = self.fm.confpath('history') try: - f = open(self.historypath, 'r') + fobj = open(self.historypath, 'r') except Exception: pass else: - for line in f: + for line in fobj: self.history.add(line[:-1]) - f.close() - self.line = "" + fobj.close() self.history_backup = History(self.history) # NOTE: the console is considered in the "question mode" when the @@ -63,20 +65,20 @@ class Console(Widget): def destroy(self): # save history to files - if ranger.arg.clean or not self.settings.save_console_history: + if ranger.args.clean or not self.settings.save_console_history: return if self.historypath: try: - f = open(self.historypath, 'w') + fobj = open(self.historypath, 'w') except Exception: pass else: for entry in self.history_backup: try: - f.write(entry + '\n') + fobj.write(entry + '\n') except UnicodeEncodeError: pass - f.close() + fobj.close() Widget.destroy(self) def draw(self): @@ -178,7 +180,7 @@ class Console(Widget): if not self.question_queue: return False question = self.question_queue[0] - text, callback, answers = question + _, callback, answers = question if answer in answers: self.question_queue.pop(0) callback(answer) @@ -269,23 +271,23 @@ class Console(Widget): # Ensure that the pointer is moved utf-char-wise if self.fm.py3: self.pos = direction.move( - direction=direction.right(), - minimum=0, - maximum=len(self.line) + 1, - current=self.pos) + direction=direction.right(), + minimum=0, + maximum=len(self.line) + 1, + current=self.pos) else: if self.fm.py3: - uc = list(self.line) + uchar = list(self.line) upos = len(self.line[:self.pos]) else: - uc = list(self.line.decode('utf-8', 'ignore')) + uchar = list(self.line.decode('utf-8', 'ignore')) upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) newupos = direction.move( - direction=direction.right(), - minimum=0, - maximum=len(uc) + 1, - current=upos) - self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore')) + direction=direction.right(), + minimum=0, + maximum=len(uchar) + 1, + current=upos) + self.pos = len(''.join(uchar[:newupos]).encode('utf-8', 'ignore')) def move_word(self, **keywords): direction = Direction(keywords) @@ -304,7 +306,7 @@ class Console(Widget): ... # it works fine in ranger, even with unicode input... ... line = "ohai world, this is dog" ... else: - ... line = "\u30AA\u30CF\u30E8\u30A6 world, this is dog" + ... line = "\\u30AA\\u30CF\\u30E8\\u30A6 world, this is dog" >>> Console.move_by_word(line, 0, -1) 0 >>> Console.move_by_word(line, 0, 1) @@ -378,7 +380,8 @@ class Console(Widget): if backward: right_part = self.line[self.pos:] i = self.pos - 2 - while i >= 0 and re.match(r'[\w\d]', self.line[i], re.U): + while i >= 0 and re.match( + r'[\w\d]', self.line[i], re.UNICODE): # pylint: disable=no-member i -= 1 self.copy = self.line[i + 1:self.pos] self.line = self.line[:i + 1] + right_part @@ -386,7 +389,8 @@ class Console(Widget): else: left_part = self.line[:self.pos] i = self.pos + 1 - while i < len(self.line) and re.match(r'[\w\d]', self.line[i], re.U): + while i < len(self.line) and re.match( + r'[\w\d]', self.line[i], re.UNICODE): # pylint: disable=no-member i += 1 self.copy = self.line[self.pos:i] if i >= len(self.line): @@ -409,11 +413,11 @@ class Console(Widget): self.pos = len(left_part) self.line = left_part + self.line[self.pos + 1:] else: - uc = list(self.line.decode('utf-8', 'ignore')) + uchar = list(self.line.decode('utf-8', 'ignore')) upos = len(self.line[:self.pos].decode('utf-8', 'ignore')) + mod - left_part = ''.join(uc[:upos]).encode('utf-8', 'ignore') + left_part = ''.join(uchar[:upos]).encode('utf-8', 'ignore') self.pos = len(left_part) - self.line = left_part + ''.join(uc[upos + 1:]).encode('utf-8', 'ignore') + self.line = left_part + ''.join(uchar[upos + 1:]).encode('utf-8', 'ignore') self.on_line_change() def execute(self, cmd=None): @@ -455,8 +459,7 @@ class Console(Widget): cmd = self._get_cmd() if cmd: return cmd.tab(tabnum) - else: - return None + return None return self.fm.commands.command_generator(self.line) @@ -494,7 +497,7 @@ class Console(Widget): cmd.quickly_executed = True self.execute(cmd) - def ask(self, text, callback, choices=['y', 'n']): + def ask(self, text, callback, choices=None): """Open a question prompt with predefined choices The "text" is displayed as the question text and should include a list @@ -507,7 +510,9 @@ class Console(Widget): The first choice is used when the user presses <Enter>, the second choice is used when the user presses <ESC>. """ - self.question_queue.append((text, callback, choices)) + self.question_queue.append( + (text, callback, choices if choices is not None else ['y', 'n'])) + if __name__ == '__main__': import doctest diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index 0c81079b..4182b189 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -3,16 +3,18 @@ """The pager displays text and allows you to scroll inside it.""" -from . import Widget -from ranger.core.loader import CommandLoader +from __future__ import (absolute_import, print_function) + from ranger.gui import ansi from ranger.ext.direction import Direction from ranger.ext.img_display import ImgDisplayUnsupportedException +from . import Widget + # TODO: Scrolling in embedded pager -class Pager(Widget): +class Pager(Widget): # pylint: disable=too-many-instance-attributes source = None source_is_stream = False @@ -81,7 +83,7 @@ class Pager(Widget): if not self.image: line_gen = self._generate_lines( - starty=self.scroll_begin, startx=self.startx) + starty=self.scroll_begin, startx=self.startx) for line, i in zip(line_gen, range(self.hei)): self._draw_line(i, line) @@ -94,11 +96,11 @@ class Pager(Widget): self.need_redraw_image = False try: self.fm.image_displayer.draw(self.image, self.x, self.y, - self.wid, self.hei) + self.wid, self.hei) except ImgDisplayUnsupportedException: self.fm.settings.preview_images = False - except Exception as e: - self.fm.notify(e, bad=True) + except Exception as ex: + self.fm.notify(ex, bad=True) else: self.image_drawn = True @@ -121,31 +123,31 @@ class Pager(Widget): direction = Direction(kw) if direction.horizontal(): self.startx = direction.move( - direction=direction.right(), - override=narg, - maximum=self.max_width, - current=self.startx, - pagesize=self.wid, - offset=-self.wid + 1) + direction=direction.right(), + override=narg, + maximum=self.max_width, + current=self.startx, + pagesize=self.wid, + offset=-self.wid + 1) if direction.vertical(): movement = dict( - direction=direction.down(), - override=narg, - current=self.scroll_begin, - pagesize=self.hei, - offset=-self.hei + 1) + direction=direction.down(), + override=narg, + current=self.scroll_begin, + pagesize=self.hei, + offset=-self.hei + 1) if self.source_is_stream: # For streams, we first pretend that the content ends much later, # in case there are still unread lines. desired_position = direction.move( - maximum=len(self.lines) + 9999, - **movement) + maximum=len(self.lines) + 9999, + **movement) # Then, read the new lines as needed to produce a more accurate # maximum for the movement: self._get_line(desired_position + self.hei) self.scroll_begin = direction.move( - maximum=len(self.lines), - **movement) + maximum=len(self.lines), + **movement) def press(self, key): self.fm.ui.keymaps.use_keymap('pager') @@ -190,13 +192,13 @@ class Pager(Widget): self.markup = 'ansi' if not self.source_is_stream and strip: - self.lines = map(lambda x: x.strip(), self.lines) + self.lines = [line.strip() for line in self.lines] self.source = source return True def click(self, event): - n = event.ctrl() and 1 or 3 + n = 1 if event.ctrl() else 3 direction = event.mouse_wheel_direction() if direction: self.move(down=direction * n) @@ -209,10 +211,10 @@ class Pager(Widget): except (KeyError, IndexError): if attempt_to_read and self.source_is_stream: try: - for l in self.source: - if len(l) > self.max_width: - self.max_width = len(l) - self.lines.append(l) + for line in self.source: + if len(line) > self.max_width: + self.max_width = len(line) + self.lines.append(line) if len(self.lines) > n: break except (UnicodeError, IOError): @@ -227,7 +229,7 @@ class Pager(Widget): while True: try: line = self._get_line(i).expandtabs(4) - if self.markup is 'ansi': + if self.markup == 'ansi': line = ansi.char_slice(line, startx, self.wid) + ansi.reset else: line = line[startx:self.wid + startx] diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index 0ac62d86..bb0b2d4c 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -8,18 +8,21 @@ print for the current file. The right side shows directory information such as the space used by all the files in this directory. """ +from __future__ import (absolute_import, print_function) + import os +from os import getuid, readlink from pwd import getpwuid from grp import getgrgid -from os import getuid, readlink from time import time, strftime, localtime from ranger.ext.human_readable import human_readable -from . import Widget from ranger.gui.bar import Bar +from . import Widget + -class StatusBar(Widget): +class StatusBar(Widget): # pylint: disable=too-many-instance-attributes __doc__ = __doc__ owners = {} groups = {} @@ -37,7 +40,7 @@ class StatusBar(Widget): Widget.__init__(self, win) self.column = column self.settings.signal_bind('setopt.display_size_in_status_bar', - self.request_redraw, weak=True) + self.request_redraw, weak=True) def request_redraw(self): self.need_redraw = True @@ -111,7 +114,7 @@ class StatusBar(Widget): def _draw_message(self): self.win.erase() self.color('in_statusbar', 'message', - self.msg.bad and 'bad' or 'good') + self.msg.bad and 'bad' or 'good') self.addnstr(0, 0, self.msg.text, self.wid) def _draw_hint(self): @@ -133,7 +136,7 @@ class StatusBar(Widget): space_left -= len(string) starting_point += len(string) - def _get_left_part(self, bar): + def _get_left_part(self, bar): # pylint: disable=too-many-branches,too-many-statements left = bar.left if self.column is not None and self.column.target is not None\ @@ -156,7 +159,7 @@ class StatusBar(Widget): perms = '--%s--' % self.fm.mode.upper() else: perms = target.get_permission_string() - how = getuid() == stat.st_uid and 'good' or 'bad' + how = 'good' if getuid() == stat.st_uid else 'bad' left.add(perms, 'permissions', how) left.add_space() left.add(str(stat.st_nlink), 'nlink') @@ -166,7 +169,7 @@ class StatusBar(Widget): left.add(self._get_group(target), 'group') if target.is_link: - how = target.exists and 'good' or 'bad' + how = 'good' if target.exists else 'bad' try: dest = readlink(target.path) except Exception: @@ -230,7 +233,7 @@ class StatusBar(Widget): except KeyError: return str(gid) - def _get_right_part(self, bar): + def _get_right_part(self, bar): # pylint: disable=too-many-branches right = bar.right if self.column is None: return @@ -261,8 +264,11 @@ class StatusBar(Widget): if len(target.marked_items) == target.size: right.add(human_readable(target.disk_usage, separator='')) else: - sumsize = sum(f.size for f in target.marked_items if not - f.is_directory or f._cumulative_size_calculated) + sumsize = sum( + f.size for f in target.marked_items + if not f.is_directory or + f._cumulative_size_calculated # pylint: disable=protected-access + ) right.add(human_readable(sumsize, separator='')) right.add("/" + str(len(target.marked_items))) else: @@ -280,9 +286,8 @@ class StatusBar(Widget): # Indicate that there are marked files. Useful if you scroll # away and don't see them anymore. right.add('Mrk', base, 'marked') - elif len(target.files): - right.add(str(target.pointer + 1) + '/' - + str(len(target.files)) + ' ', base) + elif target.files: + right.add(str(target.pointer + 1) + '/' + str(len(target.files)) + ' ', base) if max_pos <= 0: right.add('All', base, 'all') elif pos == 0: @@ -291,7 +296,7 @@ class StatusBar(Widget): right.add('Bot', base, 'bot') else: right.add('{0:0.0%}'.format(float(pos) / max_pos), - base, 'percentage') + base, 'percentage') else: right.add('0/0 All', base, 'all') @@ -319,7 +324,7 @@ def get_free_space(path): return stat.f_bavail * stat.f_frsize -class Message(object): +class Message(object): # pylint: disable=too-few-public-methods elapse = None text = None bad = False diff --git a/ranger/gui/widgets/taskview.py b/ranger/gui/widgets/taskview.py index f05606c9..d7c5571b 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -3,9 +3,12 @@ """The TaskView allows you to modify what the loader is doing.""" -from . import Widget +from __future__ import (absolute_import, print_function) + from ranger.ext.accumulator import Accumulator +from . import Widget + class TaskView(Widget, Accumulator): old_lst = None @@ -50,10 +53,8 @@ class TaskView(Widget, Accumulator): clr.append('selected') descr = obj.get_description() - if obj.progressbar_supported and obj.percent >= 0 \ - and obj.percent <= 100: - self.addstr(y, 0, "%3.2f%% - %s" % - (obj.percent, descr), self.wid) + if obj.progressbar_supported and obj.percent >= 0 and obj.percent <= 100: + self.addstr(y, 0, "%3.2f%% - %s" % (obj.percent, descr), self.wid) wid = int(self.wid / 100.0 * obj.percent) self.color_at(y, 0, self.wid, tuple(clr)) self.color_at(y, 0, wid, tuple(clr), 'loaded') @@ -79,7 +80,7 @@ class TaskView(Widget, Accumulator): if self.fm.loader.queue: self.fm.loader.remove(index=i) - def task_move(self, to, i=None): + def task_move(self, to, i=None): # pylint: disable=invalid-name if i is None: i = self.pointer diff --git a/ranger/gui/widgets/titlebar.py b/ranger/gui/widgets/titlebar.py index d46fec62..fc5aeeab 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -6,11 +6,13 @@ It displays the current path among other things. """ +from __future__ import (absolute_import, print_function) + from os.path import basename +from ranger.gui.bar import Bar from . import Widget -from ranger.gui.bar import Bar class TitleBar(Widget): @@ -41,8 +43,7 @@ class TitleBar(Widget): self._print_result(self.result) if self.wid > 2: self.color('in_titlebar', 'throbber') - self.addnstr(self.y, self.wid - 2 - self.tab_width, - self.throbber, 1) + self.addnstr(self.y, self.wid - 2 - self.tab_width, self.throbber, 1) def click(self, event): """Handle a MouseEvent""" @@ -56,7 +57,7 @@ class TitleBar(Widget): return False pos = self.wid - 1 - for tabname in reversed(self.fm._get_tab_list()): + for tabname in reversed(self.fm._get_tab_list()): # pylint: disable=protected-access tabtext = self._get_tab_text(tabname) pos -= len(tabtext) if event.x > pos: @@ -123,13 +124,13 @@ class TitleBar(Widget): def _get_right_part(self, bar): # TODO: fix that pressed keys are cut off when chaining CTRL keys - kb = str(self.fm.ui.keybuffer) - self.old_keybuffer = kb - bar.addright(kb, 'keybuffer', fixed=True) + kbuf = str(self.fm.ui.keybuffer) + self.old_keybuffer = kbuf + bar.addright(kbuf, 'keybuffer', fixed=True) bar.addright(' ', 'space', fixed=True) self.tab_width = 0 if len(self.fm.tabs) > 1: - for tabname in self.fm._get_tab_list(): + for tabname in self.fm._get_tab_list(): # pylint: disable=protected-access tabtext = self._get_tab_text(tabname) self.tab_width += len(tabtext) clr = 'good' if tabname == self.fm.current_tab else 'bad' diff --git a/ranger/gui/widgets/view_base.py b/ranger/gui/widgets/view_base.py index a50fb5ce..001f40b3 100644 --- a/ranger/gui/widgets/view_base.py +++ b/ranger/gui/widgets/view_base.py @@ -3,25 +3,30 @@ """The base GUI element for views on the directory""" +from __future__ import (absolute_import, print_function) + import curses -import _curses from ranger.ext.keybinding_parser import key_to_string from . import Widget from ..displayable import DisplayableContainer -class ViewBase(Widget, DisplayableContainer): +class ViewBase(Widget, DisplayableContainer): # pylint: disable=too-many-instance-attributes draw_bookmarks = False need_clear = False draw_hints = False draw_info = False - def __init__(self, win): + def __init__(self, win): # pylint: disable=super-init-not-called DisplayableContainer.__init__(self, win) self.fm.signal_bind('move', self.request_clear) self.old_draw_borders = self.settings.draw_borders + self.columns = None + self.main_column = None + self.pager = None + def request_clear(self): self.need_clear = True @@ -44,17 +49,17 @@ class ViewBase(Widget, DisplayableContainer): self._draw_info(self.draw_info) def finalize(self): - if hasattr(self, 'pager') and self.pager.visible: + if self.pager is not None and self.pager.visible: try: self.fm.ui.win.move(self.main_column.y, self.main_column.x) except Exception: pass else: try: - x = self.main_column.x - y = self.main_column.y + self.main_column.target.pointer\ - - self.main_column.scroll_begin - self.fm.ui.win.move(y, x) + col_x = self.main_column.x + col_y = self.main_column.y + self.main_column.target.pointer \ + - self.main_column.scroll_begin + self.fm.ui.win.move(col_y, col_x) except Exception: pass @@ -64,9 +69,14 @@ class ViewBase(Widget, DisplayableContainer): self.color_reset() self.need_clear = True - sorted_bookmarks = sorted((item for item in self.fm.bookmarks - if self.fm.settings.show_hidden_bookmarks or - '/.' not in item[1].path), key=lambda t: t[0].lower()) + sorted_bookmarks = sorted( + ( + item for item in self.fm.bookmarks + if self.fm.settings.show_hidden_bookmarks or + '/.' not in item[1].path + ), + key=lambda t: t[0].lower(), + ) hei = min(self.hei - 1, len(sorted_bookmarks)) ystart = self.hei - hei @@ -101,21 +111,20 @@ class ViewBase(Widget, DisplayableContainer): self.columns[-1].clear_image(force=True) self.need_clear = True hints = [] - for k, v in self.fm.ui.keybuffer.pointer.items(): - k = key_to_string(k) - if isinstance(v, dict): + for key, value in self.fm.ui.keybuffer.pointer.items(): + key = key_to_string(key) + if isinstance(value, dict): text = '...' else: - text = v + text = value if text.startswith('hint') or text.startswith('chain hint'): continue - hints.append((k, text)) + hints.append((key, text)) hints.sort(key=lambda t: t[1]) hei = min(self.hei - 1, len(hints)) ystart = self.hei - hei - self.addnstr(ystart - 1, 0, "key command".ljust(self.wid), - self.wid) + self.addnstr(ystart - 1, 0, "key command".ljust(self.wid), self.wid) try: self.win.chgat(ystart - 1, 0, curses.A_UNDERLINE) except Exception: @@ -128,24 +137,6 @@ class ViewBase(Widget, DisplayableContainer): self.addnstr(i, 0, string, self.wid) i += 1 - def _collapse(self): - # Should the last column be cut off? (Because there is no preview) - if not self.settings.collapse_preview or not self.preview \ - or not self.stretch_ratios: - return False - result = not self.columns[-1].has_preview() - target = self.columns[-1].target - if not result and target and target.is_file: - if self.fm.settings.preview_script and \ - self.fm.settings.use_preview_script: - try: - result = not self.fm.previews[target.realpath]['foundpreview'] - except Exception: - return self.old_collapse - - self.old_collapse = result - return result - def click(self, event): if DisplayableContainer.click(self, event): return True @@ -154,7 +145,7 @@ class ViewBase(Widget, DisplayableContainer): self.main_column.scroll(direction) return False - def resize(self, y, x, hei, wid): + def resize(self, y, x, hei=None, wid=None): DisplayableContainer.resize(self, y, x, hei, wid) def poke(self): diff --git a/ranger/gui/widgets/view_miller.py b/ranger/gui/widgets/view_miller.py index 42013fe9..47e66060 100644 --- a/ranger/gui/widgets/view_miller.py +++ b/ranger/gui/widgets/view_miller.py @@ -3,17 +3,19 @@ """ViewMiller arranges the view in miller columns""" +from __future__ import (absolute_import, print_function) + import curses import _curses from ranger.container import settings -from ranger.ext.signals import Signal +from ranger.gui.widgets.view_base import ViewBase + from .browsercolumn import BrowserColumn from .pager import Pager from ..displayable import DisplayableContainer -from ranger.gui.widgets.view_base import ViewBase -class ViewMiller(ViewBase): +class ViewMiller(ViewBase): # pylint: disable=too-many-ancestors,too-many-instance-attributes ratios = None preview = True is_collapsed = False @@ -33,11 +35,11 @@ class ViewMiller(ViewBase): for option in ('preview_directories', 'preview_files'): self.settings.signal_bind('setopt.' + option, - self._request_clear_if_has_borders, weak=True) + self._request_clear_if_has_borders, weak=True) self.settings.signal_bind('setopt.column_ratios', self.request_clear) self.settings.signal_bind('setopt.column_ratios', self.rebuild, - priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) + priority=settings.SIGNAL_PRIORITY_AFTER_SYNC) self.old_draw_borders = self.settings.draw_borders @@ -60,17 +62,17 @@ class ViewMiller(ViewBase): last = 0.1 if self.settings.padding_right else 0 if len(self.ratios) >= 2: self.stretch_ratios = self.ratios[:-2] + \ - ((self.ratios[-2] + self.ratios[-1] * 1.0 - last), - (self.ratios[-1] * last)) + ((self.ratios[-2] + self.ratios[-1] * 1.0 - last), + (self.ratios[-1] * last)) offset = 1 - len(ratios) if self.preview: offset += 1 for level in range(len(ratios)): - fl = BrowserColumn(self.win, level + offset) - self.add_child(fl) - self.columns.append(fl) + column = BrowserColumn(self.win, level + offset) + self.add_child(column) + self.columns.append(column) try: self.main_column = self.columns[self.preview and -2 or -1] @@ -122,18 +124,18 @@ class ViewMiller(ViewBase): # Shift the rightmost vertical line to the left to create a padding, # but only when padding_right is on, the preview column is collapsed # and we did not open the pager to "zoom" in to the file. - if self.settings.padding_right and not self.pager.visible and \ - self.is_collapsed: + if self.settings.padding_right and not self.pager.visible and self.is_collapsed: right_end = self.columns[-1].x - 1 if right_end < left_start: right_end = self.wid - 1 # Draw horizontal lines and the leftmost vertical line try: + # pylint: disable=no-member win.hline(0, left_start, curses.ACS_HLINE, right_end - left_start) - win.hline(self.hei - 1, left_start, curses.ACS_HLINE, - right_end - left_start) + win.hline(self.hei - 1, left_start, curses.ACS_HLINE, right_end - left_start) win.vline(1, left_start, curses.ACS_VLINE, self.hei - 2) + # pylint: enable=no-member except _curses.error: pass @@ -148,23 +150,29 @@ class ViewMiller(ViewBase): x = child.x + child.wid y = self.hei - 1 try: + # pylint: disable=no-member win.vline(1, x, curses.ACS_VLINE, y - 1) self.addch(0, x, curses.ACS_TTEE, 0) self.addch(y, x, curses.ACS_BTEE, 0) + # pylint: enable=no-member except Exception: # in case it's off the boundaries pass # Draw the last vertical line try: + # pylint: disable=no-member win.vline(1, right_end, curses.ACS_VLINE, self.hei - 2) + # pylint: enable=no-member except _curses.error: pass + # pylint: disable=no-member self.addch(0, left_start, curses.ACS_ULCORNER) self.addch(self.hei - 1, left_start, curses.ACS_LLCORNER) self.addch(0, right_end, curses.ACS_URCORNER) self.addch(self.hei - 1, right_end, curses.ACS_LRCORNER) + # pylint: enable=no-member def _collapse(self): # Should the last column be cut off? (Because there is no preview) @@ -184,7 +192,7 @@ class ViewMiller(ViewBase): self.old_collapse = result return result - def resize(self, y, x, hei, wid): + def resize(self, y, x, hei=None, wid=None): """Resize all the columns according to the given ratio""" ViewBase.resize(self, y, x, hei, wid) @@ -213,17 +221,14 @@ class ViewMiller(ViewBase): continue if i == last_i - 1: - self.pager.resize(pad, left, hei - pad * 2, - max(1, self.wid - left - pad)) + self.pager.resize(pad, left, hei - pad * 2, max(1, self.wid - left - pad)) if cut_off: - self.columns[i].resize(pad, left, hei - pad * 2, - max(1, self.wid - left - pad)) + self.columns[i].resize(pad, left, hei - pad * 2, max(1, self.wid - left - pad)) continue try: - self.columns[i].resize(pad, left, hei - pad * 2, - max(1, wid - 1)) + self.columns[i].resize(pad, left, hei - pad * 2, max(1, wid - 1)) except KeyError: pass @@ -257,7 +262,7 @@ class ViewMiller(ViewBase): # Show the preview column when it has a preview but has # been hidden (e.g. because of padding_right = False) if not self.columns[-1].visible and self.columns[-1].has_preview() \ - and not self.pager.visible: + and not self.pager.visible: self.columns[-1].visible = True if self.preview and self.is_collapsed != self._collapse(): diff --git a/ranger/gui/widgets/view_multipane.py b/ranger/gui/widgets/view_multipane.py index f8efd80f..ac2d5fac 100644 --- a/ranger/gui/widgets/view_multipane.py +++ b/ranger/gui/widgets/view_multipane.py @@ -1,11 +1,14 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +from __future__ import (absolute_import, print_function) + from ranger.gui.widgets.view_base import ViewBase from ranger.gui.widgets.browsercolumn import BrowserColumn -class ViewMultipane(ViewBase): +class ViewMultipane(ViewBase): # pylint: disable=too-many-ancestors + def __init__(self, win): ViewBase.__init__(self, win) @@ -40,12 +43,12 @@ class ViewMultipane(ViewBase): self.add_child(column) self.resize(self.y, self.x, self.hei, self.wid) - def resize(self, y, x, hei, wid): + def resize(self, y, x, hei=None, wid=None): ViewBase.resize(self, y, x, hei, wid) column_width = int((float(wid) - len(self.columns) + 1) / len(self.columns)) left = 0 top = 0 - for i, column in enumerate(self.columns): + for column in self.columns: column.resize(top, left, hei, max(1, column_width)) left += column_width + 1 column.need_redraw = True |