posix: redo platform specific setup (now as base class)
diff --git a/serial/serialposix.py b/serial/serialposix.py
index 26fecbd..cc41a8d 100644
--- a/serial/serialposix.py
+++ b/serial/serialposix.py
@@ -24,224 +24,11 @@
 from serial.serialutil import *
 
 
-# try to detect the OS so that a device can be selected...
-# this code block should supply a device() and set_special_baudrate() function
-# for the platform
-plat = sys.platform.lower()
+class PlatformSpecificBase(object):
+    BAUDRATE_CONSTANTS = {}
 
-if   plat[:5] == 'linux':    # Linux (confirmed)
-    import array
-
-    def device(port):
-        return '/dev/ttyS%d' % port
-
-    TCGETS2 = 0x802C542A
-    TCSETS2 = 0x402C542B
-    BOTHER = 0o010000
-
-    def set_special_baudrate(port, baudrate):
-        # right size is 44 on x86_64, allow for some growth
-        buf = array.array('i', [0] * 64)
-
-        try:
-            # get serial_struct
-            fcntl.ioctl(port.fd, TCGETS2, buf)
-            # set custom speed
-            buf[2] &= ~termios.CBAUD
-            buf[2] |= BOTHER
-            buf[9] = buf[10] = baudrate
-
-            # set serial_struct
-            res = fcntl.ioctl(port.fd, TCSETS2, buf)
-        except IOError as e:
-            raise ValueError('Failed to set custom baud rate (%s): %s' % (baudrate, e))
-
-    baudrate_constants = {
-        0:       0o000000,  # hang up
-        50:      0o000001,
-        75:      0o000002,
-        110:     0o000003,
-        134:     0o000004,
-        150:     0o000005,
-        200:     0o000006,
-        300:     0o000007,
-        600:     0o000010,
-        1200:    0o000011,
-        1800:    0o000012,
-        2400:    0o000013,
-        4800:    0o000014,
-        9600:    0o000015,
-        19200:   0o000016,
-        38400:   0o000017,
-        57600:   0o010001,
-        115200:  0o010002,
-        230400:  0o010003,
-        460800:  0o010004,
-        500000:  0o010005,
-        576000:  0o010006,
-        921600:  0o010007,
-        1000000: 0o010010,
-        1152000: 0o010011,
-        1500000: 0o010012,
-        2000000: 0o010013,
-        2500000: 0o010014,
-        3000000: 0o010015,
-        3500000: 0o010016,
-        4000000: 0o010017
-    }
-
-    # RS485 ioctls
-    TIOCGRS485 = 0x542E
-    TIOCSRS485 = 0x542F
-    SER_RS485_ENABLED        = 0b00000001
-    SER_RS485_RTS_ON_SEND    = 0b00000010
-    SER_RS485_RTS_AFTER_SEND = 0b00000100
-    SER_RS485_RX_DURING_TX   = 0b00010000
-
-    def set_rs485_mode(port, rs485_settings):
-        buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding
-
-        try:
-            fcntl.ioctl(port.fd, TIOCGRS485, buf)
-            if rs485_settings is not None:
-                if rs485_settings.loopback:
-                    buf[0] |= SER_RS485_RX_DURING_TX
-                else:
-                    buf[0] &= ~SER_RS485_RX_DURING_TX
-                if rs485_settings.rts_level_for_tx:
-                    buf[0] |= SER_RS485_RTS_ON_SEND
-                else:
-                    buf[0] &= ~SER_RS485_RTS_ON_SEND
-                if rs485_settings.rts_level_for_rx:
-                    buf[0] |= SER_RS485_RTS_AFTER_SEND
-                else:
-                    buf[0] &= ~SER_RS485_RTS_AFTER_SEND
-                buf[1] = int(rs485_settings.delay_rts_before_send * 1000)
-                buf[2] = int(rs485_settings.delay_rts_after_send * 1000)
-            else:
-                buf[0] = 0  # clear SER_RS485_ENABLED
-            res = fcntl.ioctl(port.fd, TIOCSRS485, buf)
-        except IOError as e:
-            raise ValueError('Failed to set RS485 mode: %s' % (e,))
-
-
-elif plat == 'cygwin':       # cygwin/win32 (confirmed)
-
-    def device(port):
-        return '/dev/com%d' % (port + 1)
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {
-        128000: 0x01003,
-        256000: 0x01005,
-        500000: 0x01007,
-        576000: 0x01008,
-        921600: 0x01009,
-        1000000: 0x0100a,
-        1152000: 0x0100b,
-        1500000: 0x0100c,
-        2000000: 0x0100d,
-        2500000: 0x0100e,
-        3000000: 0x0100f
-    }
-
-elif plat[:7] == 'openbsd':    # OpenBSD
-
-    def device(port):
-        return '/dev/cua%02d' % port
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-elif plat[:3] == 'bsd' or  \
-    plat[:7] == 'freebsd':
-
-    def device(port):
-        return '/dev/cuad%d' % port
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-elif plat[:6] == 'darwin':   # OS X
-    import array
-
-    version = os.uname()[2].split('.')
-    # Tiger or above can support arbitrary serial speeds
-    if int(version[0]) >= 8:
-        def set_special_baudrate(port, baudrate):
-            # use IOKit-specific call to set up high speeds
-            buf = array.array('i', [baudrate])
-            IOSSIOSPEED = 0x80045402 #_IOW('T', 2, speed_t)
-            fcntl.ioctl(port.fd, IOSSIOSPEED, buf, 1)
-    else: # version < 8
-        def set_special_baudrate(port, baudrate):
-            raise ValueError("baud rate not supported")
-
-    def device(port):
-        return '/dev/cuad%d' % port
-
-    baudrate_constants = {}
-
-
-elif plat[:6] == 'netbsd':   # NetBSD 1.6 testing by Erk
-
-    def device(port):
-        return '/dev/dty%02d' % port
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-elif plat[:4] == 'irix':     # IRIX (partially tested)
-
-    def device(port):
-        return '/dev/ttyf%d' % (port+1) #XXX different device names depending on flow control
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-elif plat[:2] == 'hp':       # HP-UX (not tested)
-
-    def device(port):
-        return '/dev/tty%dp0' % (port+1)
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-elif plat[:5] == 'sunos':    # Solaris/SunOS (confirmed)
-
-    def device(port):
-        return '/dev/tty%c' % (ord('a')+port)
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-elif plat[:3] == 'aix':      # AIX
-
-    def device(port):
-        return '/dev/tty%d' % (port)
-
-    def set_special_baudrate(port, baudrate):
-        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
-
-    baudrate_constants = {}
-
-else:
-    # platform detection has failed...
-    sys.stderr.write("""\
+    def number_to_device(self, port_number):
+        sys.stderr.write("""\
 don't know how to number ttys on this system.
 ! Use an explicit path (eg /dev/ttyS1) or send this information to
 ! the author of this module:
@@ -255,15 +42,192 @@
 e.g. 'first serial port: /dev/ttyS0'
 and with a bit luck you can get this module running...
 """ % (sys.platform, os.name, VERSION))
-    # no exception, just continue with a brave attempt to build a device name
-    # even if the device name is not correct for the platform it has chances
-    # to work using a string with the real device name as port parameter.
-    def device(portum):
-        return '/dev/ttyS%d' % portnum
-    def set_special_baudrate(port, baudrate):
-        raise SerialException("sorry don't know how to handle non standard baud rate on this platform")
-    baudrate_constants = {}
-    #~ raise Exception, "this module does not run on this platform, sorry."
+        raise NotImplementedError('no number-to-device mapping defined on this platform')
+
+    def _set_special_baudrate(self, baudrate):
+        raise NotImplementedError('non-standard baudrates are not supported on this platform')
+
+    def _set_rs485_mode(self, rs485_settings):
+        raise NotImplementedError('RS485 not supported on this platform')
+
+# try to detect the OS so that a device can be selected...
+# this code block should supply a device() and set_special_baudrate() function
+# for the platform
+plat = sys.platform.lower()
+
+if   plat[:5] == 'linux':    # Linux (confirmed)
+    import array
+
+    # baudrate ioctls
+    TCGETS2 = 0x802C542A
+    TCSETS2 = 0x402C542B
+    BOTHER = 0o010000
+
+    # RS485 ioctls
+    TIOCGRS485 = 0x542E
+    TIOCSRS485 = 0x542F
+    SER_RS485_ENABLED        = 0b00000001
+    SER_RS485_RTS_ON_SEND    = 0b00000010
+    SER_RS485_RTS_AFTER_SEND = 0b00000100
+    SER_RS485_RX_DURING_TX   = 0b00010000
+
+
+    class PlatformSpecific(PlatformSpecificBase):
+        BAUDRATE_CONSTANTS = {
+            0:       0o000000,  # hang up
+            50:      0o000001,
+            75:      0o000002,
+            110:     0o000003,
+            134:     0o000004,
+            150:     0o000005,
+            200:     0o000006,
+            300:     0o000007,
+            600:     0o000010,
+            1200:    0o000011,
+            1800:    0o000012,
+            2400:    0o000013,
+            4800:    0o000014,
+            9600:    0o000015,
+            19200:   0o000016,
+            38400:   0o000017,
+            57600:   0o010001,
+            115200:  0o010002,
+            230400:  0o010003,
+            460800:  0o010004,
+            500000:  0o010005,
+            576000:  0o010006,
+            921600:  0o010007,
+            1000000: 0o010010,
+            1152000: 0o010011,
+            1500000: 0o010012,
+            2000000: 0o010013,
+            2500000: 0o010014,
+            3000000: 0o010015,
+            3500000: 0o010016,
+            4000000: 0o010017
+        }
+
+        def number_to_device(self, port_number):
+            return '/dev/ttyS%d' % (port_number,)
+
+        def _set_special_baudrate(self, baudrate):
+            # right size is 44 on x86_64, allow for some growth
+            buf = array.array('i', [0] * 64)
+            try:
+                # get serial_struct
+                fcntl.ioctl(self.fd, TCGETS2, buf)
+                # set custom speed
+                buf[2] &= ~termios.CBAUD
+                buf[2] |= BOTHER
+                buf[9] = buf[10] = baudrate
+
+                # set serial_struct
+                res = fcntl.ioctl(self.fd, TCSETS2, buf)
+            except IOError as e:
+                raise ValueError('Failed to set custom baud rate (%s): %s' % (baudrate, e))
+
+        def _set_rs485_mode(self, rs485_settings):
+            buf = array.array('i', [0] * 8) # flags, delaytx, delayrx, padding
+            try:
+                fcntl.ioctl(self.fd, TIOCGRS485, buf)
+                if rs485_settings is not None:
+                    if rs485_settings.loopback:
+                        buf[0] |= SER_RS485_RX_DURING_TX
+                    else:
+                        buf[0] &= ~SER_RS485_RX_DURING_TX
+                    if rs485_settings.rts_level_for_tx:
+                        buf[0] |= SER_RS485_RTS_ON_SEND
+                    else:
+                        buf[0] &= ~SER_RS485_RTS_ON_SEND
+                    if rs485_settings.rts_level_for_rx:
+                        buf[0] |= SER_RS485_RTS_AFTER_SEND
+                    else:
+                        buf[0] &= ~SER_RS485_RTS_AFTER_SEND
+                    buf[1] = int(rs485_settings.delay_rts_before_send * 1000)
+                    buf[2] = int(rs485_settings.delay_rts_after_send * 1000)
+                else:
+                    buf[0] = 0  # clear SER_RS485_ENABLED
+                res = fcntl.ioctl(self.fd, TIOCSRS485, buf)
+            except IOError as e:
+                raise ValueError('Failed to set RS485 mode: %s' % (e,))
+
+
+elif plat == 'cygwin':       # cygwin/win32 (confirmed)
+
+    class PlatformSpecific(PlatformSpecificBase):
+        BAUDRATE_CONSTANTS = {
+            128000: 0x01003,
+            256000: 0x01005,
+            500000: 0x01007,
+            576000: 0x01008,
+            921600: 0x01009,
+            1000000: 0x0100a,
+            1152000: 0x0100b,
+            1500000: 0x0100c,
+            2000000: 0x0100d,
+            2500000: 0x0100e,
+            3000000: 0x0100f
+        }
+
+        def number_to_device(self, port_number):
+            return '/dev/com%d' % (port_number + 1,)
+
+
+elif plat[:7] == 'openbsd':    # OpenBSD
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/cua%02d' % (port_number,)
+
+elif plat[:3] == 'bsd' or plat[:7] == 'freebsd':
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/cuad%d' % (port_number,)
+
+elif plat[:6] == 'darwin':   # OS X
+    import array
+    IOSSIOSPEED = 0x80045402 #_IOW('T', 2, speed_t)
+
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/cuad%d' % (port_number,)
+
+        osx_version = os.uname()[2].split('.')
+        # Tiger or above can support arbitrary serial speeds
+        if int(osx_version[0]) >= 8:
+            def _set_special_baudrate(self, baudrate):
+                # use IOKit-specific call to set up high speeds
+                buf = array.array('i', [baudrate])
+                fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1)
+
+
+elif plat[:6] == 'netbsd':   # NetBSD 1.6 testing by Erk
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/dty%02d' % (port_number,)
+
+elif plat[:4] == 'irix':     # IRIX (partially tested)
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/ttyf%d' % (port_number + 1,) #XXX different device names depending on flow control
+
+elif plat[:2] == 'hp':       # HP-UX (not tested)
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/tty%dp0' % (port_number + 1,)
+
+elif plat[:5] == 'sunos':    # Solaris/SunOS (confirmed)
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/tty%c' % (ord('a') + port_number,)
+
+elif plat[:3] == 'aix':      # AIX
+    class PlatformSpecific(PlatformSpecificBase):
+        def number_to_device(self, port_number):
+            return '/dev/tty%d' % (port_number,)
+
+else:
+    class PlatformSpecific(PlatformSpecificBase):
+        pass
 
 # whats up with "aix", "beos", ....
 # they should work, just need to know the device names.
@@ -271,42 +235,42 @@
 
 # load some constants for later use.
 # try to use values from termios, use defaults from linux otherwise
-TIOCMGET  = hasattr(termios, 'TIOCMGET') and termios.TIOCMGET or 0x5415
-TIOCMBIS  = hasattr(termios, 'TIOCMBIS') and termios.TIOCMBIS or 0x5416
-TIOCMBIC  = hasattr(termios, 'TIOCMBIC') and termios.TIOCMBIC or 0x5417
-TIOCMSET  = hasattr(termios, 'TIOCMSET') and termios.TIOCMSET or 0x5418
+TIOCMGET  = getattr(termios, 'TIOCMGET', 0x5415)
+TIOCMBIS  = getattr(termios, 'TIOCMBIS', 0x5416)
+TIOCMBIC  = getattr(termios, 'TIOCMBIC', 0x5417)
+TIOCMSET  = getattr(termios, 'TIOCMSET', 0x5418)
 
-#TIOCM_LE = hasattr(termios, 'TIOCM_LE') and termios.TIOCM_LE or 0x001
-TIOCM_DTR = hasattr(termios, 'TIOCM_DTR') and termios.TIOCM_DTR or 0x002
-TIOCM_RTS = hasattr(termios, 'TIOCM_RTS') and termios.TIOCM_RTS or 0x004
-#TIOCM_ST = hasattr(termios, 'TIOCM_ST') and termios.TIOCM_ST or 0x008
-#TIOCM_SR = hasattr(termios, 'TIOCM_SR') and termios.TIOCM_SR or 0x010
+#TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001)
+TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002)
+TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004)
+#TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008)
+#TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010)
 
-TIOCM_CTS = hasattr(termios, 'TIOCM_CTS') and termios.TIOCM_CTS or 0x020
-TIOCM_CAR = hasattr(termios, 'TIOCM_CAR') and termios.TIOCM_CAR or 0x040
-TIOCM_RNG = hasattr(termios, 'TIOCM_RNG') and termios.TIOCM_RNG or 0x080
-TIOCM_DSR = hasattr(termios, 'TIOCM_DSR') and termios.TIOCM_DSR or 0x100
-TIOCM_CD  = hasattr(termios, 'TIOCM_CD') and termios.TIOCM_CD or TIOCM_CAR
-TIOCM_RI  = hasattr(termios, 'TIOCM_RI') and termios.TIOCM_RI or TIOCM_RNG
-#TIOCM_OUT1 = hasattr(termios, 'TIOCM_OUT1') and termios.TIOCM_OUT1 or 0x2000
-#TIOCM_OUT2 = hasattr(termios, 'TIOCM_OUT2') and termios.TIOCM_OUT2 or 0x4000
+TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020)
+TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040)
+TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080)
+TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100)
+TIOCM_CD  = getattr(termios, 'TIOCM_CD', TIOCM_CAR)
+TIOCM_RI  = getattr(termios, 'TIOCM_RI', TIOCM_RNG)
+#TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000)
+#TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000)
 if hasattr(termios, 'TIOCINQ'):
     TIOCINQ = termios.TIOCINQ
 else:
-    TIOCINQ = hasattr(termios, 'FIONREAD') and termios.FIONREAD or 0x541B
-TIOCOUTQ   = hasattr(termios, 'TIOCOUTQ') and termios.TIOCOUTQ or 0x5411
+    TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
+TIOCOUTQ   = getattr(termios, 'TIOCOUTQ', 0x5411)
 
 TIOCM_zero_str = struct.pack('I', 0)
 TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
 TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
 
-TIOCSBRK  = hasattr(termios, 'TIOCSBRK') and termios.TIOCSBRK or 0x5427
-TIOCCBRK  = hasattr(termios, 'TIOCCBRK') and termios.TIOCCBRK or 0x5428
+TIOCSBRK  = getattr(termios, 'TIOCSBRK', 0x5427)
+TIOCCBRK  = getattr(termios, 'TIOCCBRK', 0x5428)
 
 CMSPAR = 0o10000000000 # Use "stick" (mark/space) parity
 
 
-class Serial(SerialBase, io.RawIOBase):
+class Serial(SerialBase, io.RawIOBase, PlatformSpecific):
     """\
     Serial port class POSIX implementation. Serial port configuration is 
     done with termios and fcntl. Runs on Linux and many other Un*x like
@@ -381,7 +345,7 @@
             ispeed = ospeed = getattr(termios, 'B%s' % (self._baudrate))
         except AttributeError:
             try:
-                ispeed = ospeed = baudrate_constants[self._baudrate]
+                ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate]
             except KeyError:
                 #~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
                 # may need custom baud rate, it isn't in our list.
@@ -456,10 +420,6 @@
                 cflag &= ~(termios.CNEW_RTSCTS)
         # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
 
-        # XXX linux only
-        if self._rs485_mode is not None:
-            set_rs485_mode(self, self._rs485_mode)
-
         # buffer
         # vmin "minimal number of characters to be read. 0 for non blocking"
         if vmin < 0 or vmin > 255:
@@ -475,7 +435,10 @@
 
         # apply custom baud rate, if any
         if custom_baud is not None:
-            set_special_baudrate(self, custom_baud)
+            self._set_special_baudrate(custom_baud)
+
+        if self._rs485_mode is not None:
+            self._set_rs485_mode(self._rs485_mode)
 
     def close(self):
         """Close port"""
@@ -486,7 +449,7 @@
             self._isOpen = False
 
     def makeDeviceName(self, port):
-        return device(port)
+        return self.number_to_device(port)
 
     #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -