about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/pylint.yml4
-rw-r--r--.github/workflows/python.yml2
-rw-r--r--.pylintrc2
-rw-r--r--Makefile4
-rw-r--r--README.md6
-rw-r--r--doc/ranger.123
-rw-r--r--doc/ranger.pod23
-rw-r--r--ranger/config/.pylintrc2
-rw-r--r--ranger/container/file.py2
-rw-r--r--ranger/core/loader.py2
-rwxr-xr-xranger/data/scope.sh38
-rw-r--r--ranger/ext/img_display.py2
-rw-r--r--ranger/ext/vcs/vcs.py6
-rw-r--r--tests/pylint/py2_compat.py8
-rw-r--r--tests/pylint/test_py2_compat.py39
15 files changed, 115 insertions, 48 deletions
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index eac9aa5c..c15f8f64 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -27,6 +27,6 @@ jobs:
       run: |
         python -m pip install --upgrade pip
         pip install -r requirements.txt
-    - name: Lint with pylint
+    - name: Lint with pylint, test with pytest
       run: |
-        make test_pylint
+        make test_pylint test_pytest
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index b96d6812..dc41318e 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -31,4 +31,4 @@ jobs:
         pip install -r requirements.txt
     - name: Flake8 and test
       run: |
-        make test_flake8 test_pytest test_doctest test_other
+        make test_flake8 test_doctest test_other
diff --git a/.pylintrc b/.pylintrc
index 994ddf62..9b4ad466 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -13,7 +13,7 @@ max-branches=16
 [FORMAT]
 max-line-length = 99
 enable=no-absolute-import,old-division
-disable=cyclic-import,duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-break,no-else-continue,no-else-raise,no-else-return,raise-missing-from,redefined-variable-type,stop-iteration-return,super-with-arguments,useless-object-inheritance
+disable=consider-using-f-string,cyclic-import,duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-break,no-else-continue,no-else-raise,no-else-return,raise-missing-from,redefined-variable-type,stop-iteration-return,super-with-arguments,useless-object-inheritance
 
 [TYPECHECK]
 ignored-classes=ranger.core.actions.Actions
diff --git a/Makefile b/Makefile
index 3479bb2d..568ebd42 100644
--- a/Makefile
+++ b/Makefile
@@ -135,13 +135,13 @@ test_other:
 test: test_py test_shellcheck
 	@echo "$(bold)Finished testing: All tests passed!$(normal)"
 
-doc/ranger.1: doc/ranger.pod README.md
+doc/ranger.1: doc/ranger.pod
 	pod2man --stderr --center='ranger manual' \
 		--date='$(NAME)-$(VERSION)' \
 		--release=$(shell date -u '+%Y-%m-%d') \
 		doc/ranger.pod doc/ranger.1
 
-doc/rifle.1: doc/rifle.pod README.md
+doc/rifle.1: doc/rifle.pod
 	pod2man --stderr --center='rifle manual' \
 		--date='$(NAME_RIFLE)-$(VERSION_RIFLE)' \
 		--release=$(shell date -u '+%Y-%m-%d') \
diff --git a/README.md b/README.md
index bc811d12..6598b655 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,9 @@ For enhanced file previews (with `scope.sh`):
 
 * `img2txt` (from `caca-utils`) for ASCII-art image previews
 * `w3mimgdisplay`, `ueberzug`, `mpv`, `iTerm2`, `kitty`, `terminology` or `urxvt` for image previews
