list_ports_linux: cleanup
diff --git a/serial/tools/list_ports_linux.py b/serial/tools/list_ports_linux.py
index 11092b5..f7491e5 100644
--- a/serial/tools/list_ports_linux.py
+++ b/serial/tools/list_ports_linux.py
@@ -3,64 +3,47 @@
 # portable serial port access with python

 #

 # This is a module that gathers a list of serial ports including details on

-# GNU/Linux systems

+# GNU/Linux systems.

+# The comports function is expected to return an iterable that yields tuples of

+# 3 strings: port name, human readable description and a hardware ID.

 #

 # (C) 2011-2015 Chris Liechti <cliechti@gmx.net>

 #

 # SPDX-License-Identifier:    BSD-3-Clause

 

 import glob

-import sys

 import os

 import re

+import subprocess

+import sys

 

-try:

-    import subprocess

-except ImportError:

-    def popen(argv):

-        try:

-            si, so =  os.popen4(' '.join(argv))

-            return so.read().strip()

-        except:

-            raise IOError('lsusb failed')

-else:

-    def popen(argv):

-        try:

-            return subprocess.check_output(argv, stderr=subprocess.STDOUT).strip()

-        except:

-            raise IOError('lsusb failed')

+def read_command(argv):

+    """run a command and return its output"""

+    try:

+        return subprocess.check_output(argv, stderr=subprocess.STDOUT).strip().decode('ascii', 'replace')

+    except subprocess.SubprocessError as e:

+        raise IOError('command %r failed: %s' % (argv, e))

 

-

-# The comports function is expected to return an iterable that yields tuples of

-# 3 strings: port name, human readable description and a hardware ID.

-#

-# as currently no method is known to get the second two strings easily, they

-# are currently just identical to the port name.

-

-# try to detect the OS so that a device can be selected...

-plat = sys.platform.lower()

-

-def read_line(filename):

+def read_line(*args):

     """\

     Helper function to read a single line from a file.

+    One or more parameters are allowed, they are joined with os.path.join.

     Returns None on errors..

     """

     try:

-        f = open(filename)

-        line = f.readline().strip()

-        f.close()

+        with open(os.path.join(*args)) as f:

+            line = f.readline().strip()

         return line

     except IOError:

         return None

 

 def re_group(regexp, text):

     """search for regexp in text, return 1st group on match"""

-    if sys.version < '3':

-        m = re.search(regexp, text)

+    m = re.search(regexp, text)

+    if m:

+        return m.group(1)

     else:

-        # text is bytes-like

-        m = re.search(regexp, text.decode('ascii', 'replace'))

-    if m: return m.group(1)

+        return None

 

 

 # try to extract descriptions from sysfs. this was done by experimenting,

@@ -69,14 +52,14 @@
 def usb_sysfs_hw_string(sysfs_path):

     """given a path to a usb device in sysfs, return a string describing it"""

     bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-')

-    snr = read_line(sysfs_path+'/serial')

-    if snr:

+    snr = read_line(sysfs_path, 'serial')

+    if snr is not None:

         snr_txt = ' SNR=%s' % (snr,)

     else:

         snr_txt = ''

     return 'USB VID:PID=%s:%s%s' % (

-            read_line(sysfs_path+'/idVendor'),

-            read_line(sysfs_path+'/idProduct'),

+            read_line(sysfs_path, 'idVendor'),

+            read_line(sysfs_path, 'idProduct'),

             snr_txt

             )

 

@@ -84,8 +67,11 @@
     base = os.path.basename(os.path.realpath(sysfs_path))

     bus = base.split('-')[0]

     try:

-        dev = int(read_line(os.path.join(sysfs_path, 'devnum')))

-        desc = popen(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)])

+        dev = int(read_line(sysfs_path, 'devnum'))

+        desc = read_command(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)])

+    except IOError:

+        return base

+    else:

         # descriptions from device

         iManufacturer = re_group('iManufacturer\s+\w+ (.+)', desc)

         iProduct = re_group('iProduct\s+\w+ (.+)', desc)

@@ -95,8 +81,6 @@
         idProduct = re_group('idProduct\s+0x\w+ (.+)', desc)

         # create descriptions. prefer text from device, fall back to the others

         return '%s %s %s' % (iManufacturer or idVendor, iProduct or idProduct, iSerial)

-    except IOError:

-        return base

 

 def describe(device):

     """\

@@ -154,3 +138,4 @@
 if __name__ == '__main__':

     for port, desc, hwid in sorted(comports()):

         print("%s: %s [%s]" % (port, desc, hwid))

+