Hello community,

here is the log from the commit of package urlscan for openSUSE:Factory checked 
in at 2020-03-20 23:58:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/urlscan (Old)
 and      /work/SRC/openSUSE:Factory/.urlscan.new.3160 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "urlscan"

Fri Mar 20 23:58:39 2020 rev:6 rq:786748 version:0.9.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/urlscan/urlscan.changes  2019-02-08 
12:13:45.873461149 +0100
+++ /work/SRC/openSUSE:Factory/.urlscan.new.3160/urlscan.changes        
2020-03-21 00:02:31.449136276 +0100
@@ -1,0 +2,15 @@
+Fri Mar 13 06:10:44 UTC 2020 - Dr. Werner Fink <[email protected]>
+
+- Update to version 0.9.4
+  * Remove Python 2 compatibility
+- Update to version 0.9.3
+  * Cycle through opening links with webbrowser module, xdg-open or --run 
argument
+  * Add option to copy to primary selection or clipboard. Fix #87
+  * Generate new config file using command line switch instead of keybinding
+  * Allow remapping a key to open url in addition to space and enter
+  * Show help menu with F1 and show dynamic keybindings
+  * Allow editing key bindings in config.json. Fix #72.
+- Set python flavour spec file macro to get the rpm pythin macros defined
+- Remove egg information tree as we require python3-urwid
+
+-------------------------------------------------------------------

Old:
----
  urlscan-0.9.2.tar.gz

New:
----
  urlscan-0.9.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ urlscan.spec ++++++
--- /var/tmp/diff_new_pack.gKYhZO/_old  2020-03-21 00:02:32.881137060 +0100
+++ /var/tmp/diff_new_pack.gKYhZO/_new  2020-03-21 00:02:32.909137075 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package urlscan
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,25 +12,29 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
 Name:           urlscan
-Version:        0.9.2
+Version:        0.9.4
 Release:        0
 Summary:        An other URL extractor/viewer
 License:        GPL-2.0-or-later
 Group:          Productivity/Networking/Web/Browsers
-Url:            https://github.com/firecat53/urlscan
+URL:            https://github.com/firecat53/urlscan
 Source0:        
https://github.com/firecat53/urlscan/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
 Source1:        muttrc
+Requires:       python3
+Requires:       python3-base
 Requires:       python3-urwid
+BuildRequires:  python3-base
 BuildRequires:  python3-devel
+BuildRequires:  python3-rpm-macros
 BuildRequires:  python3-setuptools
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 BuildArch:      noarch
-%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from 
distutils.sysconfig import get_python_lib; print get_python_lib()")}
+%define python_flavor python3
 
 %description
 The urlscan utility displays URLs found in an email message with
@@ -49,13 +53,14 @@
 rm -rf %{buildroot}/usr/share/doc/%{name}*
 mkdir -p %{buildroot}%{_defaultdocdir}/%{name}
 install -m 0644 %{S:1} %{buildroot}%{_defaultdocdir}/%{name}
+rm -rvf %{buildroot}%{python_sitelib}/%{name}-%{version}-*-info
 
 %files
 %defattr(-,root,root)
 %license COPYING
 %doc README.rst
 %{_bindir}/%{name}
