diff options
author | nfnty <git@nfnty.se> | 2016-12-21 05:06:55 +0100 |
---|---|---|
committer | nfnty <git@nfnty.se> | 2017-01-17 05:59:02 +0100 |
commit | b3d031a913814900467358b2adf20a148bf6de1a (patch) | |
tree | 6f5b435817ec9cbe850fb73485a7e6c77da28c14 | |
parent | 76791a70467d7223a966aa9f12f5583b01d704a8 (diff) | |
download | ranger-b3d031a913814900467358b2adf20a148bf6de1a.tar.gz |
linting: pylint and flake8
81 files changed, 1105 insertions, 920 deletions
diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..db515b53 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,15 @@ +[BASIC] +good-names=i,j,k,n,x,y,ex,Run,_,fm,ui,fg,bg +bad-names=foo,baz,toto,tutu,tata + +[DESIGN] +max-args=6 +max-branches=16 + +[FORMAT] +max-line-length = 99 +disable=locally-disabled,locally-enabled,missing-docstring,duplicate-code,fixme,consider-iterating-dictionary,broad-except,cyclic-import,attribute-defined-outside-init,access-member-before-definition + +[TYPECHECK] +ignored-classes=ranger.ext.openstruct.OpenStruct,ranger.core.runner.Context,ranger.core.actions.Actions,ranger.gui.widgets.view_base.ViewBase,ranger.gui.curses_shortcuts.CursesShortcuts +generated-members=ranger.arg diff --git a/.travis.yml b/.travis.yml index 2bc017d3..9b194d4b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,15 @@ -language: python +dist: 'trusty' +language: 'python' python: - - "2.6" - - "2.7" - - "3.2" - - "3.3" - - "3.4" - - "3.5" - - "3.5-dev" # 3.5 development branch - - "nightly" # currently points to 3.6-dev + - '2.6' + - '2.7' + - '3.2' + - '3.3' + - '3.4' + - '3.5' -# command to install dependencies install: - - pip install pytest + - 'pip install pytest pylint flake8' -# command to run tests script: - - make test + - 'make test' diff --git a/Makefile b/Makefile index 870901da..beb0a325 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,15 @@ doc: cleandoc pydoc.writedocs("$(CWD)")' find . -name \*.html -exec sed -i 's|'"$(CWD)"'|../..|g' -- {} \; +TEST_PATHS_MAIN = doc/tools/*.py examples/*.py $(filter-out ranger/__pycache__ ranger/config ranger/data, $(wildcard ranger/*)) tests *.py +TEST_PATH_CONFIG = ranger/config + test: + @echo "Running pylint..." + pylint $(TEST_PATHS_MAIN) + pylint --rcfile=$(TEST_PATH_CONFIG)/.pylintrc $(TEST_PATH_CONFIG) + @echo "Running flake8..." + flake8 $(TEST_PATHS_MAIN) $(TEST_PATH_CONFIG) @echo "Running doctests..." @for FILE in $(shell grep -IHm 1 doctest -r ranger | grep $(FILTER) | cut -d: -f1); do \ echo "Testing $$FILE..."; \ diff --git a/doc/tools/convert_papermode_to_metadata.py b/doc/tools/convert_papermode_to_metadata.py index 61427c83..e73df344 100755 --- a/doc/tools/convert_papermode_to_metadata.py +++ b/doc/tools/convert_papermode_to_metadata.py @@ -14,10 +14,9 @@ import json import os import sys -if sys.version < '3.': - getuserinput = raw_input -else: - getuserinput = input +if sys.version_info[0] < 3: + input = raw_input # NOQA pylint: disable=undefined-variable,redefined-builtin,invalid-name + FIELDS = ["name", "year", "title", "authors", "url"] @@ -30,7 +29,7 @@ def replace(source, target): # Ask for user confirmation if the target file already exists if os.path.exists(target): sys.stdout.write("Warning: target file `%s' exists! Overwrite? [y/N]") - userinput = getuserinput() + userinput = input() if not (userinput.startswith("y") or userinput.startswith("Y")): print("Skipping file `%s'" % source) return @@ -63,6 +62,7 @@ def replace(source, target): else: print("Skipping writing `%s' due to a lack of data" % target) + if __name__ == "__main__": if set(['--help', '-h']) & set(sys.argv[1:]): print(__doc__.strip()) diff --git a/doc/tools/print_colors.py b/doc/tools/print_colors.py index 8cc944ed..c4b6e736 100755 --- a/doc/tools/print_colors.py +++ b/doc/tools/print_colors.py @@ -5,27 +5,26 @@ It will exit after a keypress. """ import curses -from curses import * -@wrapper +@curses.wrapper def main(win): def print_all_colors(attr): - for c in range(-1, curses.COLORS): + for color in range(-1, curses.COLORS): try: - init_pair(c, c, 0) + curses.init_pair(color, color, 0) except Exception: pass else: - win.addstr(str(c) + ' ', color_pair(c) | attr) - start_color() + win.addstr(str(color) + ' ', curses.color_pair(color) | attr) + curses.start_color() try: - use_default_colors() + curses.use_default_colors() except Exception: pass win.addstr("available colors: %d\n\n" % curses.COLORS) print_all_colors(0) win.addstr("\n\n") - print_all_colors(A_BOLD) + print_all_colors(curses.A_BOLD) win.refresh() win.getch() diff --git a/doc/tools/print_keys.py b/doc/tools/print_keys.py index 73091db4..cf915591 100755 --- a/doc/tools/print_keys.py +++ b/doc/tools/print_keys.py @@ -3,18 +3,19 @@ You can use this tool to find out values of keypresses """ -from curses import * +import curses -sep = '; ' +SEPARATOR = '; ' -@wrapper -def main(w): - mousemask(ALL_MOUSE_EVENTS) - mouseinterval(0) + +@curses.wrapper +def main(window): + curses.mousemask(curses.ALL_MOUSE_EVENTS) + curses.mouseinterval(0) while True: - ch = w.getch() - if ch == KEY_MOUSE: - w.addstr(repr(getmouse()) + sep) + char = window.getch() + if char == curses.KEY_MOUSE: + window.addstr(repr(curses.getmouse()) + SEPARATOR) else: - w.addstr(str(ch) + sep) + window.addstr(str(char) + SEPARATOR) diff --git a/examples/plugin_chmod_keybindings.py b/examples/plugin_chmod_keybindings.py index 63f42b0e..72e90121 100644 --- a/examples/plugin_chmod_keybindings.py +++ b/examples/plugin_chmod_keybindings.py @@ -5,11 +5,13 @@ # for the "chmod" command. import ranger.api -old_hook_init = ranger.api.hook_init + + +HOOK_INIT_OLD = ranger.api.hook_init def hook_init(fm): - old_hook_init(fm) + HOOK_INIT_OLD(fm) # Generate key bindings for the chmod command command = "map {0}{1}{2} shell -d chmod {1}{0}{2} %s" @@ -18,4 +20,5 @@ def hook_init(fm): fm.execute_console(command.format('-', mode, perm)) fm.execute_console(command.format('+', mode, perm)) + ranger.api.hook_init = hook_init diff --git a/examples/plugin_file_filter.py b/examples/plugin_file_filter.py index 5d5f1466..486e59d9 100644 --- a/examples/plugin_file_filter.py +++ b/examples/plugin_file_filter.py @@ -5,19 +5,20 @@ # Save the original filter function import ranger.container.directory -old_accept_file = ranger.container.directory.accept_file -HIDE_FILES = ("/boot", "/sbin", "/proc", "/sys") -# Define a new one +ACCEPT_FILE_OLD = ranger.container.directory.accept_file + +HIDE_FILES = ("/boot", "/sbin", "/proc", "/sys") +# Define a new one def custom_accept_file(file, filters): if not file.fm.settings.show_hidden and file.path in HIDE_FILES: return False else: - return old_accept_file(file, filters) + return ACCEPT_FILE_OLD(file, filters) + # Overwrite the old function -import ranger.container.directory ranger.container.directory.accept_file = custom_accept_file diff --git a/examples/plugin_hello_world.py b/examples/plugin_hello_world.py index 0158a653..dc4c3fee 100644 --- a/examples/plugin_hello_world.py +++ b/examples/plugin_hello_world.py @@ -9,7 +9,7 @@ import ranger.api # Save the previously existing hook, because maybe another module already # extended that hook and we don't want to lose it: -old_hook_ready = ranger.api.hook_ready +HOOK_READY_OLD = ranger.api.hook_ready # Create a replacement for the hook that... @@ -19,7 +19,8 @@ def hook_ready(fm): fm.notify("Hello World") # ...and calls the saved hook. If you don't care about the return value, # simply return the return value of the previous hook to be safe. - return old_hook_ready(fm) + return HOOK_READY_OLD(fm) + # Finally, "monkey patch" the existing hook_ready function with our replacement: ranger.api.hook_ready = hook_ready diff --git a/examples/plugin_ipc.py b/examples/plugin_ipc.py index 47fb1c84..ef87c3c5 100644 --- a/examples/plugin_ipc.py +++ b/examples/plugin_ipc.py @@ -9,15 +9,16 @@ import ranger.api -old_hook_init = ranger.api.hook_init + +HOOK_INIT_OLD = ranger.api.hook_init def hook_init(fm): try: # Create a FIFO. import os - IPC_FIFO = "/tmp/ranger-ipc." + str(os.getpid()) - os.mkfifo(IPC_FIFO) + ipc_fifo = "/tmp/ranger-ipc." + str(os.getpid()) + os.mkfifo(ipc_fifo) # Start the reader thread. try: @@ -30,7 +31,7 @@ def hook_init(fm): with open(filepath, 'r') as fifo: line = fifo.read() fm.execute_console(line.strip()) - thread.start_new_thread(ipc_reader, (IPC_FIFO,)) + thread.start_new_thread(ipc_reader, (ipc_fifo,)) # Remove the FIFO on ranger exit. def ipc_cleanup(filepath): @@ -39,10 +40,12 @@ def hook_init(fm): except IOError: pass import atexit - atexit.register(ipc_cleanup, IPC_FIFO) + atexit.register(ipc_cleanup, ipc_fifo) except IOError: # IPC support disabled pass finally: - old_hook_init(fm) + HOOK_INIT_OLD(fm) + + ranger.api.hook_init = hook_init diff --git a/examples/plugin_linemode.py b/examples/plugin_linemode.py index 851d6213..6f16743d 100644 --- a/examples/plugin_linemode.py +++ b/examples/plugin_linemode.py @@ -6,6 +6,7 @@ # the default linemode. import codecs + import ranger.api from ranger.core.linemode import LinemodeBase @@ -16,3 +17,6 @@ class MyLinemode(LinemodeBase): def filetitle(self, file, metadata): return codecs.encode(file.relative_path, "rot_13") + + def infostring(self, file, metadata): + raise NotImplementedError diff --git a/examples/plugin_new_macro.py b/examples/plugin_new_macro.py index 8dbe435d..1c69b841 100644 --- a/examples/plugin_new_macro.py +++ b/examples/plugin_new_macro.py @@ -4,18 +4,20 @@ # date in commands that allow macros. You can test it with the command # ":shell echo %date; read" -# Save the original macro function +import time + import ranger.core.actions -old_get_macros = ranger.core.actions.Actions._get_macros -# Define a new macro function -import time +# Save the original macro function +GET_MACROS_OLD = ranger.core.actions.Actions._get_macros # pylint: disable=protected-access +# Define a new macro function def get_macros_with_date(self): - macros = old_get_macros(self) + macros = GET_MACROS_OLD(self) macros['date'] = time.strftime('%m/%d/%Y') return macros + # Overwrite the old one -ranger.core.actions.Actions._get_macros = get_macros_with_date +ranger.core.actions.Actions._get_macros = get_macros_with_date # pylint: disable=protected-access diff --git a/examples/plugin_new_sorting_method.py b/examples/plugin_new_sorting_method.py index 012bd7a2..2dd50257 100644 --- a/examples/plugin_new_sorting_method.py +++ b/examples/plugin_new_sorting_method.py @@ -3,6 +3,8 @@ # This plugin adds the sorting algorithm called 'random'. To enable it, type # ":set sort=random" or create a key binding with ":map oz set sort=random" -from ranger.container.directory import Directory from random import random + +from ranger.container.directory import Directory + Directory.sort_dict['random'] = lambda path: random() diff --git a/examples/plugin_pmount.py b/examples/plugin_pmount.py index fe0adffb..6cd325f7 100644 --- a/examples/plugin_pmount.py +++ b/examples/plugin_pmount.py @@ -15,7 +15,8 @@ MOUNT_KEY = '<alt>m' UMOUNT_KEY = '<alt>M' LIST_MOUNTS_KEY = '<alt>N' -old_hook_init = ranger.api.hook_init + +HOOK_INIT_OLD = ranger.api.hook_init def hook_init(fm): @@ -33,6 +34,10 @@ def hook_init(fm): ) fm.execute_console("map {key}{0}{1} chain cd; shell pumount sd{0}{1}".format( disk, part, key=UMOUNT_KEY)) - finally: - return old_hook_init(fm) + except Exception: + pass + + return HOOK_INIT_OLD(fm) + + ranger.api.hook_init = hook_init diff --git a/ranger.py b/ranger.py index 864acf27..f8c00213 100755 --- a/ranger.py +++ b/ranger.py @@ -20,20 +20,20 @@ if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then fi rm -f -- "$tempfile" return $returnvalue -""" and None +""" and None # pylint: disable=pointless-statement -import sys -from os.path import exists, abspath +import sys # NOQA pylint: disable=wrong-import-position +from os.path import exists, abspath # NOQA pylint: disable=wrong-import-position # Need to find out whether or not the flag --clean was used ASAP, # because --clean is supposed to disable bytecode compilation -argv = sys.argv[1:sys.argv.index('--')] if '--' in sys.argv else sys.argv[1:] -sys.dont_write_bytecode = '-c' in argv or '--clean' in argv +ARGV = sys.argv[1:sys.argv.index('--')] if '--' in sys.argv else sys.argv[1:] +sys.dont_write_bytecode = '-c' in ARGV or '--clean' in ARGV # Don't import ./ranger when running an installed binary at /usr/.../ranger if __file__[:4] == '/usr' and exists('ranger') and abspath('.') in sys.path: sys.path.remove(abspath('.')) # Start ranger -import ranger -sys.exit(ranger.main()) +import ranger # NOQA pylint: disable=wrong-import-position,import-self +sys.exit(ranger.main()) # pylint: disable=no-member diff --git a/ranger/__init__.py b/ranger/__init__.py index c2aa804e..e8f05c4d 100644 --- a/ranger/__init__.py +++ b/ranger/__init__.py @@ -10,7 +10,6 @@ program you want to use to open your files with. import sys import os -import tempfile # Information __license__ = 'GPL3' @@ -34,4 +33,4 @@ 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 +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..3c7818b8 100644 --- a/ranger/api/__init__.py +++ b/ranger/api/__init__.py @@ -3,10 +3,13 @@ """Files in this module contain helper functions used in configuration files.""" -# Hooks for use in plugins: +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 +23,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 +35,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 46075f26..76fe588a 100644 --- a/ranger/api/commands.py +++ b/ranger/api/commands.py @@ -4,11 +4,14 @@ # TODO: Add an optional "!" to all commands and set a flag if it's there 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 @@ -26,10 +29,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: @@ -51,9 +56,11 @@ 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): @@ -99,12 +106,12 @@ 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 + return cls.name else: - return self.__name__ + return cls.__name__ def execute(self): """Override this""" @@ -150,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. @@ -242,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 @@ -301,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 @@ -383,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:]: @@ -403,7 +397,7 @@ class FunctionCommand(Command): value = (value == 'True') else: try: - value = float(value) + value = float(value) # pylint: disable=redefined-variable-type except Exception: pass @@ -417,13 +411,13 @@ 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: raise @@ -448,7 +442,7 @@ 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 + 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..3161a2c6 100644 --- a/ranger/api/options.py +++ b/ranger/api/options.py @@ -2,7 +2,10 @@ # 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 +# 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 8d1958c7..557b6075 100644 --- a/ranger/colorschemes/default.py +++ b/ranger/colorschemes/default.py @@ -2,13 +2,17 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. 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: diff --git a/ranger/colorschemes/jungle.py b/ranger/colorschemes/jungle.py index fbcfab7d..b56bb6ed 100644 --- a/ranger/colorschemes/jungle.py +++ b/ranger/colorschemes/jungle.py @@ -1,8 +1,8 @@ # 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 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 95b4bfc3..315b4259 100644 --- a/ranger/colorschemes/snow.py +++ b/ranger/colorschemes/snow.py @@ -2,7 +2,7 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. from ranger.gui.colorscheme import ColorScheme -from ranger.gui.color import * +from ranger.gui.color import default_colors, reverse, bold class Snow(ColorScheme): diff --git a/ranger/colorschemes/solarized.py b/ranger/colorschemes/solarized.py index c3c0cad6..09d66bff 100644 --- a/ranger/colorschemes/solarized.py +++ b/ranger/colorschemes/solarized.py @@ -6,13 +6,17 @@ # This is a modification of Roman Zimbelmann's default colorscheme. 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: 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/config/commands.py b/ranger/config/commands.py index 92c9f385..a3a44278 100755 --- a/ranger/config/commands.py +++ b/ranger/config/commands.py @@ -73,17 +73,21 @@ # 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 collections import deque +import os +import re + +from ranger.api.commands import Command class alias(Command): @@ -121,7 +125,6 @@ class cd(Command): """ def execute(self): - import os.path if self.arg(1) == '-r': self.shift() destination = os.path.realpath(self.rest(1)) @@ -140,7 +143,6 @@ class cd(Command): self.fm.cd(destination) def tab(self, tabnum): - import os from os.path import dirname, basename, expanduser, join cwd = self.fm.thisdir.path @@ -254,7 +256,7 @@ class open_with(Command): 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: @@ -329,11 +331,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) @@ -354,7 +358,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 @@ -388,7 +392,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))) @@ -420,7 +423,6 @@ class setintag(setlocal): class default_linemode(Command): def execute(self): - import re from ranger.container.fsobject import FileSystemObject if len(self.args) < 2: @@ -441,13 +443,13 @@ 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: + lmode = self.rest(1) + if lmode not in FileSystemObject.linemode_dict: self.fm.notify("Invalid linemode: %s; should be %s" % - (linemode, "/".join(FileSystemObject.linemode_dict)), bad=True) + (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 @@ -456,13 +458,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. @@ -522,33 +523,29 @@ 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')) @@ -617,13 +614,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) 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() @@ -638,12 +635,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() + fobj.write("\n".join(fobj.path for fobj in self.fm.copy_buffer)) + fobj.close() class unmark_tag(mark_tag): @@ -732,17 +729,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) @@ -764,10 +760,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) @@ -779,19 +775,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): @@ -801,13 +798,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) @@ -826,12 +824,12 @@ 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: @@ -863,7 +861,7 @@ class bulkrename(Command): 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 @@ -939,29 +937,27 @@ 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): @@ -990,10 +986,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): @@ -1164,7 +1161,7 @@ class scout(Command): 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 pattern = self.pattern @@ -1177,12 +1174,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 @@ -1356,8 +1353,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: @@ -1456,12 +1453,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): @@ -1488,13 +1484,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..4333823c 100644 --- a/ranger/config/commands_sample.py +++ b/ranger/config/commands_sample.py @@ -4,19 +4,18 @@ # 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. # ----------------------------------------------------------------------------- # 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/container/bookmarks.py b/ranger/container/bookmarks.py index 98de84ec..6b188de0 100644 --- a/ranger/container/bookmarks.py +++ b/ranger/container/bookmarks.py @@ -151,16 +151,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 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 +178,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 d48bd7ca..72a9cd33 100644 --- a/ranger/container/directory.py +++ b/ranger/container/directory.py @@ -3,10 +3,9 @@ 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 @@ -65,8 +64,8 @@ def accept_file(file, filters): returns True if file shall be shown, otherwise False. """ - for filter in filters: - if filter and not filter(file): + for filt in filters: + if filt and not filt(file): return False return True @@ -85,14 +84,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] 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 @@ -172,7 +172,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 +207,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): @@ -256,6 +256,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 +270,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 @@ -351,16 +352,22 @@ 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 = File( # pylint: disable=redefined-variable-type + 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) @@ -374,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() @@ -401,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 @@ -479,8 +487,7 @@ 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 dirpath, _, filenames in os.walk(self.path, onerror=lambda _: None): for file in filenames: try: if dirpath == self.path: @@ -499,7 +506,7 @@ class Directory(FileSystemObject, Accumulator, Loadable): 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)) @@ -520,15 +527,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): @@ -539,7 +546,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: @@ -642,7 +649,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) @@ -653,8 +660,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 file in self.files: + file._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 7bb15c84..f6ab880c 100644 --- a/ranger/container/file.py +++ b/ranger/container/file.py @@ -5,7 +5,7 @@ import re from ranger.container.fsobject import FileSystemObject N_FIRST_BYTES = 256 -control_characters = set(chr(n) for n in +control_characters = set(chr(n) for n in # pylint: disable=invalid-name set(range(0, 9)) | set(range(14, 32))) # Don't even try to preview files which match this regular expression: @@ -45,26 +45,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 e4571ffb..619f5df5 100644 --- a/ranger/container/fsobject.py +++ b/ranger/container/fsobject.py @@ -1,23 +1,15 @@ # 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 = '?' - 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,20 +17,37 @@ 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 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, @@ -164,8 +173,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,10 +188,10 @@ 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] + self._mimetype = self.fm.mimetypes.guess_type(bname, False)[0] if self._mimetype is None: self._mimetype = '' @@ -217,7 +226,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 +258,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 +281,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 082a951a..c72d5c3b 100644 --- a/ranger/container/history.py +++ b/ranger/container/history.py @@ -13,8 +13,10 @@ 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: @@ -75,9 +77,9 @@ class History(object): 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: - self._history = self._history[-self.maxlen:] + self._history = self._history[-self.maxlen:] # pylint: disable=protected-access self._index = len(self._history) - future_length - 1 assert self._index < len(self._history) diff --git a/ranger/container/settings.py b/ranger/container/settings.py index 9b6a3917..5424bffb 100644 --- a/ranger/container/settings.py +++ b/ranger/container/settings.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. +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>" @@ -199,10 +200,11 @@ class Settings(SignalDispatcher, FileManagerAware): 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: @@ -259,7 +261,7 @@ class Settings(SignalDispatcher, FileManagerAware): self._raw_set(signal.setting, signal.value, signal.path, signal.tags) -class LocalSettings(): +class LocalSettings(): # pylint: disable=too-few-public-methods def __init__(self, path, parent): self.__dict__['_parent'] = parent @@ -278,8 +280,8 @@ class LocalSettings(): 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..128f984e 100644 --- a/ranger/container/tags.py +++ b/ranger/container/tags.py @@ -39,7 +39,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 +56,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: @@ -72,35 +72,35 @@ class Tags(object): 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 +122,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): @@ -146,8 +146,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 05529000..4f3338d2 100644 --- a/ranger/core/actions.py +++ b/ranger/core/actions.py @@ -1,14 +1,15 @@ # 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 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 +29,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 +41,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. @@ -105,7 +108,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: @@ -151,12 +154,12 @@ class Actions(FileManagerAware, SettingsAware): """ if isinstance(text, Exception): if ranger.arg.debug: - raise + raise text bad = True elif bad is True and ranger.arg.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) @@ -177,8 +180,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() @@ -189,7 +192,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. @@ -197,7 +201,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 @@ -210,28 +215,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: + except ValueError as ex: if ranger.arg.debug: raise else: - return self.notify(e) + return self.notify(ex) try: cmd_class(string, quantifier=quantifier).execute() - except Exception as e: + except Exception as ex: if ranger.arg.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): @@ -247,7 +254,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 @@ -274,7 +281,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 @@ -348,20 +355,20 @@ 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: + except Exception as ex: if ranger.arg.debug: raise else: self.notify('Error in line `%s\':\n %s' % - (line, str(e)), bad=True) + (line, str(ex)), bad=True) def execute_file(self, files, **kw): """Uses the "rifle" module to open/execute a file @@ -385,14 +392,14 @@ class Actions(FileManagerAware, SettingsAware): if ranger.arg.choosefiles: open(ranger.arg.choosefiles, 'w').write("".join( - f.path + "\n" for f in self.fm.thistab.get_selection())) + fobj.path + "\n" for fobj in self.fm.thistab.get_selection())) if ranger.arg.choosefile or ranger.arg.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', '') @@ -400,7 +407,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) @@ -411,7 +418,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: @@ -443,9 +450,9 @@ 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 ') @@ -473,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() @@ -521,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 @@ -532,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) @@ -594,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>. @@ -627,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: @@ -714,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) @@ -727,22 +738,27 @@ 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): + 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 @@ -845,7 +861,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 @@ -887,11 +903,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 @@ -903,17 +919,17 @@ class Actions(FileManagerAware, SettingsAware): except Exception: return False - if version_info[0] == 3: - def sha1_encode(self, path): + @staticmethod + def sha1_encode(path): + if version_info[0] < 3: + return os.path.join(ranger.arg.cachedir, + sha1(path).hexdigest()) + '.jpg' + else: 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): + def get_preview(self, file, width, height): # pylint: disable=too-many-return-statements pager = self.ui.get_pager() path = file.realpath @@ -937,8 +953,8 @@ 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) @@ -962,7 +978,8 @@ class Actions(FileManagerAware, SettingsAware): 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)): + if os.path.isfile(cacheimg) and \ + os.path.getmtime(cacheimg) > os.path.getmtime(path): data['foundpreview'] = True data['imagepreview'] = True pager.set_image(cacheimg) @@ -975,34 +992,34 @@ class Actions(FileManagerAware, SettingsAware): 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: @@ -1020,7 +1037,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: @@ -1173,7 +1190,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): @@ -1199,7 +1216,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 = [] @@ -1225,7 +1242,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): @@ -1257,7 +1274,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) @@ -1293,32 +1310,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): @@ -1348,23 +1365,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(filter(lambda fobj: fobj.path not in files, self.copy_buffer)) + 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 1a3a7cb3..8535a17c 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -21,7 +21,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 +30,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,abstract-method + SignalDispatcher): input_blocked = False input_blocked_until = 0 mode = 'normal' # either 'normal' or 'visual'. @@ -43,15 +46,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 +65,7 @@ class FM(Actions, SignalDispatcher): self.copy_buffer = set() self.do_cut = False self.metadata = MetadataManager() + self.image_displayer = None try: self.username = pwd.getpwuid(os.geteuid()).pw_name @@ -107,7 +108,7 @@ class FM(Actions, SignalDispatcher): if not ranger.arg.clean and self.tags is None: self.tags = Tags(self.confpath('tagged')) elif ranger.arg.clean: - self.tags = TagsDummy("") + self.tags = TagsDummy("") # pylint: disable=redefined-variable-type if self.bookmarks is None: if ranger.arg.clean: @@ -203,7 +204,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 @@ -255,11 +257,11 @@ class FM(Actions, SignalDispatcher): 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) except OSError as err: @@ -270,9 +272,9 @@ class FM(Actions, SignalDispatcher): 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': @@ -298,14 +300,16 @@ class FM(Actions, SignalDispatcher): 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: assert 0, "Should not access relpath_conf in clean mode!" else: return os.path.join(ranger.arg.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: diff --git a/ranger/core/linemode.py b/ranger/core/linemode.py index 4b29443a..d9d91350 100644 --- a/ranger/core/linemode.py +++ b/ranger/core/linemode.py @@ -4,7 +4,8 @@ # Author: Wojciech Siewierski <wojciech.siewierski@onet.pl>, 2015 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 @@ -51,7 +52,7 @@ class LinemodeBase(object): raise NotImplementedError -class DefaultLinemode(LinemodeBase): +class DefaultLinemode(LinemodeBase): # pylint: disable=abstract-method name = "filename" def filetitle(self, file, metadata): @@ -98,7 +99,7 @@ class FileInfoLinemode(LinemodeBase): def infostring(self, file, metadata): if not file.is_directory: - from subprocess import Popen, PIPE, CalledProcessError + from subprocess import CalledProcessError try: fileinfo = spawn.check_output(["file", "-bL", file.path]).strip() except CalledProcessError: diff --git a/ranger/core/loader.py b/ranger/core/loader.py index 191dbf36..862e01b4 100644 --- a/ranger/core/loader.py +++ b/ranger/core/loader.py @@ -2,15 +2,17 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. 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 HAVE_CHARDET = True @@ -43,7 +45,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 +62,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,50 +91,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, + n = 0 + for n in shutil_g.move(src=fobj.path, dst=self.original_path, overwrite=self.overwrite): - self.percent = float(done + d) / size * 100. + 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, + 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, f.basename), + self.original_path, fobj.basename), symlinks=True, overwrite=self.overwrite): - self.percent = float(done + d) / size * 100. + 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, + n = 0 + for n in shutil_g.copy2(fobj.path, self.original_path, symlinks=True, overwrite=self.overwrite): - self.percent = float(done + d) / size * 100. + 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 @@ -142,7 +145,8 @@ class CommandLoader(Loadable, SignalDispatcher, FileManagerAware): finished = False process = None - def __init__(self, args, descr, silent=False, read=False, input=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) @@ -152,18 +156,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: @@ -173,11 +173,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: @@ -194,17 +194,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: @@ -260,10 +260,10 @@ 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') @@ -287,6 +287,7 @@ class Loader(FileManagerAware): self.throbber_status = 0 self.rotate() self.old_item = None + self.status = None def rotate(self): """Rotate the throbber""" @@ -315,19 +316,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 6d4fdaa6..48e3a6f0 100644 --- a/ranger/core/main.py +++ b/ranger/core/main.py @@ -6,13 +6,18 @@ 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 @@ -24,9 +29,9 @@ def main(): ranger.arg = arg = parse_arguments() setup_logging(debug=arg.debug, logfile=arg.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,8 +48,8 @@ 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'", arg.confdir) + LOG.debug("cache dir: '%s'", arg.cachedir) if arg.copy_config is not None: fm = FM() @@ -54,13 +59,13 @@ def main(): 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: sys.stdout.write(line[2:]) @@ -68,7 +73,7 @@ def main(): sys.stdout.write(line) return 1 if arg.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) @@ -102,14 +107,14 @@ def main(): try: # Initialize objects fm = FM(paths=targets) - FileManagerAware._setup(fm) + FileManagerAware._setup(fm) # pylint: disable=protected-access load_settings(fm, arg.clean) if arg.list_unused_keys: from ranger.ext.keybinding_parser import (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): @@ -124,7 +129,7 @@ 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.") + LOG.info("Running as root, disabling the file previews.") if not arg.debug: from ranger.ext import curses_interrupt_handler curses_interrupt_handler.install_interrupt_handler() @@ -150,7 +155,7 @@ def main(): 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: @@ -184,13 +189,13 @@ def main(): print("ranger crashed. " "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 @@ -262,18 +267,21 @@ def parse_arguments(): return arg -def load_settings(fm, clean): +COMMANDS_EXCLUDE = ['settings', 'notify'] + + +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) @@ -284,13 +292,13 @@ 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) @@ -315,9 +323,9 @@ def load_settings(fm, clean): 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 +340,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 @@ -367,7 +375,6 @@ Remove the options.py or discard stderr to get rid of this warning. def allow_access_to_confdir(confdir, allow): - import sys from errno import EEXIST if allow: @@ -381,7 +388,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 5afa2995..1d0e1cd5 100644 --- a/ranger/core/metadata.py +++ b/ranger/core/metadata.py @@ -11,14 +11,15 @@ 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 - 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): @@ -42,10 +43,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) @@ -55,7 +52,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: @@ -86,14 +82,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 @@ -120,9 +115,9 @@ class MetadataManager(object): return self.metafile_cache[metafile] else: if exists(metafile): - with open(metafile, "r") as f: + with open(metafile, "r") as fobj: try: - entries = json.load(f) + entries = json.load(fobj) except ValueError: raise ValueError("Failed decoding JSON file %s" % metafile) diff --git a/ranger/core/runner.py b/ranger/core/runner.py index d00ebc80..1a9c8193 100644 --- a/ranger/core/runner.py +++ b/ranger/core/runner.py @@ -30,7 +30,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' @@ -63,6 +63,8 @@ class Context(object): """ def __init__(self, **keywords): + self.flags = None + self.wait = False self.__dict__ = keywords @property @@ -85,7 +87,7 @@ 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 @@ -113,9 +115,12 @@ class Runner(object): except Exception: self._log("Failed to suspend UI") - def __call__(self, action=None, try_app_first=False, - app='default', files=None, mode=0, - flags='', wait=True, **popen_kws): + 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. Returns False if nothing can be done, None if there was an error, @@ -220,9 +225,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() @@ -238,6 +243,6 @@ class Runner(object): 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..764c2384 100644 --- a/ranger/core/shared.py +++ b/ranger/core/shared.py @@ -3,17 +3,17 @@ """Shared objects contain singletons for shared use.""" -from ranger.ext.lazy_property import lazy_property +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 2a3bfeaf..56348111 100644 --- a/ranger/core/tab.py +++ b/ranger/core/tab.py @@ -2,16 +2,15 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. 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 @@ -66,7 +65,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: @@ -83,7 +82,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): @@ -143,8 +142,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 9cc2f4e3..dcbfb4d9 100644 --- a/ranger/ext/accumulator.py +++ b/ranger/ext/accumulator.py @@ -89,10 +89,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..3c26d4f9 100644 --- a/ranger/ext/cached_function.py +++ b/ranger/ext/cached_function.py @@ -12,5 +12,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..d220d99c 100644 --- a/ranger/ext/curses_interrupt_handler.py +++ b/ranger/ext/curses_interrupt_handler.py @@ -11,12 +11,12 @@ a Ctrl+C (ASCII value 3) to the curses getch stack. 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 +24,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 b5fea6aa..3de7e1c3 100644 --- a/ranger/ext/direction.py +++ b/ranger/ext/direction.py @@ -51,7 +51,7 @@ class Direction(dict): except Exception: return fallback - def up(self): + def up(self): # pylint: disable=invalid-name return -Direction.down(self) def down(self): @@ -104,8 +104,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: @@ -147,6 +147,7 @@ class Direction(dict): 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..385ec45d 100644 --- a/ranger/ext/get_executables.py +++ b/ranger/ext/get_executables.py @@ -2,17 +2,18 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. 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 50b95b99..e4b04525 100644 --- a/ranger/ext/human_readable.py +++ b/ranger/ext/human_readable.py @@ -2,7 +2,7 @@ # License: GNU GPL version 3, see the file "AUTHORS" for details. -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) @@ -42,6 +42,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 5b2632da..c40c084a 100644 --- a/ranger/ext/img_display.py +++ b/ranger/ext/img_display.py @@ -15,12 +15,13 @@ 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 = [] @@ -70,7 +71,8 @@ class W3MImageDisplayer(ImageDisplayer): 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): @@ -84,10 +86,10 @@ class W3MImageDisplayer(ImageDisplayer): # 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) @@ -124,11 +126,11 @@ class W3MImageDisplayer(ImageDisplayer): 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 ex self.process.stdin.flush() self.process.stdout.readline() @@ -236,19 +238,20 @@ 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) + return width * max_scale else: - return (width * min_scale) + return width * min_scale else: scale = max_height / float(height) - return (width * scale) + return width * scale elif width > max_width: scale = max_width / float(width) - return (width * scale) + return width * scale else: 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') try: @@ -258,7 +261,8 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware): finally: file.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) @@ -306,17 +310,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 @@ -328,18 +334,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 @@ -347,10 +353,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..a18276b9 100644 --- a/ranger/ext/iter_tools.py +++ b/ranger/ext/iter_tools.py @@ -42,6 +42,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 441949f4..ec9d8034 100644 --- a/ranger/ext/keybinding_parser.py +++ b/ranger/ext/keybinding_parser.py @@ -6,12 +6,12 @@ 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 +38,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 'abcdefghijklmnopqrstuvwxyz_': - special_keys['c-' + char] = ord(char) - 96 + for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!{}': + special_keys['a-' + char] = (ALT_KEY, ord(char)) -special_keys['c-space'] = 0 + for char in 'abcdefghijklmnopqrstuvwxyz_': + special_keys['c-' + char] = ord(char) - 96 -for n in range(64): - special_keys['f' + str(n)] = curses.KEY_F0 + n + special_keys['c-space'] = 0 + + 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 +92,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 +109,8 @@ def parse_keybinding(obj): yield int(string) else: yield ord('<') - for c in bracket_content: - yield ord(c) + for char in bracket_content: + yield ord(char) yield ord('>') except TypeError: yield keys # it was no tuple, just an int @@ -118,8 +124,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): @@ -206,7 +212,7 @@ class KeyMaps(dict): _unbind_traverse(pointer, keys) -class KeyBuffer(object): +class KeyBuffer(object): # pylint: disable=too-many-instance-attributes any_key = ANYKEY passive_key = PASSIVE_ACTION quantifier_key = QUANT_KEY @@ -214,9 +220,6 @@ class KeyBuffer(object): def __init__(self, keymap=None): self.keymap = keymap - self.clear() - - def clear(self): self.keys = [] self.wildcards = [] self.pointer = self.keymap @@ -230,6 +233,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 @@ -264,6 +270,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..43dc65af 100644 --- a/ranger/ext/lazy_property.py +++ b/ranger/ext/lazy_property.py @@ -1,7 +1,7 @@ # From http://blog.pythonisito.com/2008/08/lazy-descriptors.html -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 +29,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..c8d887d0 100644 --- a/ranger/ext/logutils.py +++ b/ranger/ext/logutils.py @@ -28,9 +28,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): diff --git a/ranger/ext/popen_forked.py b/ranger/ext/popen_forked.py index d4a75a48..82997f0c 100644 --- a/ranger/ext/popen_forked.py +++ b/ranger/ext/popen_forked.py @@ -5,7 +5,7 @@ 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 +19,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/rifle.py b/ranger/ext/rifle.py index 2577f468..842f289e 100755 --- a/ranger/ext/rifle.py +++ b/ranger/ext/rifle.py @@ -32,11 +32,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 +70,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 +81,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 +111,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 +122,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,6 +143,9 @@ 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 = [ @@ -152,10 +159,10 @@ class Rifle(object): """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 +175,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: + except Exception as ex: self.hook_logger("Syntax error in %s line %d (%s)" % - (config_file, lineno, str(e))) - f.close() + (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 +191,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,7 +257,7 @@ 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], @@ -292,7 +300,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 +336,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) @@ -370,15 +379,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']: @@ -399,7 +407,7 @@ 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. " @@ -430,8 +438,8 @@ 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) @@ -445,7 +453,9 @@ def main(): 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 97401cd6..5cea1ec5 100644 --- a/ranger/ext/shell_escape.py +++ b/ranger/ext/shell_escape.py @@ -21,6 +21,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 8c5fc805..f00e992d 100644 --- a/ranger/ext/shutil_generatorized.py +++ b/ranger/ext/shutil_generatorized.py @@ -6,9 +6,9 @@ XXX The functions here don't copy the resource fork or other metadata on Mac. """ 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,10 +25,11 @@ 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): @@ -63,16 +64,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 +88,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 +100,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 +144,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 +198,18 @@ 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, + n = 0 + for n in copytree(srcname, dstname, symlinks, ignore, overwrite): - yield done + d - done += d + yield done + n + done += n else: # Will raise a SpecialFileError for unsupported file types - d = 0 - for d in copy2(srcname, dstname, + n = 0 + for n in copy2(srcname, dstname, overwrite=overwrite, symlinks=symlinks): - yield done + d - done += d + 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 e35eab57..3800a6ea 100644 --- a/ranger/ext/signals.py +++ b/ranger/ext/signals.py @@ -82,7 +82,7 @@ class Signal(dict): self.stopped = True -class SignalHandler: +class SignalHandler: # 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() @@ -110,7 +110,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): @@ -149,7 +149,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): @@ -160,9 +161,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 @@ -173,12 +176,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 @@ -220,14 +224,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. @@ -251,6 +257,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) @@ -263,6 +270,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/widestring.py b/ranger/ext/widestring.py index 9d7b9d03..603439cb 100644 --- a/ranger/ext/widestring.py +++ b/ranger/ext/widestring.py @@ -32,9 +32,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 +45,14 @@ 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: @@ -101,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'> @@ -124,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 24e378e2..184c04fb 100644 --- a/ranger/gui/ansi.py +++ b/ranger/gui/ansi.py @@ -4,12 +4,16 @@ """A library to help to convert ANSI codes to curses instructions.""" -from ranger.gui import color 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 +23,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': @@ -164,6 +168,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 bac8adb5..91fdff24 100644 --- a/ranger/gui/bar.py +++ b/ranger/gui/bar.py @@ -1,8 +1,11 @@ # 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 import sys + +from ranger.ext.widestring import WideString, utf_char_width + + PY3 = sys.version_info[0] >= 3 @@ -88,13 +91,13 @@ 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') diff --git a/ranger/gui/color.py b/ranger/gui/color.py index 62870ace..786299a3 100644 --- a/ranger/gui/color.py +++ b/ranger/gui/color.py @@ -46,6 +46,8 @@ def get_color(fg, bg): return COLOR_PAIRS[key] + +# pylint: disable=invalid-name black = curses.COLOR_BLACK blue = curses.COLOR_BLUE cyan = curses.COLOR_CYAN @@ -64,3 +66,4 @@ underline = curses.A_UNDERLINE invisible = curses.A_INVIS default_colors = (default, default, normal) +# pylint: enable=invalid-name diff --git a/ranger/gui/colorscheme.py b/ranger/gui/colorscheme.py index 02333828..363d4da3 100644 --- a/ranger/gui/colorscheme.py +++ b/ranger/gui/colorscheme.py @@ -66,7 +66,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 +75,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 @@ -91,9 +92,9 @@ def _colorscheme_name_to_class(signal): 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 @@ -117,7 +118,7 @@ def _colorscheme_name_to_class(signal): if signal.previous and isinstance(signal.previous, ColorScheme): signal.value = signal.previous else: - signal.value = ColorScheme() + signal.value = ColorScheme() # pylint: disable=redefined-variable-type raise Exception("Cannot locate colorscheme `%s'" % scheme_name) else: if usecustom: diff --git a/ranger/gui/context.py b/ranger/gui/context.py index 13598b16..be118655 100644 --- a/ranger/gui/context.py +++ b/ranger/gui/context.py @@ -23,14 +23,19 @@ CONTEXT_KEYS = ['reset', 'error', 'badinfo', 'vcsstaged', 'vcssync', 'vcsnone', 'vcsbehind', 'vcsahead', 'vcsdiverged'] -class Context(object): +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 -# set all keys to False -for key in CONTEXT_KEYS: - setattr(Context, key, False) + +def _context_init(): + # 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..3213d097 100644 --- a/ranger/gui/curses_shortcuts.py +++ b/ranger/gui/curses_shortcuts.py @@ -1,9 +1,9 @@ # This file is part of ranger, the console file manager. # License: GNU GPL version 3, see the file "AUTHORS" for details. +import sys import curses import _curses -import sys from ranger.gui.color import get_color from ranger.core.shared import SettingsAware diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py index 4c5133d4..1eef8f41 100644 --- a/ranger/gui/displayable.py +++ b/ranger/gui/displayable.py @@ -5,7 +5,8 @@ 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 @@ -163,7 +164,7 @@ class Displayable(FileManagerAware, CursesShortcuts): 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 +178,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 +186,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 +312,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/ui.py b/ranger/gui/ui.py index cb8e5718..90dee4c9 100644 --- a/ranger/gui/ui.py +++ b/ranger/gui/ui.py @@ -3,16 +3,18 @@ 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 +40,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,7 +49,7 @@ 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() @@ -59,8 +62,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') @@ -198,7 +201,7 @@ class UI(DisplayableContainer): 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 +234,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 @@ -455,7 +457,8 @@ class UI(DisplayableContainer): 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/browsercolumn.py b/ranger/gui/widgets/browsercolumn.py index 3fde7f03..28216dd5 100644 --- a/ranger/gui/widgets/browsercolumn.py +++ b/ranger/gui/widgets/browsercolumn.py @@ -3,21 +3,18 @@ """The BrowserColumn widget displays the contents of a directory or file.""" -import curses 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 @@ -39,7 +36,7 @@ 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 @@ -182,14 +179,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 +198,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 +271,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) @@ -390,7 +389,8 @@ class BrowserColumn(Pager): else: return self.target.pointer - def _total_len(self, predisplay): + @staticmethod + def _total_len(predisplay): return sum([len(WideString(s)) for s, L in predisplay]) def _draw_text_display(self, text, space): @@ -479,7 +479,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) diff --git a/ranger/gui/widgets/console.py b/ranger/gui/widgets/console.py index 85fb92a3..ecd98d9b 100644 --- a/ranger/gui/widgets/console.py +++ b/ranger/gui/widgets/console.py @@ -14,7 +14,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 @@ -38,13 +38,13 @@ class Console(Widget): if not ranger.arg.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() + fobj.close() self.line = "" self.history_backup = History(self.history) @@ -67,16 +67,16 @@ class Console(Widget): 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 +178,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) @@ -275,17 +275,17 @@ class Console(Widget): 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, + maximum=len(uchar) + 1, current=upos) - self.pos = len(''.join(uc[:newupos]).encode('utf-8', 'ignore')) + self.pos = len(''.join(uchar[:newupos]).encode('utf-8', 'ignore')) def move_word(self, **keywords): direction = Direction(keywords) @@ -409,11 +409,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): @@ -494,7 +494,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 +507,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 979c6d62..fabf6113 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -3,16 +3,16 @@ """The pager displays text and allows you to scroll inside it.""" -from . import Widget -from ranger.core.loader import CommandLoader 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 @@ -97,8 +97,8 @@ class Pager(Widget): 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 @@ -190,7 +190,7 @@ 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 @@ -209,10 +209,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): diff --git a/ranger/gui/widgets/statusbar.py b/ranger/gui/widgets/statusbar.py index 87cd7c53..ead79e5d 100644 --- a/ranger/gui/widgets/statusbar.py +++ b/ranger/gui/widgets/statusbar.py @@ -9,17 +9,18 @@ such as the space used by all the files in this directory. """ 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 = {} @@ -133,7 +134,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\ @@ -230,7 +231,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 +262,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: @@ -319,7 +323,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 f72de94a..803b4ae4 100644 --- a/ranger/gui/widgets/taskview.py +++ b/ranger/gui/widgets/taskview.py @@ -3,9 +3,10 @@ """The TaskView allows you to modify what the loader is doing.""" -from . import Widget from ranger.ext.accumulator import Accumulator +from . import Widget + class TaskView(Widget, Accumulator): old_lst = None @@ -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 283da3a8..3f96e094 100644 --- a/ranger/gui/widgets/titlebar.py +++ b/ranger/gui/widgets/titlebar.py @@ -8,9 +8,9 @@ It displays the current path among other things. from os.path import basename +from ranger.gui.bar import Bar from . import Widget -from ranger.gui.bar import Bar class TitleBar(Widget): @@ -56,7 +56,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 +123,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 bc9a8260..d6652ef5 100644 --- a/ranger/gui/widgets/view_base.py +++ b/ranger/gui/widgets/view_base.py @@ -4,19 +4,18 @@ """The base GUI element for views on the directory""" 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) @@ -51,10 +50,10 @@ class ViewBase(Widget, DisplayableContainer): pass else: try: - x = self.main_column.x - y = self.main_column.y + self.main_column.target.pointer\ + 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(y, x) + self.fm.ui.win.move(col_y, col_x) except Exception: pass @@ -101,15 +100,15 @@ 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)) @@ -154,7 +153,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 b4db5b08..1496e18c 100644 --- a/ranger/gui/widgets/view_miller.py +++ b/ranger/gui/widgets/view_miller.py @@ -6,14 +6,14 @@ 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 @@ -68,9 +68,9 @@ class ViewMiller(ViewBase): 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] @@ -130,10 +130,12 @@ class ViewMiller(ViewBase): # 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.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) diff --git a/ranger/gui/widgets/view_multipane.py b/ranger/gui/widgets/view_multipane.py index abeb31fe..f99b133c 100644 --- a/ranger/gui/widgets/view_multipane.py +++ b/ranger/gui/widgets/view_multipane.py @@ -5,7 +5,7 @@ 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) @@ -41,12 +41,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 diff --git a/setup.py b/setup.py index ce0631f0..26bb58e2 100755 --- a/setup.py +++ b/setup.py @@ -4,6 +4,7 @@ import distutils.core import os.path + import ranger @@ -11,6 +12,7 @@ def _findall(directory): return [os.path.join(directory, f) for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))] + if __name__ == '__main__': distutils.core.setup( name='ranger', @@ -24,17 +26,17 @@ if __name__ == '__main__': scripts=['scripts/ranger', 'scripts/rifle'], data_files=[ ('share/applications', - ['doc/ranger.desktop']), + ['doc/ranger.desktop']), ('share/man/man1', - ['doc/ranger.1', - 'doc/rifle.1']), + ['doc/ranger.1', + 'doc/rifle.1']), ('share/doc/ranger', - ['README.md', - 'CHANGELOG.md', - 'HACKING.md', - 'doc/colorschemes.txt']), + ['README.md', + 'CHANGELOG.md', + 'HACKING.md', + 'doc/colorschemes.txt']), ('share/doc/ranger/config/colorschemes', - _findall('doc/config/colorschemes')), + _findall('doc/config/colorschemes')), ('share/doc/ranger/config', _findall('doc/config')), ('share/doc/ranger/tools', _findall('doc/tools')), ('share/doc/ranger/examples', _findall('examples')), diff --git a/tests/ranger/container/test_bookmarks.py b/tests/ranger/container/test_bookmarks.py index a2cd446f..8107a71a 100644 --- a/tests/ranger/container/test_bookmarks.py +++ b/tests/ranger/container/test_bookmarks.py @@ -1,5 +1,6 @@ import os import time + import pytest from ranger.container.bookmarks import Bookmarks diff --git a/tests/ranger/container/test_container.py b/tests/ranger/container/test_container.py index 2b823912..7144236f 100644 --- a/tests/ranger/container/test_container.py +++ b/tests/ranger/container/test_container.py @@ -10,92 +10,92 @@ def testhistorybasic(): # item added to it. It has a `current` index that serves as a cursor. # A history has a limited size, check that only `maxlen` items are stored - h = history.History(maxlen=10) + hist = history.History(maxlen=10) for entry in HISTORY_TEST_ENTRIES: - h.add(entry) + hist.add(entry) # 10 items are stored - assert len(h) == 10 - assert h.current() == "19" - assert h.top() == "19" - assert h.bottom() == "10" + assert len(hist) == 10 + assert hist.current() == "19" + assert hist.top() == "19" + assert hist.bottom() == "10" # going back in time affects only changes current item - h.back() - assert len(h) == 10 - assert h.current() == "18" - assert h.top() == "19" - assert h.bottom() == "10" + hist.back() + assert len(hist) == 10 + assert hist.current() == "18" + assert hist.top() == "19" + assert hist.bottom() == "10" # __iter__ is actually an interator and we can iterate through the list - it = iter(h) - assert iter(it) == it - assert list(it) == HISTORY_TEST_ENTRIES[10:] + iterator = iter(hist) + assert iter(iterator) == iterator + assert list(iterator) == HISTORY_TEST_ENTRIES[10:] # search allows to go back in time as long as a pattern matches and we don't # go over a step limit - assert h.search("45", -9) == "18" - assert h.search("1", -5) == "13" + assert hist.search("45", -9) == "18" + assert hist.search("1", -5) == "13" # fast forward selects the last item - h.fast_forward() - assert h.current() == "19" + hist.fast_forward() + assert hist.current() == "19" # back followed by forward is a noop - h.back() - h.forward() - assert h.current() == "19" + hist.back() + hist.forward() + assert hist.current() == "19" # move can be expressed as multiple calls to back and forward - h.move(-3) - h.forward() - h.forward() - h.forward() - assert h.current() == "19" + hist.move(-3) + hist.forward() + hist.forward() + hist.forward() + assert hist.current() == "19" # back, forward, move play well with boundaries for _ in range(30): - h.back() + hist.back() for _ in range(30): - h.forward() + hist.forward() for _ in range(30): - h.move(-2) + hist.move(-2) for _ in range(30): - h.move(2) - assert h.current() == "19" + hist.move(2) + assert hist.current() == "19" # we can create an history from another history - h = history.History(maxlen=10) + hist = history.History(maxlen=10) for entry in HISTORY_TEST_ENTRIES: - h.add(entry) + hist.add(entry) # XXX maxlen should not be used to refer to something that isn't a length - otherh = history.History(maxlen=h) - assert(list(h) == list(otherh)) + otherh = history.History(maxlen=hist) + assert list(hist) == list(otherh) # Rebase replaces the past of the history with that of another - otherh = history.History(maxlen=h) - old_current_item = h.current() + otherh = history.History(maxlen=hist) + old_current_item = hist.current() for entry in OTHER_TEST_ENTRIES: otherh.add(entry) assert list(otherh)[-3:] == ["42", "43", "44"] - h.rebase(otherh) - assert h.current() == old_current_item - assert list(h)[-3:] == ['43', '44', old_current_item] + hist.rebase(otherh) + assert hist.current() == old_current_item + assert list(hist)[-3:] == ['43', '44', old_current_item] # modify, modifies the top of the stack - h.modify("23") - assert h.current() == "23" + hist.modify("23") + assert hist.current() == "23" def testhistoryunique(): # Check that unique history refuses to store duplicated entries - h = history.History(maxlen=10, unique=True) + hist = history.History(maxlen=10, unique=True) for entry in HISTORY_TEST_ENTRIES: - h.add(entry) - assert h.current() == "19" - h.add("17") - assert list(h).count("17") == 1 - assert h.current() == "17" + hist.add(entry) + assert hist.current() == "19" + hist.add("17") + assert list(hist).count("17") == 1 + assert hist.current() == "17" diff --git a/tests/ranger/container/test_fsobject.py b/tests/ranger/container/test_fsobject.py index 73d2024a..49c15666 100644 --- a/tests/ranger/container/test_fsobject.py +++ b/tests/ranger/container/test_fsobject.py @@ -1,10 +1,9 @@ -import pytest import operator from ranger.container.fsobject import FileSystemObject -class MockFM(object): +class MockFM(object): # pylint: disable=too-few-public-methods """Used to fulfill the dependency by FileSystemObject.""" default_linemodes = [] @@ -22,13 +21,13 @@ def test_basename_natural1(): """Test filenames without extensions.""" fsos = [create_filesystem_object(path) for path in ("hello", "hello1", "hello2")] - assert(fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural"))) - assert(fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural_lower"))) + assert fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural")) + assert fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural_lower")) def test_basename_natural2(): """Test filenames with extensions.""" fsos = [create_filesystem_object(path) for path in ("hello", "hello.txt", "hello1.txt", "hello2.txt")] - assert(fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural"))) - assert(fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural_lower"))) + assert fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural")) + assert fsos == sorted(fsos[::-1], key=operator.attrgetter("basename_natural_lower")) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..61d90815 --- /dev/null +++ b/tox.ini @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 99 |