-* `convert` (from `imagemagick`) to auto-rotate images and for SVG previews
+* `convert` (from `imagemagick`) to auto-rotate images
+* `rsvg-convert` (from [`librsvg`](https://wiki.gnome.org/Projects/LibRsvg))
+  for SVG previews
 * `ffmpeg`, or `ffmpegthumbnailer` for video thumbnails
 * `highlight`, `bat` or `pygmentize` for syntax highlighting of code
 * `atool`, `bsdtar`, `unrar` and/or `7z` to preview archives
@@ -107,6 +109,8 @@ For enhanced file previews (with `scope.sh`):
 * `jupyter nbconvert` for Jupyter Notebooks
 * `fontimage` for font previews
 * `openscad` for 3D model previews (`stl`, `off`, `dxf`, `scad`, `csg`)
+* `draw.io` for [draw.io](https://app.diagrams.net/) diagram previews
+  (`drawio` extension)
 
 Installing
 ----------
diff --git a/doc/ranger.1 b/doc/ranger.1
index a3d13994..cc166604 100644
--- a/doc/ranger.1
+++ b/doc/ranger.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40)
+.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.42)
 .\"
 .\" Standard preamble:
 .\" ========================================================================
@@ -133,7 +133,7 @@
 .\" ========================================================================
 .\"
 .IX Title "RANGER 1"
-.TH RANGER 1 "ranger-1.9.3" "2021-11-13" "ranger manual"
+.TH RANGER 1 "ranger-1.9.3" "2022-03-18" "ranger manual"
 .\" For nroff, turn off justification.  Always turn off hyphenation; it makes
 .\" way too many mistakes in technical documents.
 .if n .ad l
@@ -308,7 +308,9 @@ are automatically used when available but completely optional.
 \&\f(CW\*(C`w3mimgdisplay\*(C'\fR, \f(CW\*(C`ueberzug\*(C'\fR, \f(CW\*(C`mpv\*(C'\fR, \f(CW\*(C`iTerm2\*(C'\fR, \f(CW\*(C`kitty\*(C'\fR, \f(CW\*(C`terminology\*(C'\fR or
 \&\f(CW\*(C`urxvt\*(C'\fR for image previews
 .IP "\-" 2
-\&\f(CW\*(C`convert\*(C'\fR (from \f(CW\*(C`imagemagick\*(C'\fR) to auto-rotate images and for \s-1SVG\s0 previews
+\&\f(CW\*(C`convert\*(C'\fR (from \f(CW\*(C`imagemagick\*(C'\fR) to auto-rotate images
+.IP "\-" 2
+\&\f(CW\*(C`rsvg\-convert\*(C'\fR (from \f(CW\*(C`librsvg\*(C'\fR) for \s-1SVG\s0 previews
 .IP "\-" 2
 \&\f(CW\*(C`ffmpegthumbnailer\*(C'\fR for video thumbnails
 .IP "\-" 2
@@ -316,11 +318,13 @@ are automatically used when available but completely optional.
 .IP "\-" 2
 \&\f(CW\*(C`atool\*(C'\fR, \f(CW\*(C`bsdtar\*(C'\fR, \f(CW\*(C`unrar\*(C'\fR and/or \f(CW\*(C`7z\*(C'\fR to preview archives
 .IP "\-" 2
-\&\f(CW\*(C`bsdtar\*(C'\fR, \f(CW\*(C`tar\*(C'\fR, \f(CW\*(C`unrar\*(C'\fR, \f(CW\*(C`unzip\*(C'\fR and/or \f(CW\*(C`zipinfo\*(C'\fR (and \f(CW\*(C`sed\*(C'\fR) to preview archives as their first image
+\&\f(CW\*(C`bsdtar\*(C'\fR, \f(CW\*(C`tar\*(C'\fR, \f(CW\*(C`unrar\*(C'\fR, \f(CW\*(C`unzip\*(C'\fR and/or \f(CW\*(C`zipinfo\*(C'\fR (and \f(CW\*(C`sed\*(C'\fR) to preview
+archives as their first image
 .IP "\-" 2
 \&\f(CW\*(C`lynx\*(C'\fR, \f(CW\*(C`w3m\*(C'\fR or \f(CW\*(C`elinks\*(C'\fR to preview html pages
 .IP "\-" 2
-\&\f(CW\*(C`pdftotext\*(C'\fR or \f(CW\*(C`mutool\*(C'\fR (and \f(CW\*(C`fmt\*(C'\fR) for textual pdf previews, \f(CW\*(C`pdftoppm\*(C'\fR to preview as image
+\&\f(CW\*(C`pdftotext\*(C'\fR or \f(CW\*(C`mutool\*(C'\fR (and \f(CW\*(C`fmt\*(C'\fR) for textual pdf previews, \f(CW\*(C`pdftoppm\*(C'\fR to
+preview as image
 .IP "\-" 2
 \&\f(CW\*(C`djvutxt\*(C'\fR for textual DjVu previews, \f(CW\*(C`ddjvu\*(C'\fR to preview as image
 .IP "\-" 2
@@ -335,6 +339,11 @@ are automatically used when available but completely optional.
 \&\f(CW\*(C`python\*(C'\fR or \f(CW\*(C`jq\*(C'\fR for \s-1JSON\s0 files
 .IP "\-" 2
 \&\f(CW\*(C`fontimage\*(C'\fR for font previews
+.IP "\-" 2
+\&\f(CW\*(C`openscad\*(C'\fR for 3D model previews (stl, off, dxf, scad, csg)
+.IP "\-" 2
+\&\f(CW\*(C`draw.io\*(C'\fR for draw.io <https://app.diagrams.net/> diagram previews (drawio
+extension)
 .RE
 .RS 2
 .RE
@@ -1445,8 +1454,8 @@ following \f(CW\*(C`FILTER_TYPE\*(C'\fRs are available:
 Filter files so only files that have duplicates in the same directory are
 shown. Useful when cleaning up identical songs and memes that were saved using
 distinct file names.
-.IP "filename \s-1NAME\s0" 2
-.IX Item "filename NAME"
+.IP "name \s-1NAME\s0" 2
+.IX Item "name NAME"
 Filter files that contain \s-1NAME\s0 in the filename, regular expression syntax is
 allowed.
 .IP "hash \s-1PATH\s0" 2
diff --git a/doc/ranger.pod b/doc/ranger.pod
index 2484581c..f3413856 100644
--- a/doc/ranger.pod
+++ b/doc/ranger.pod
@@ -234,7 +234,11 @@ C<urxvt> for image previews
 
 =item -
 
-C<convert> (from C<imagemagick>) to auto-rotate images and for SVG previews
+C<convert> (from C<imagemagick>) to auto-rotate images
+
+=item -
+
+C<rsvg-convert> (from C<librsvg>) for SVG previews
 
 =item -
 
@@ -250,7 +254,8 @@ C<atool>, C<bsdtar>, C<unrar> and/or C<7z> to preview archives
 
 =item -
 
-C<bsdtar>, C<tar>, C<unrar>, C<unzip> and/or C<zipinfo> (and C<sed>) to preview archives as their first image
+C<bsdtar>, C<tar>, C<unrar>, C<unzip> and/or C<zipinfo> (and C<sed>) to preview
+archives as their first image
 
 =item -
 
@@ -258,7 +263,8 @@ C<lynx>, C<w3m> or C<elinks> to preview html pages
 
 =item -
 
-C<pdftotext> or C<mutool> (and C<fmt>) for textual pdf previews, C<pdftoppm> to preview as image
+C<pdftotext> or C<mutool> (and C<fmt>) for textual pdf previews, C<pdftoppm> to
+preview as image
 
 =item -
 
@@ -288,6 +294,15 @@ C<python> or C<jq> for JSON files
 
 C<fontimage> for font previews
 
+=item -
+
+C<openscad> for 3D model previews (stl, off, dxf, scad, csg)
+
+=item -
+
+C<draw.io> for draw.io L<https://app.diagrams.net/> diagram previews (drawio
+extension)
+
 =back
 
 =back
@@ -1560,7 +1575,7 @@ Filter files so only files that have duplicates in the same directory are
 shown. Useful when cleaning up identical songs and memes that were saved using
 distinct file names.
 
-=item filename NAME
+=item name NAME
 
 Filter files that contain NAME in the filename, regular expression syntax is
 allowed.
diff --git a/ranger/config/.pylintrc b/ranger/config/.pylintrc
index 316bf189..509c6f31 100644
--- a/ranger/config/.pylintrc
+++ b/ranger/config/.pylintrc
@@ -5,4 +5,4 @@ class-rgx=[a-z][a-z0-9_]{1,30}$
 [FORMAT]
 max-line-length = 99
 max-module-lines=3000
-disable=duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-return,super-with-arguments
+disable=consider-using-f-string,duplicate-code,fixme,import-outside-toplevel,locally-disabled,locally-enabled,missing-docstring,no-else-return,super-with-arguments
diff --git a/ranger/container/file.py b/ranger/container/file.py
index 9477abe7..4cc29887 100644
--- a/ranger/container/file.py
+++ b/ranger/container/file.py
@@ -86,7 +86,7 @@ class File(FileSystemObject):
             return True
         if PREVIEW_BLACKLIST.search(self.basename):
             return False
-        if self.path == '/dev/core' or self.path == '/proc/kcore':
+        if self.path in ('/dev/core', '/proc/kcore'):
             return False
         if self.is_binary():
             return False
diff --git a/ranger/core/loader.py b/ranger/core/loader.py
index 5c9e28a5..19611c7b 100644
--- a/ranger/core/loader.py
+++ b/ranger/core/loader.py
@@ -192,7 +192,7 @@ class CommandLoader(  # pylint: disable=too-many-instance-attributes
             try:
                 stdin.write(self.input)
             except IOError as ex:
-                if ex.errno != errno.EPIPE and ex.errno != errno.EINVAL:
+                if ex.errno not in (errno.EPIPE, errno.EINVAL):
                     raise
             stdin.close()
         if self.silent and not self.read:  # pylint: disable=too-many-nested-blocks
diff --git a/ranger/data/scope.sh b/ranger/data/scope.sh
index 36c462c6..2e9983ee 100755
--- a/ranger/data/scope.sh
+++ b/ranger/data/scope.sh
@@ -140,9 +140,11 @@ handle_image() {
     local mimetype="${1}"
     case "${mimetype}" in
         ## SVG
-        # image/svg+xml|image/svg)
-        #     convert -- "${FILE_PATH}" "${IMAGE_CACHE_PATH}" && exit 6
-        #     exit 1;;
+        image/svg+xml|image/svg)
+            rsvg-convert --keep-aspect-ratio --width "${DEFAULT_SIZE%x*}" "${FILE_PATH}" -o "${IMAGE_CACHE_PATH}.png" \
+                && mv "${IMAGE_CACHE_PATH}.png" "${IMAGE_CACHE_PATH}" \
+                && exit 6
+            exit 1;;
 
         ## DjVu
         # image/vnd.djvu)
@@ -268,19 +270,23 @@ handle_image() {
     #     mv "${TMPPNG}" "${IMAGE_CACHE_PATH}"
     # }
 
-    # case "${FILE_EXTENSION_LOWER}" in
-    #     ## 3D models
-    #     ## OpenSCAD only supports png image output, and ${IMAGE_CACHE_PATH}
-    #     ## is hardcoded as jpeg. So we make a tempfile.png and just
-    #     ## move/rename it to jpg. This works because image libraries are
-    #     ## smart enough to handle it.
-    #     csg|scad)
-    #         openscad_image "${FILE_PATH}" && exit 6
-    #         ;;
-    #     3mf|amf|dxf|off|stl)
-    #         openscad_image <(echo "import(\"${FILE_PATH}\");") && exit 6
-    #         ;;
-    # esac
+    case "${FILE_EXTENSION_LOWER}" in
+       ## 3D models
+       ## OpenSCAD only supports png image output, and ${IMAGE_CACHE_PATH}
+       ## is hardcoded as jpeg. So we make a tempfile.png and just
+       ## move/rename it to jpg. This works because image libraries are
+       ## smart enough to handle it.
+       # csg|scad)
+       #     openscad_image "${FILE_PATH}" && exit 6
+       #     ;;
+       # 3mf|amf|dxf|off|stl)
+       #     openscad_image <(echo "import(\"${FILE_PATH}\");") && exit 6
+       #     ;;
+       drawio)
+           draw.io -x "${FILE_PATH}" -o "${IMAGE_CACHE_PATH}" \
+               --width "${DEFAULT_SIZE%x*}" && exit 6
+           exit 1;;
+    esac
 }
 
 handle_mime() {
diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py
index 43e4203d..61e10f96 100644
--- a/ranger/ext/img_display.py
+++ b/ranger/ext/img_display.py
@@ -643,7 +643,7 @@ class KittyImageDisplayer(ImageDisplayer, FileManagerAware):
             image = image.resize((int(scale * image.width), int(scale * image.height)),
                                  self.backend.LANCZOS)
 
-        if image.mode != 'RGB' and image.mode != 'RGBA':
+        if image.mode not in ('RGB', 'RGBA'):
             image = image.convert('RGB')
         # start_x += ((box[0] - image.width) // 2) // self.pix_row
         # start_y += ((box[1] - image.height) // 2) // self.pix_col
diff --git a/ranger/ext/vcs/vcs.py b/ranger/ext/vcs/vcs.py
index 5619ed19..93fb1d0b 100644
--- a/ranger/ext/vcs/vcs.py
+++ b/ranger/ext/vcs/vcs.py
@@ -463,10 +463,10 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
             self.paused.set()
             self._advance.wait()
             self._awoken.wait()
-            if self.__stop.isSet():
+            if self.__stop.is_set():
                 self.stopped.set()
                 return
-            if not self._advance.isSet():
+            if not self._advance.is_set():
                 continue
             self._awoken.clear()
             self.paused.clear()
@@ -491,7 +491,7 @@ class VcsThread(threading.Thread):  # pylint: disable=too-many-instance-attribut
         self._advance.set()
         self._awoken.set()
         self.stopped.wait(1)
-        return self.stopped.isSet()
+        return self.stopped.is_set()
 
     def pause(self):
         """Pause thread"""
diff --git a/tests/pylint/py2_compat.py b/tests/pylint/py2_compat.py
index 7e136148..e0353260 100644
--- a/tests/pylint/py2_compat.py
+++ b/tests/pylint/py2_compat.py
@@ -51,6 +51,9 @@ class Py2CompatibilityChecker(BaseChecker):
                   "Python 2 subprocess.Popen objects were not contextmanagers,"
                   "popen23.Popen wraps them to enable use with"
                   "with-statements."),
+        "E4240": ("Use format method",
+                  "use-format-method",
+                  "Python 2 (and <3.6) does not support f-strings."),
     }
     # This class variable declares the options
     # that are configurable by the user.
@@ -121,6 +124,11 @@ class Py2CompatibilityChecker(BaseChecker):
                     self.add_message("implicit-format-spec", node=node,
                                      confidence=HIGH)
 
+    def visit_joinedstr(self, node):
+        """Make sure we don't use f-strings"""
+        if isinstance(node, astroid.nodes.JoinedStr):
+            self.add_message("use-format-method", node=node, confidence=HIGH)
+
     def visit_with(self, node):
         """Make sure subprocess.Popen objects aren't used in with-statements"""
         for (cm, _) in node.items:
diff --git a/tests/pylint/test_py2_compat.py b/tests/pylint/test_py2_compat.py
index 7156aba7..33fc5681 100644
--- a/tests/pylint/test_py2_compat.py
+++ b/tests/pylint/test_py2_compat.py
@@ -4,6 +4,7 @@ import py2_compat
 
 import astroid
 import pylint.testutils
+from pylint.interfaces import HIGH
 
 from sys import version_info
 PY2 = version_info[0] < 3
@@ -22,9 +23,10 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
         """)
 
         with self.assertAddsMessages(
-            pylint.testutils.Message(
+            pylint.testutils.MessageTest(
                 msg_id='old-style-class',
                 node=oldstyle_class,
+                confidence=HIGH,
             ),
         ):
             self.checker.visit_classdef(oldstyle_class)
@@ -53,9 +55,10 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
         """)
 
         with self.assertAddsMessages(
-            pylint.testutils.Message(
+            pylint.testutils.MessageTest(
                 msg_id='print-without-import',
                 node=print_function_call,
+                confidence=HIGH,
             ),
         ):
             self.checker.visit_call(print_function_call)
@@ -92,9 +95,10 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
         """)
 
         with self.assertAddsMessages(
-            pylint.testutils.Message(
+            pylint.testutils.MessageTest(
                 msg_id='print-without-import',
                 node=early_print_function_call,
+                confidence=HIGH,
             ),
         ):
             self.checker.visit_call(early_print_function_call)
@@ -108,9 +112,10 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
         """)
 
         with self.assertAddsMessages(
-            pylint.testutils.Message(
+            pylint.testutils.MessageTest(
                 msg_id='implicit-format-spec',
                 node=implicit_format_spec,
+                confidence=HIGH,
             ),
         ):
             self.checker.visit_call(implicit_format_spec)
@@ -131,9 +136,10 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
         """)
 
         with self.assertAddsMessages(
-            pylint.testutils.Message(
+            pylint.testutils.MessageTest(
                 msg_id='with-popen23',
                 node=with_Popen,
+                confidence=HIGH,
             ),
         ):
             self.checker.visit_with(with_subprocess_Popen)
@@ -141,6 +147,25 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
         with self.assertNoMessages():
             self.checker.visit_with(with_Popen23)
 
+    def test_use_format(self):
+        old_format, new_format, f_string = astroid.extract_node("""
+            "2 + 2 is %s" % (2+2) #@
+            "2 + 2 is {0}".format(2+2) #@
+            f"2 + 2 is {2+2}" #@
+        """)
+
+        with self.assertAddsMessages(
+            pylint.testutils.MessageTest(
+                msg_id='use-format-method',
+                node=f_string,
+                confidence=HIGH,
+            ),
+        ):
+            self.checker.visit_joinedstr(f_string)
+        with self.assertNoMessages():
+            self.checker.visit_joinedstr(old_format)
+            self.checker.visit_joinedstr(new_format)
+
     # # These checks still exist as old-division and no-absolute-import
     # def test_division_without_import(self):
     #     division = astroid.extract_node("""
@@ -148,7 +173,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
     #     """)
 
     #     with self.assertAddsMessages(
-    #         pylint.testutils.Message(
+    #         pylint.testutils.MessageTest(
     #             msg_id='division-without-import',
     #             node=division,
     #         ),
@@ -170,7 +195,7 @@ class TestPy2CompatibilityChecker(pylint.testutils.CheckerTestCase):
     #     """)
 
     #     with self.assertAddsMessages(
-    #         pylint.testutils.Message(
+    #         pylint.testutils.MessageTest(
     #             msg_id='old-no-absolute-import',
     #             node=no_import,
     #         ),