-%{python_sitelib}/*
+%{python_sitelib}/%{name}
 %{_mandir}/man1/%{name}.1.gz
 %doc %{_defaultdocdir}/%{name}/muttrc
 

++++++ urlscan-0.9.2.tar.gz -> urlscan-0.9.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/.gitignore new/urlscan-0.9.4/.gitignore
--- old/urlscan-0.9.2/.gitignore        2019-01-22 00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/.gitignore        2019-08-30 23:40:59.000000000 +0200
@@ -7,4 +7,3 @@
 dist
 test_emails/
 MANIFEST
-Pipfile.lock
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/Pipfile new/urlscan-0.9.4/Pipfile
--- old/urlscan-0.9.2/Pipfile   2019-01-22 00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/Pipfile   1970-01-01 01:00:00.000000000 +0100
@@ -1,10 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple";
-verify_ssl = true
-name = "pypi"
-
-[packages]
-urwid = ">=1.2.1"
-urlscan = {editable = true, path = "."}
-
-[dev-packages]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/README.rst new/urlscan-0.9.4/README.rst
--- old/urlscan-0.9.2/README.rst        2019-01-22 00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/README.rst        2019-08-30 23:40:59.000000000 +0200
@@ -17,7 +17,9 @@
 mailreader to allow you to easily launch a Web browser for URLs contained in
 email messages. It is a replacement for the "urlview" program.
 
-Requires: Python 2.6+ (including Python 3.x) and the python-urwid library
+*NOTE* The last version that is Python 2 compatible is 0.9.3.
+
+Requires: Python 3.3+ and the python-urwid library
 
 Features
 --------
@@ -46,24 +48,37 @@
 - Execute an arbitrary function (for example, copy URL to clipboard) instead of
   opening URL in a browser.
 
-- Configure colors via ~/.config/urlscan/config.json. Generate default config
-  file for editing with `P`. Cycle through available palettes with `p`.
+- Use `l` to cycle through whether URLs are opened using the Python webbrowser
+  module (default), xdg-open (if installed) or opened by a function passed on
+  the command line with `--run`.
+
+- Configure colors and keybindings via ~/.config/urlscan/config.json. Generate
+  default config file for editing by running `urlscan -g`. Cycle through
+  available palettes with `p`.
 
-- Copy URL to clipboard (primary) with `C`. Requires xsel or xclip.
+- Copy URL to clipboard with `C` or to primary selection with `P`.  Requires
+  xsel or xclip.
 
 - Run a command with the selected URL as the argument or pipe the selected
   URL to a command.
 
+- Show complete help menu with `F1`. Hide header on startup with `--nohelp`.
+
 Installation and setup
 ----------------------
 
 To install urlscan, install from your distribution repositories (Archlinux),
-from Pypi, or install from source using setup.py.
+from Pypi, or do a local development install with pip -e::
+
+    pip install --user urlscan
+
+    OR
+
+    cd <path/to/urlscan> && pip install --user -e .
 
 .. NOTE::
 
-    To work with Python 3.x the minimum required version of urwid is 1.2.1.
-    Python 2.x needs urwid >= 1.1.0
+    The minimum required version of urwid is 1.2.1.
 
 Once urlscan is installed, add the following lines to your .muttrc:
 
@@ -87,7 +102,7 @@
 
 ::
 
-    urlscan [-n, --no-browser] [-c, --compact] [-d, --dedupe] [-r, --run 
<expression>] [-p, --pipe] <file>
+    urlscan [-g, --genconf] [-n, --no-browser] [-c, --compact] [-d, --dedupe] 
[-r, --run <expression>] [-p, --pipe] [-H, --nohelp] <file>
 
 Urlscan can extract URLs and email addresses from emails or any text file.
 Calling with no flags will start the curses browser. Calling with '-n' will 
just
@@ -96,19 +111,52 @@
 duplicate URLs. Files can also be piped to urlscan using normal shell pipe
 mechanisms: `cat <something> | urlscan` or `urlscan < <something>`
 
-Instead of opening a web browser, the selected URL can be passed as the
-argument to a command using `--run <command>`. Alternatively, the URL can be
-piped to the command using `--run <command> --pipe`. Using --run with --pipe is
-preferred if the command supports it, as it is marginally more secure and
-tolerant of special characters in the URL.
+Instead of opening a web browser, the selected URL can be passed as the 
argument
+to a command using `--run "<command> {}"`. Note the use of `{}` in the command
+string to denote the selected URL. Alternatively, the URL can be piped to the
+command using `--run <command> --pipe`. Using --run with --pipe is preferred if
+the command supports it, as it is marginally more secure and tolerant of 
special
+characters in the URL.
 
 Theming
 -------
 
-Press 'P' from urlscan to generate ~/.config/urlscan/config.json with the
-default color and black & white palettes. This can be edited or added to, as
-desired. The first palette in the list will be the default. Configure the
-palettes according to the `Urwid display attributes`_.
+Run `urlscan -g` to generate ~/.config/urlscan/config.json with the default
+color and black & white palettes. This can be edited or added to, as desired.
+The first palette in the list will be the default. Configure the palettes
+according to the `Urwid display attributes`_.
+
+Keybindings
+-----------
+
+Run `urlscan -g` to generate ~/.config/urlscan/config.json. All of the keys 
will
+be listed. You can either leave in place or delete any that will not be 
altered.
+
+To unset a binding, set it equal to "".  For example: `"P": ""`
+
+The follow actions are supported:
+
+- `all_escape` -- toggle unescape all URLs (default: `u`)
+- `all_shorten` -- toggle shorten all URLs (default: `S`)
+- `bottom` -- move cursor to last item (default: `G`)
+- `clear_screen` -- redraw screen (default: `Ctrl-l`)
+- `clipboard` -- copy highlighted URL to clipboard using xsel/xclip (default: 
`C`)
+- `clipboard_pri` -- copy highlighted URL to primary selection using 
xsel/xclip (default: `P`)
+- `context` -- show/hide context (default: `c`)
+- `down` -- cursor down (default: `j`)
+- `help_menu` -- show/hide help menu (default: `F1`)
+- `link_handler` -- cycle link handling (webbrowser, xdg-open or --run) 
(default: `l`)
+- `open_url` -- open selected URL (default: `space` or `enter`)
+- `palette` -- cycle through palettes (default: `p`)
+- `quit` -- quit (default: `q` or `Q`)
+- `shorten` -- toggle shorten highlighted URL (default: `s`)
+- `top` -- move to first list item (default: `g`)
+- `up` -- cursor up (default: `k`)
+
+Update TLD list (for developers, not users)
+-------------------------------------------
+
+`wget https://data.iana.org/TLD/tlds-alpha-by-domain.txt`
 
 Known bugs and limitations
 --------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/bin/urlscan 
new/urlscan-0.9.4/bin/urlscan
--- old/urlscan-0.9.2/bin/urlscan       2019-01-22 00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/bin/urlscan       2019-08-30 23:40:59.000000000 +0200
@@ -41,6 +41,9 @@
 
     """
     arg_parse = argparse.ArgumentParser(description="Parse and display URLs")
+    arg_parse.add_argument('--genconf', '-g',
+                           action='store_true', default=False,
+                           help="Generate config file and exit.")
     arg_parse.add_argument('--compact', '-c',
                            action='store_true', default=False,
                            help="Don't display the context of each URL.")
@@ -58,6 +61,9 @@
     arg_parse.add_argument('--pipe', '-p', dest='pipe',
                            action='store_true', default=False,
                            help='Pipe URL into the command specified by --run')
+    arg_parse.add_argument('--nohelp', '-H', dest='nohelp',
+                           action='store_true', default=False,
+                           help='Hide help menu by default')
     arg_parse.add_argument('message', nargs='?', default=sys.stdin,
                            help="Filename of the message to parse")
     return arg_parse.parse_args()
@@ -172,10 +178,14 @@
 
     """
     args = parse_arguments()
+    if args.genconf is True:
+        urlchoose.URLChooser([], genconf=True)
+        return
     msg = process_input(args.message)
     if args.nobrowser is False:
         tui = urlchoose.URLChooser(urlscan.msgurls(msg),
                                    compact=args.compact,
+                                   nohelp=args.nohelp,
                                    dedupe=args.dedupe,
                                    run=args.run,
                                    pipe=args.pipe)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/conf.py new/urlscan-0.9.4/conf.py
--- old/urlscan-0.9.2/conf.py   1970-01-01 01:00:00.000000000 +0100
+++ new/urlscan-0.9.4/conf.py   2019-08-30 23:40:59.000000000 +0200
@@ -0,0 +1,3 @@
+URLINTERNALPATTERN = r'[{}()@\w/\\\-%?!&.=:;+,#~]'
+URLTRAILINGPATTERN = r'[{}(@\w/\-%&=+#]'
+HTTPURLPATTERN = (r'(?:(https?|file|ftps?)://' + URLINTERNALPATTERN + r'*' + 
URLTRAILINGPATTERN + r')')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/setup.py new/urlscan-0.9.4/setup.py
--- old/urlscan-0.9.2/setup.py  2019-01-22 00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/setup.py  2019-08-30 23:40:59.000000000 +0200
@@ -3,12 +3,12 @@
 from setuptools import setup
 
 setup(name="urlscan",
-      version="0.9.2",
+      version="0.9.4",
       description="View/select the URLs in an email message or file",
       author="Scott Hansen",
       author_email="[email protected]",
       url="https://github.com/firecat53/urlscan";,
-      download_url="https://github.com/firecat53/urlscan/archive/0.9.2.zip";,
+      download_url="https://github.com/firecat53/urlscan/archive/0.9.4.zip";,
       packages=['urlscan'],
       scripts=['bin/urlscan'],
       package_data={'urlscan': ['assets/*']},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/urlscan-0.9.2/urlscan/assets/tlds-alpha-by-domain.txt 
new/urlscan-0.9.4/urlscan/assets/tlds-alpha-by-domain.txt
--- old/urlscan-0.9.2/urlscan/assets/tlds-alpha-by-domain.txt   2019-01-22 
00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/urlscan/assets/tlds-alpha-by-domain.txt   2019-08-30 
23:40:59.000000000 +0200
@@ -1,4 +1,4 @@
-# Version 2018073000, Last Updated Mon Jul 30 07:07:01 2018 UTC
+# Version 2019021501, Last Updated Fri Feb 15 07:07:01 2019 UTC
 AAA
 AARP
 ABARTH
@@ -145,7 +145,6 @@
 BJ
 BLACK
 BLACKFRIDAY
-BLANCO
 BLOCKBUSTER
 BLOG
 BLOOMBERG
@@ -391,7 +390,6 @@
 ENGINEER
 ENGINEERING
 ENTERPRISES
-EPOST
 EPSON
 EQUIPMENT
 ER
@@ -522,7 +520,6 @@
 GOLDPOINT
 GOLF
 GOO
-GOODHANDS
 GOODYEAR
 GOOG
 GOOGLE
@@ -651,7 +648,6 @@
 JETZT
 JEWELRY
 JIO
-JLC
 JLL
 JM
 JMP
@@ -934,7 +930,6 @@
 PA
 PAGE
 PANASONIC
-PANERAI
 PARIS
 PARS
 PARTNERS
@@ -1154,13 +1149,13 @@
 SONY
 SOY
 SPACE
-SPIEGEL
 SPORT
 SPOT
 SPREADBETTING
 SR
 SRL
 SRT
+SS
 ST
 STADA
 STAPLES
@@ -1168,7 +1163,6 @@
 STARHUB
 STATEBANK
 STATEFARM
-STATOIL
 STC
 STCGROUP
 STOCKHOLM
@@ -1214,7 +1208,6 @@
 TECH
 TECHNOLOGY
 TEL
-TELECITY
 TELEFONICA
 TEMASEK
 TENNIS
@@ -1309,7 +1302,6 @@
 VIRGIN
 VISA
 VISION
-VISTA
 VISTAPRINT
 VIVA
 VIVO
@@ -1457,6 +1449,7 @@
 XN--MGBAAKC7DVF
 XN--MGBAAM7A8H
 XN--MGBAB2BD
+XN--MGBAH1A3HJKRD
 XN--MGBAI9AZGQP6J
 XN--MGBAYH7GPA
 XN--MGBB9FBPOB
@@ -1535,7 +1528,6 @@
 ZARA
 ZERO
 ZIP
-ZIPPO
 ZM
 ZONE
 ZUERICH
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/urlscan/urlchoose.py 
new/urlscan-0.9.4/urlscan/urlchoose.py
--- old/urlscan-0.9.2/urlscan/urlchoose.py      2019-01-22 00:05:53.000000000 
+0100
+++ new/urlscan-0.9.4/urlscan/urlchoose.py      2019-08-30 23:40:59.000000000 
+0200
@@ -24,7 +24,7 @@
 from os.path import dirname, exists, expanduser
 import re
 import shlex
-from subprocess import Popen, PIPE
+from subprocess import call, Popen, PIPE, DEVNULL
 import sys
 from threading import Thread
 from time import sleep
@@ -34,11 +34,6 @@
 import urwid.curses_display
 import urwid.raw_display
 
-# Python 2 compatibility
-try:
-    FileNotFoundError
-except NameError:
-    FileNotFoundError = IOError
 
 def shorten_url(url, cols, shorten):
     """Shorten long URLs to fit on one line.
@@ -91,38 +86,89 @@
 
 class URLChooser:
 
-    def __init__(self, extractedurls, compact=False, dedupe=False, 
shorten=True,
-                 run="", pipe=False):
+    def __init__(self, extractedurls, compact=False, nohelp=False, 
dedupe=False,
+                 shorten=True, run="", pipe=False, genconf=False):
         self.conf = expanduser("~/.config/urlscan/config.json")
-        self.palettes = []
+        self.keys = {'/': self._search_key,
+                     '0': self._digits,
+                     '1': self._digits,
+                     '2': self._digits,
+                     '3': self._digits,
+                     '4': self._digits,
+                     '5': self._digits,
+                     '6': self._digits,
+                     '7': self._digits,
+                     '8': self._digits,
+                     '9': self._digits,
+                     'C': self._clipboard,
+                     'c': self._context,
+                     'ctrl l': self._clear_screen,
+                     'f1': self._help_menu,
+                     'G': self._bottom,
+                     'g': self._top,
+                     'j': self._down,
+                     'k': self._up,
+                     'P': self._clipboard_pri,
+                     'l': self._link_handler,
+                     'p': self._palette,
+                     'Q': self._quit,
+                     'q': self._quit,
+                     'S': self._all_shorten,
+                     's': self._shorten,
+                     'u': self._all_escape
+                     }
+        self.palettes = {}
+        # Default color palette
+        default = [('header', 'white', 'dark blue', 'standout'),
+                   ('footer', 'white', 'dark red', 'standout'),
+                   ('search', 'white', 'dark green', 'standout'),
+                   ('msgtext', '', ''),
+                   ('msgtext:ellipses', 'light gray', 'black'),
+                   ('urlref:number:braces', 'light gray', 'black'),
+                   ('urlref:number', 'yellow', 'black', 'standout'),
+                   ('urlref:url', 'white', 'black', 'standout'),
+                   ('url:sel', 'white', 'dark blue', 'bold')]
+        # Default black & white palette
+        blw = [('header', 'black', 'light gray', 'standout'),
+               ('footer', 'black', 'light gray', 'standout'),
+               ('search', 'black', 'light gray', 'standout'),
+               ('msgtext', '', ''),
+               ('msgtext:ellipses', 'white', 'black'),
+               ('urlref:number:braces', 'white', 'black'),
+               ('urlref:number', 'white', 'black', 'standout'),
+               ('urlref:url', 'white', 'black', 'standout'),
+               ('url:sel', 'black', 'light gray', 'bold')]
+        self.palettes.update([("default", default), ("bw", blw)])
+        if genconf is True:
+            self._config_create()
         try:
             with open(self.conf, 'r') as conf_file:
                 data = json.load(conf_file)
-                for pal in data.values():
-                    self.palettes.append([tuple(i) for i in pal])
+                try:
+                    for pal_name, pal in data['palettes'].items():
+                        self.palettes.update([(pal_name, [tuple(i) for i in 
pal])])
+                except KeyError:
+                    pass
+                try:
+                    items = data['keys'].items()
+                    for key, value in items:
+                        if value:
+                            if value == "open_url":
+                                urwid.Button._command_map._command[key] = 
'activate'
+                            value = getattr(self, "_{}".format(value))
+                        else:
+                            del self.keys[key]
+                            continue
+                        self.keys.update([(key, value)])
+                except KeyError:
+                    pass
         except FileNotFoundError:
             pass
-        # Default color palette
-        self.palettes.append([('header', 'white', 'dark blue', 'standout'),
-                              ('footer', 'white', 'dark red', 'standout'),
-                              ('search', 'white', 'dark green', 'standout'),
-                              ('msgtext', '', ''),
-                              ('msgtext:ellipses', 'light gray', 'black'),
-                              ('urlref:number:braces', 'light gray', 'black'),
-                              ('urlref:number', 'yellow', 'black', 'standout'),
-                              ('urlref:url', 'white', 'black', 'standout'),
-                              ('url:sel', 'white', 'dark blue', 'bold')])
-        # Default black & white palette
-        self.palettes.append([('header', 'black', 'light gray', 'standout'),
-                              ('footer', 'black', 'light gray', 'standout'),
-                              ('search', 'black', 'light gray', 'standout'),
-                              ('msgtext', '', ''),
-                              ('msgtext:ellipses', 'white', 'black'),
-                              ('urlref:number:braces', 'white', 'black'),
-                              ('urlref:number', 'white', 'black', 'standout'),
-                              ('urlref:url', 'white', 'black', 'standout'),
-                              ('url:sel', 'black', 'light gray', 'bold')])
-
+        try:
+            call(['xdg-open'], stdout=DEVNULL)
+            self.xdg = True
+        except OSError:
+            self.xdg = False
         self.shorten = shorten
         self.compact = compact
         self.run = run
@@ -131,6 +177,8 @@
         self.search_string = ""
         self.no_matches = False
         self.enter = False
+        self.activate_keys = [i for i, j in 
urwid.Button._command_map._command.items()
+                              if j == 'activate']
         self.items, self.urls = self.process_urls(extractedurls,
                                                   dedupe=dedupe,
                                                   shorten=self.shorten)
@@ -146,31 +194,34 @@
         # Store items grouped into sections
         self.items_org = grp_list(self.items)
         listbox = urwid.ListBox(self.items)
-        header = (":: q - Quit :: "
-                  "/ - search :: "
-                  "c - context :: "
-                  "C - copy to clipboard :: "
-                  "s - URL short :: "
-                  "S - all URL short :: "
-                  "g/G - top/bottom :: "
-                  "<num> - jump to <num> :: "
-                  "p - cycle palettes :: "
-                  "P - create config file ::"
-                  "u - unescape URL ::")
-        headerwid = urwid.AttrMap(urwid.Text(header), 'header')
-        self.top = urwid.Frame(listbox, headerwid)
+        self.header = (":: F1 - help/keybindings :: "
+                       "q - quit :: "
+                       "/ - search :: "
+                       "URL opening mode - {}")
+        self.link_open_modes = ["Web Browser", "Xdg-Open"] if self.xdg is True 
else ["Web Browser"]
+        if self.run:
+            self.link_open_modes.insert(0, self.run)
+        self.nohelp = nohelp
+        if nohelp is False:
+            self.headerwid = urwid.AttrMap(urwid.Text(
+                self.header.format(self.link_open_modes[0])), 'header')
+        else:
+            self.headerwid = None
+        self.top = urwid.Frame(listbox, self.headerwid)
         if self.urls:
             self.top.body.focus_position = \
                 (2 if self.compact is False else 0)
         self.tui = urwid.curses_display.Screen()
+        self.palette_names = list(self.palettes.keys())
         self.palette_idx = 0
         self.number = ""
+        self.help_menu = False
 
     def main(self):
         """Urwid main event loop
 
         """
-        self.loop = urwid.MainLoop(self.top, self.palettes[0], screen=self.tui,
+        self.loop = urwid.MainLoop(self.top, 
self.palettes[self.palette_names[0]], screen=self.tui,
                                    handle_mouse=False, 
input_filter=self.handle_keys,
                                    unhandled_input=self.unhandled)
         self.loop.run()
@@ -200,17 +251,20 @@
                         text = ""
                     footerwid = urwid.AttrMap(urwid.Text(text), footer)
                     self.top.footer = footerwid
-                elif k == ' ':
-                    self.search_string += " "
-                    footerwid = urwid.AttrMap(urwid.Text(text), 'footer')
-                    self.top.footer = footerwid
+                elif k in self.activate_keys:
+                    self.search_string += k
+                    self._search()
                 elif k == 'backspace':
                     self.search_string = self.search_string[:-1]
                     self._search()
-            elif k in ('enter', ' ') and self.urls and self.search is False:
-                load_text = "Loading URL..." if not self.run else "Executing: 
{}".format(self.run)
-                if os.environ.get('BROWSER') not in ['elinks', 'links', 'w3m', 
'lynx']:
-                    self._footer_start_thread(load_text, 5)
+            elif k in self.activate_keys and \
+                    self.urls and \
+                    self.search is False and \
+                    self.help_menu is False:
+                self._open_url()
+            elif self.help_menu is True:
+                self._help_menu()
+                return []
             if k == 'up':
                 # Works around bug where the up arrow goes higher than the top 
list
                 # item and unintentionally triggers context and palette 
switches.
@@ -224,145 +278,235 @@
         return [i for i in keys if i != 'backspace']
 
     def unhandled(self, key):
-        """Add other keyboard actions (q, j, k, s, S, c, C, g, G) not handled 
by
-        the ListBox widget.
+        """Handle other keyboard actions not handled by the ListBox widget.
 
         """
-        size = self.tui.get_cols_rows()
+        self.key = key
+        self.size = self.tui.get_cols_rows()
         if self.search is True:
             if self.enter is False and self.no_matches is False:
                 if len(key) == 1 and key.isprintable():
                     self.search_string += key
                 self._search()
+            elif self.enter is True and not self.search_string:
+                self.search = False
+                self.enter = False
+            return
+        if not self.urls and key not in "Qq":
+            return  # No other actions are useful with no URLs
+        if self.help_menu is False:
+            try:
+                self.keys[key]()
+            except KeyError:
+                pass
+
+    def _quit(self):
+        """q/Q"""
+        raise urwid.ExitMainLoop()
+
+    def _open_url(self):
+        """<Enter> or <space>"""
+        load_text = "Loading URL..." if self.link_open_modes[0] != self.run \
+                else "Executing: {}".format(self.run)
+        if os.environ.get('BROWSER') not in ['elinks', 'links', 'w3m', 'lynx']:
+            self._footer_start_thread(load_text, 5)
+
+    def _help_menu(self):
+        """F1"""
+        if self.help_menu is False:
+            self.focus_pos_saved = self.top.body.focus_position
+            help_men = "\n".join(["{} - {}".format(i, j.__name__.strip('_'))
+                                  for i, j in self.keys.items() if j.__name__ 
!=
+                                  '_digits'])
+            help_men = "KEYBINDINGS\n" + help_men + "\n<0-9> - Jump to item"
+            docs = ("OPTIONS\n"
+                    "all_escape -- toggle unescape all URLs\n"
+                    "all_shorten -- toggle shorten all URLs\n"
+                    "bottom -- move cursor to last item\n"
+                    "clear_screen -- redraw screen\n"
+                    "clipboard -- copy highlighted URL to clipboard using 
xsel/xclip\n"
+                    "clipboard_pri -- copy highlighted URL to primary 
selection using xsel/xclip\n"
+                    "config_create -- create ~/.config/urlscan/config.json\n"
+                    "context -- show/hide context\n"
+                    "down -- cursor down\n"
+                    "help_menu -- show/hide help menu\n"
+                    "link_handler -- cycle through xdg-open, webbrowser and 
user-defined function\n"
+                    "open_url -- open selected URL\n"
+                    "palette -- cycle through palettes\n"
+                    "quit -- quit\n"
+                    "shorten -- toggle shorten highlighted URL\n"
+                    "top -- move to first list item\n"
+                    "up -- cursor up\n")
+            self.top.body = \
+                    urwid.ListBox(urwid.SimpleListWalker([urwid.Columns([(30, 
urwid.Text(help_men)),
+                                                                         
urwid.Text(docs)])]))
+        else:
+            self.top.body = urwid.ListBox(self.items)
+            self.top.body.focus_position = self.focus_pos_saved
+        self.help_menu = not self.help_menu
+
+    def _search_key(self):
+        """ / """
+        if self.urls:
+            self.search = True
+            if self.compact is True:
+                self.compact = False
+                self.items, self.items_com = self.items_com, self.items
+        else:
             return
-        if key in ('q', 'Q'):
-            raise urwid.ExitMainLoop()
-        elif not self.urls:
-            pass  # No other actions are useful with no URLs
-        elif key == '/':
-            if self.urls:
-                self.search = True
-                if self.compact is True:
-                    self.compact = False
-                    self.items, self.items_com = self.items_com, self.items
+        self.no_matches = False
+        self.search_string = ""
+        # Reset the search highlighting
+        self._search()
+        footerwid = urwid.AttrMap(urwid.Text("Search: "), 'footer')
+        self.top.footer = footerwid
+        self.items = self.items_orig
+        self.top.body = urwid.ListBox(self.items)
+
+    def _digits(self):
+        """ 0-9 """
+        self.number += self.key
+        try:
+            if self.compact is False:
+                self.top.body.focus_position = \
+                    self.items.index(self.items_com[max(int(self.number) - 1, 
0)])
             else:
-                return
-            self.no_matches = False
-            self.search_string = ""
-            # Reset the search highlighting
-            self._search()
-            footerwid = urwid.AttrMap(urwid.Text("Search: "), 'footer')
+                self.top.body.focus_position = \
+                    self.items.index(self.items[max(int(self.number) - 1, 0)])
+        except IndexError:
+            self.number = self.number[:-1]
+        self.top.keypress(self.size, "")  # Trick urwid into redisplaying the 
cursor
+        if self.number:
+            self._footer_start_thread("Selection: {}".format(self.number), 1)
+
+    def _clear_screen(self):
+        """ Ctrl-l """
+        self.draw_screen(self.size)
+
+    def _down(self):
+        """ j """
+        self.top.keypress(self.size, "down")
+
+    def _up(self):
+        """ k """
+        self.top.keypress(self.size, "up")
+
+    def _top(self):
+        """ g """
+        # Goto top of the list
+        self.top.body.focus_position = 2 if self.compact is False else 0
+        self.top.keypress(self.size, "")  # Trick urwid into redisplaying the 
cursor
+
+    def _bottom(self):
+        """ G """
+        # Goto bottom of the list
+        self.top.body.focus_position = len(self.items) - 1
+        self.top.keypress(self.size, "")  # Trick urwid into redisplaying the 
cursor
+
+    def _shorten(self):
+        """ s """
+        # Toggle shortened URL for selected item
+        fpo = self.top.body.focus_position
+        url_idx = len([i for i in self.items[:fpo + 1]
+                       if isinstance(i, urwid.Columns)]) - 1
+        if self.compact is False and fpo <= 1:
+            return
+        url = self.urls[url_idx]
+        short = not "..." in self.items[fpo][1].label
+        self.items[fpo][1].set_label(shorten_url(url, self.size[0], short))
+
+    def _all_shorten(self):
+        """ S """
+        # Toggle all shortened URLs
+        self.shorten = not self.shorten
+        urls = iter(self.urls)
+        for item in self.items:
+            # Each Column has (Text, Button). Update the Button label
+            if isinstance(item, urwid.Columns):
+                item[1].set_label(shorten_url(next(urls),
+                                              self.size[0],
+                                              self.shorten))
+    def _all_escape(self):
+        """ u """
+        # Toggle all escaped URLs
+        self.unesc = not self.unesc
+        self.urls, self.urls_unesc = self.urls_unesc, self.urls
+        urls = iter(self.urls)
+        for item in self.items:
+            # Each Column has (Text, Button). Update the Button label
+            if isinstance(item, urwid.Columns):
+                item[1].set_label(shorten_url(next(urls),
+                                              self.size[0],
+                                              self.shorten))
+
+    def _context(self):
+        """ c """
+        # Show/hide context
+        if self.search_string:
+            # Reset search when toggling compact mode
+            footerwid = urwid.AttrMap(urwid.Text(""), 'default')
             self.top.footer = footerwid
+            self.search_string = ""
             self.items = self.items_orig
-            self.top.body = urwid.ListBox(self.items)
-        elif key.isdigit():
-            self.number += key
-            try:
-                if self.compact is False:
-                    self.top.body.focus_position = \
-                        self.items.index(self.items_com[max(int(self.number) - 
1, 0)])
-                else:
-                    self.top.body.focus_position = \
-                        self.items.index(self.items[max(int(self.number) - 1, 
0)])
-            except IndexError:
-                self.number = self.number[:-1]
-            self.top.keypress(size, "")  # Trick urwid into redisplaying the 
cursor
-            if self.number:
-                self._footer_start_thread("Selection: {}".format(self.number), 
1)
-        elif key == 'ctrl l':
-            self.draw_screen(size)
-        elif key == 'j':
-            self.top.keypress(size, "down")
-        elif key == 'k':
-            self.top.keypress(size, "up")
-        elif key == 'g':
-            # Goto top of the list
-            self.top.body.focus_position = 2 if self.compact is False else 0
-            self.top.keypress(size, "")  # Trick urwid into redisplaying the 
cursor
-        elif key == 'G':
-            # Goto bottom of the list
-            self.top.body.focus_position = len(self.items) - 1
-            self.top.keypress(size, "")  # Trick urwid into redisplaying the 
cursor
-        elif key == 's':
-            # Toggle shortened URL for selected item
-            fpo = self.top.body.focus_position
-            url_idx = len([i for i in self.items[:fpo + 1]
-                           if isinstance(i, urwid.Columns)]) - 1
-            if self.compact is False and fpo <= 1:
-                return
-            url = self.urls[url_idx]
-            short = False if "..." in self.items[fpo][1].label else True
-            self.items[fpo][1].set_label(shorten_url(url, size[0], short))
-        elif key in ('S', 'u'):
-            # Toggle all shortened or escaped URLs
-            if key == 'S':
-                self.shorten = not self.shorten
-            if key == 'u':
-                self.unesc = not self.unesc
-                self.urls, self.urls_unesc = self.urls_unesc, self.urls
-            urls = iter(self.urls)
-            for item in self.items:
-                # Each Column has (Text, Button). Update the Button label
-                if isinstance(item, urwid.Columns):
-                    item[1].set_label(shorten_url(next(urls),
-                                                  size[0],
-                                                  self.shorten))
-        elif key == 'c':
-            # Show/hide context
-            if self.search_string:
-                # Reset search when toggling compact mode
-                footerwid = urwid.AttrMap(urwid.Text(""), 'default')
-                self.top.footer = footerwid
-                self.search_string = ""
-                self.items = self.items_orig
-            fpo = self.top.body.focus_position
-            self.items, self.items_com = self.items_com, self.items
-            self.top.body = urwid.ListBox(self.items)
-            self.top.body.focus_position = self._cur_focus(fpo)
-            self.compact = not self.compact
-        elif key == 'C':
-            # Copy highlighted url to clipboard
-            fpo = self.top.body.focus_position
-            url_idx = len([i for i in self.items[:fpo + 1]
-                           if isinstance(i, urwid.Columns)]) - 1
-            if self.compact is False and fpo <= 1:
-                return
-            url = self.urls[url_idx]
+        fpo = self.top.body.focus_position
+        self.items, self.items_com = self.items_com, self.items
+        self.top.body = urwid.ListBox(self.items)
+        self.top.body.focus_position = self._cur_focus(fpo)
+        self.compact = not self.compact
+
+    def _clipboard(self, pri=False):
+        """ C """
+        # Copy highlighted url to clipboard
+        fpo = self.top.body.focus_position
+        url_idx = len([i for i in self.items[:fpo + 1]
+                       if isinstance(i, urwid.Columns)]) - 1
+        if self.compact is False and fpo <= 1:
+            return
+        url = self.urls[url_idx]
+        if pri is True:
             cmds = ("xsel -i", "xclip -i")
-            for cmd in cmds:
-                try:
-                    proc = Popen(shlex.split(cmd), stdin=PIPE)
-                    
proc.communicate(input=url.encode(sys.getdefaultencoding()))
-                    self._footer_start_thread("Copied url to primary 
selection", 5)
-                except OSError:
-                    continue
-                break
-        elif key == 'p':
-            # Loop through available palettes
-            self.palette_idx += 1
+        else:
+            cmds = ("xsel -ib", "xclip -i -selection clipboard")
+        for cmd in cmds:
             try:
-                
self.loop.screen.register_palette(self.palettes[self.palette_idx])
-            except IndexError:
-                self.loop.screen.register_palette(self.palettes[0])
-                self.palette_idx = 0
-            self.loop.screen.clear()
-        elif key == 'P':
-            # Create ~/.config/urlscan/config.json if if doesn't exist
-            if not exists(self.conf):
-                try:
-                    # Python 2/3 compatible recursive directory creation
-                    os.makedirs(dirname(expanduser(self.conf)))
-                except OSError as err:
-                    if errno.EEXIST != err.errno:
-                        raise
-                names = ["default", "bw"]
-                with open(expanduser(self.conf), 'w') as pals:
-                    pals.writelines(json.dumps(dict(zip(names,
-                                                        self.palettes)),
-                                               indent=4))
-                self._footer_start_thread("Created 
~/.config/urlscan/config.json", 5)
-            else:
-                self._footer_start_thread("Config.json already exists", 5)
+                proc = Popen(shlex.split(cmd), stdin=PIPE)
+                proc.communicate(input=url.encode(sys.getdefaultencoding()))
+                self._footer_start_thread("Copied url to {} selection".format(
+                    "primary" if pri is True else "clipboard"), 5)
+            except OSError:
+                continue
+            break
+
+    def _clipboard_pri(self):
+        """ P """
+        # Copy highlighted url to primary selection
+        self._clipboard(pri=True)
+
+    def _palette(self):
+        """ p """
+        # Loop through available palettes
+        self.palette_idx += 1
+        try:
+            
self.loop.screen.register_palette(self.palettes[self.palette_names[self.palette_idx]])
+        except IndexError:
+            
self.loop.screen.register_palette(self.palettes[self.palette_names[0]])
+            self.palette_idx = 0
+        self.loop.screen.clear()
+
+    def _config_create(self):
+        """ --genconf """
+        # Create ~/.config/urlscan/config.json if if doesn't exist
+        if not exists(self.conf):
+            os.makedirs(dirname(expanduser(self.conf)), exist_ok=True)
+            keys = dict(zip(self.keys.keys(),
+                            [i.__name__.strip('_') for i in 
self.keys.values()]))
+            with open(expanduser(self.conf), 'w') as pals:
+                pals.writelines(json.dumps({"palettes": self.palettes, "keys": 
keys},
+                                           indent=4))
+            print("Created ~/.config/urlscan/config.json")
+        else:
+            print("~/.config/urlscan/config.json already exists")
+
 
     def _footer_start_thread(self, text, time):
         """Display given text in the footer. Clears after <time> seconds
@@ -456,6 +600,18 @@
     def _get_search(self):
         return lambda: self.search, lambda: self.enter
 
+    def _link_handler(self):
+        """Function to cycle through opening links via webbrowser module,
+        xdg-open or custom expression passed with --run.
+
+        """
+        mode = self.link_open_modes.pop()
+        self.link_open_modes.insert(0, mode)
+        if self.nohelp is False:
+            self.headerwid = urwid.AttrMap(urwid.Text(
+                self.header.format(self.link_open_modes[0])), 'header')
+            self.top.header = self.headerwid
+
     def mkbrowseto(self, url):
         """Create the urwid callback function to open the web browser or call
         another function with the URL.
@@ -481,9 +637,12 @@
                 if self._get_search()[1]() is True:
                     self.search = False
                     self.enter = False
-            elif not self.run:
+            elif self.link_open_modes[0] == "Web Browser":
                 webbrowser.open(url)
-            elif self.run and self.pipe:
+            elif self.link_open_modes[0] == "Xdg-Open":
+                run = 'xdg-open "{}"'.format(url)
+                process = Popen(shlex.split(run), stdout=PIPE, stdin=PIPE)
+            elif self.link_open_modes[0] == self.run and self.pipe:
                 process = Popen(shlex.split(self.run), stdout=PIPE, stdin=PIPE)
                 process.communicate(input=url.encode(sys.getdefaultencoding()))
             else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/urlscan/urlscan.py 
new/urlscan-0.9.4/urlscan/urlscan.py
--- old/urlscan-0.9.2/urlscan/urlscan.py        2019-01-22 00:05:53.000000000 
+0100
+++ new/urlscan-0.9.4/urlscan/urlscan.py        2019-08-30 23:40:59.000000000 
+0200
@@ -18,13 +18,9 @@
 
 """Contains the backend logic that scans messages for URLs and context."""
 
-from __future__ import unicode_literals
 import os
 import re
-try:
-    from HTMLParser import HTMLParser
-except ImportError:
-    from html.parser import HTMLParser
+from html.parser import HTMLParser
 
 
 def get_charset(message, default="utf-8"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/urlscan-0.9.2/urlscan.1 new/urlscan-0.9.4/urlscan.1
--- old/urlscan-0.9.2/urlscan.1 2019-01-22 00:05:53.000000000 +0100
+++ new/urlscan-0.9.4/urlscan.1 2019-08-30 23:40:59.000000000 +0200
@@ -1,6 +1,6 @@
 .\"                                      Hey, EMACS: -*- nroff -*-
 
-.TH URLSCAN 1 "January 14, 2019"
+.TH URLSCAN 1 "30 August 2019"
 
 .SH NAME
 urlscan \- browse the URLs in an email message from a terminal
@@ -30,35 +30,50 @@
 and base64.
 
 \fB2.\fR Extraction and display of the context surrounding each URL. Toggle
-context view on/off with `c`.
+context view on/off with \fBc\fR.
 
-\fB3.\fR Copy current URL to clipboard primary selection with `C`.
+\fB3.\fR Copy current URL to primary selection with \fBP\fR or to clipboard 
with
+\fBC\fR.
 
 \fB4.\fR URLs are shortened by default to fit on one line. Toggle one or all
-shortened URLs with `s` or `S`.
+shortened URLs with \fBs\fR or \fBS\fR.
 
-\fB5.\fR Incremental case-insensitive search using `/`. Footer shows current
-search term. `/` again resets search.
+\fB5.\fR Incremental case-insensitive search using \fB/\fR. Footer shows 
current
+search term. \fB/\fR again resets search.
 
 \fB6.\fR Cycle through all available palettes (color and black & white 
available
-by default) using `p`. `P` will generate a ~/.config/urlscan/config.json file
-for editing or adding additional pallettes. See
+by default) using \fBp\fR. Running \fBurlscan \-g\fR will generate a
+~/.config/urlscan/config.json file for editing or adding additional pallettes
+and keybindings. See
 
 http://urwid.org/manual/displayattributes.html#display-attributes
 
-for options and allowed values.
+for color options and allowed values.
 
-\fB7.\fR `u` will unescape the highlighted URL if necessary.
+\fB7.\fR \fBu\fR will unescape the highlighted URL if necessary.
 
-\fB8.\fR Run a command with the selected URL as the argument or pipe the 
selected
-  URL to a command using the `--run` and `--pipe` arguments.
+\fB8.\fR Run a command with the selected URL as the argument or pipe the
+selected URL to a command using the \fB--run\fR and \fB--pipe\fR arguments.
+
+\fB9.\fR Use \fBl\fR to cycle through whether URLs are opened using the Python
+webbrowser module (default), xdg-open (if installed) or a function passed on 
the
+command line with \fB--run\fR. The \fB--run\fR function will respect the value
+of \fB--pipe\fR.
+
+\fB10.\fR \fBF1\fR shows the help menu.
 
 .SH OPTIONS
 .TP
+.B \-g, \-\-genconf
+Generate ~/.config/urlscan/config.json with default options.
+.TP
 .B \-c, \-\-compact
 Display a simple list of the extracted URLs, instead of showing the
 context of each URL. Also toggle with `c` from within the viewer.
 .TP
+.B \-H, \-\-nohelp
+Start with header menu hidden.
+.TP
 .B \-d, \-\-dedupe
 Remove duplicated URLs from the list of URLs.
 .TP
@@ -68,9 +83,10 @@
 .TP
 .B \-r, \-\-run \<expression\>
 Execute \<expression\> in place of opening URL with a browser. Use {} in
-\<expression\> to substitute in the URL. Example:
+\<expression\> to substitute in the URL. Examples:
 
     $ urlscan --run 'echo {} | xclip -i' file.txt
+    $ urlscan --run 'tmux set buffer {}'
 .TP
 .B \-p, \-\-pipe
 Pipe the selected URL to the command specified by `--run`. This is preferred
@@ -99,6 +115,48 @@
 Alternately, you can pipe a message into urlscan using the '|' operator. This
 can be useful for applying a different flag (such as the '-d' or '-c' options).
 
+.SH KEYBINDINGS
+
+Run \fBurlscan \-g\fR to generate ~/.config/urlscan/config.json. All of the 
keys
+will be listed. You can either leave in place or delete any that will not be
+altered.
+
+To unset a binding, set it equal to "". For example: \fB"P": ""\fR
+
+The follow actions are supported:
+.TP
+\fBall_escape\fR \-\- toggle unescape all URLs (Default: \fBu\fR)
+.TP
+\fBall_shorten\fR \-\- toggle shorten all URLs (Default: \fBS\fR)
+.TP
+\fBbottom\fR \-\- move cursor to last item (Default: \fBG\fR)
+.TP
+\fBclear_screen\fR \-\- redraw screen (Default: \fBCtrl-l\fR)
+.TP
+\fBclipboard\fR \-\- copy highlighted URL to clipboard using xsel/xclip 
(Default: \fBC\fR)
+.TP
+\fBclipboard_pri\fR \-\- copy highlighted URL to primary selection using 
xsel/xclip (Default: \fBP\fR)
+.TP
+\fBcontext\fR \-\- show/hide context (Default: \fBc\fR)
+.TP
+\fBdown\fR \-\- cursor down (Default: \fBj\fR)
+.TP
+\fBhelp_menu\fR \-\- show/hide help menu (Default: \fBF1\fR)
+.TP
+\fBlink_handler\fR \-\- cycle link handling (webbrowser, xdg-open or custom) 
(Default: \fBl\fR)
+.TP
+\fBopen_url\fR \-\- open selected URL (Default: \fBspace\fR or \fBenter\fR)
+.TP
+\fBpalette\fR \-\- cycle through palettes (Default: \fBp\fR)
+.TP
+\fBquit\fR \-\- quit (Default: \fBq\fR or \fBQ\fR)
+.TP
+\fBshorten\fR \-\- toggle shorten highlighted URL (Default: \fBs\fR)
+.TP
+\fBtop\fR \-\- move to first list item (Default: \fBg\fR)
+.TP
+\fBup\fR \-\- cursor up (Default: \fBk\fR)
+
 .SH FILES
 
 $HOME/.config/urlscan/config.json


Reply via email to