Repository: ManiacalLabs/BiblioPixel
Pull request #172 (2017-04-23 16:16:48)

Better validation of ChannelOrder.
* Move ChannelOrder into its own file.
* Better validation and parsing of user input for Channel orders.
* Needed for "dotfiles" (#112) and "hardware configuration" (#127).


Patch:
diff --git a/bibliopixel/drivers/APA102.py b/bibliopixel/drivers/APA102.py
index 63860a2..7d86617 100644
--- a/bibliopixel/drivers/APA102.py
+++ b/bibliopixel/drivers/APA102.py
@@ -1,4 +1,5 @@
-from . spi_driver_base import DriverSPIBase, ChannelOrder
+from . channel_order import ChannelOrder
+from . spi_driver_base import DriverSPIBase
 
 
 class DriverAPA102(DriverSPIBase):
diff --git a/bibliopixel/drivers/LPD8806.py b/bibliopixel/drivers/LPD8806.py
index f46decf..3330a43 100644
--- a/bibliopixel/drivers/LPD8806.py
+++ b/bibliopixel/drivers/LPD8806.py
@@ -1,6 +1,6 @@
 from .. import gamma
-
-from . spi_driver_base import DriverSPIBase, ChannelOrder
+from . channel_order import ChannelOrder
+from . spi_driver_base import DriverSPIBase
 
 
 class DriverLPD8806(DriverSPIBase):
diff --git a/bibliopixel/drivers/WS2801.py b/bibliopixel/drivers/WS2801.py
index 806b2ae..9be8bcf 100644
--- a/bibliopixel/drivers/WS2801.py
+++ b/bibliopixel/drivers/WS2801.py
@@ -1,4 +1,5 @@
-from . spi_driver_base import DriverSPIBase, ChannelOrder
+from . channel_order import ChannelOrder
+from . spi_driver_base import DriverSPIBase
 import os
 from .. import gamma
 
diff --git a/bibliopixel/drivers/__init__.py b/bibliopixel/drivers/__init__.py
index 118f51b..e69de29 100644
--- a/bibliopixel/drivers/__init__.py
+++ b/bibliopixel/drivers/__init__.py
@@ -1,1 +0,0 @@
-from . driver_base import ChannelOrder
diff --git a/bibliopixel/drivers/channel_order.py b/bibliopixel/drivers/channel_order.py
new file mode 100644
index 0000000..6a66eac
--- /dev/null
+++ b/bibliopixel/drivers/channel_order.py
@@ -0,0 +1,52 @@
+class ChannelOrder(object):
+
+    RGB = 0, 1, 2
+    RBG = 0, 2, 1
+    GRB = 1, 0, 2
+    GBR = 1, 2, 0
+    BRG = 2, 0, 1
+    BGR = 2, 1, 0
+
+    ORDERS = RGB, RBG, GRB, GBR, BRG, BGR
+
+    @staticmethod
+    def make(x):
+        orders = ChannelOrder.ORDERS
+        try:
+            length = len(x)
+        except:
+            # Must be an integer index.
+            if x < 0:
+                raise ValueError(
+                    'ChannelOrder index %s is not between 0 and 5' % x)
+            try:
+                return orders[x]
+            except IndexError:
+                raise ValueError(
+                    'ChannelOrder index %s is not between 0 and 5' % x)
+            except:
+                raise ValueError(
+                    'ChannelOrder index "%s" is not an integer' % x)
+
+        if length != 3:
+            raise ValueError('ChannelOrder "%s" does not have length 3' % x)
+
+        try:
+            s = x.lower()
+        except AttributeError:
+            s = tuple(x)
+        else:
+            try:
+                s = tuple('rgb'.index(i) for i in s)
+            except ValueError:
+                raise ValueError('ChannelOrder "%s" has non-rgb elements' % x)
+
+        if not all(0 <= x <= 2 for x in s):
+            raise ValueError(
+                'ChannelOrder %s has members not between 0 and 2.' % x)
+
+        try:
+            # Canonicalize and detect dupes at the same time.
+            return orders[orders.index(s)]
+        except:
+            raise ValueError('ChannelOrder %s has duplicate elements' % x)
diff --git a/bibliopixel/drivers/driver_base.py b/bibliopixel/drivers/driver_base.py
index d0fae4a..c382c04 100644
--- a/bibliopixel/drivers/driver_base.py
+++ b/bibliopixel/drivers/driver_base.py
@@ -1,19 +1,9 @@
+from . channel_order import ChannelOrder
 from .. import gamma as _gamma
 from .. import data_maker
 import threading, time
 
 
-class ChannelOrder:
-    RGB = 0, 1, 2
-    RBG = 0, 2, 1
-    GRB = 1, 0, 2
-    GBR = 1, 2, 0
-    BRG = 2, 0, 1
-    BGR = 2, 1, 0
-
-    ORDERS = RGB, RBG, GRB, GBR, BRG, BGR
-
-
 class DriverBase(object):
     """Base driver class to build other drivers from."""
 
diff --git a/bibliopixel/drivers/serial/driver.py b/bibliopixel/drivers/serial/driver.py
index 592e6db..6dae78e 100644
--- a/bibliopixel/drivers/serial/driver.py
+++ b/bibliopixel/drivers/serial/driver.py
@@ -2,7 +2,8 @@ import os, sys, time, traceback
 
 from . codes import CMDTYPE, LEDTYPE, SPIChipsets, BufferChipsets
 from . devices import Devices, serial
-from .. driver_base import DriverBase, ChannelOrder
+from .. channel_order import ChannelOrder
+from .. driver_base import DriverBase
 from ... import log, util
 from ... return_codes import RETURN_CODES, print_error, BiblioSerialError
 
diff --git a/bibliopixel/drivers/spi_driver_base.py b/bibliopixel/drivers/spi_driver_base.py
index 905c7cb..0f9eebf 100644
--- a/bibliopixel/drivers/spi_driver_base.py
+++ b/bibliopixel/drivers/spi_driver_base.py
@@ -1,5 +1,6 @@
 import os, sys, time
-from . driver_base import DriverBase, ChannelOrder
+from . channel_order import ChannelOrder
+from . driver_base import DriverBase
 from .. import log
 
 


Test patch:
diff --git a/test/channel_order_test.py b/test/channel_order_test.py
new file mode 100644
index 0000000..3235c5d
--- /dev/null
+++ b/test/channel_order_test.py
@@ -0,0 +1,37 @@
+import unittest
+from bibliopixel.drivers.channel_order import ChannelOrder
+
+make = ChannelOrder.make
+
+
+class ChannelOrderTest(unittest.TestCase):
+    def test_all(self):
+        self.assertIs(make('rgb'), ChannelOrder.RGB)
+        self.assertIs(make(0), ChannelOrder.RGB)
+        self.assertIs(make([0, 1, 2]), ChannelOrder.RGB)
+
+        self.assertIs(make('gRB'), ChannelOrder.GRB)
+        self.assertIs(make(2), ChannelOrder.GRB)
+        self.assertIs(make((1, 0, 2)), ChannelOrder.GRB)
+
+        self.assertIs(make('BGR'), ChannelOrder.BGR)
+        self.assertIs(make(5), ChannelOrder.BGR)
+        self.assertIs(make((2, 1, 0)), ChannelOrder.BGR)
+
+    def test_index(self):
+        with self.assertRaisesRegex(ValueError, 'index .+ is not between'):
+            make(-1)
+        map(make, range(6))
+        with self.assertRaisesRegex(ValueError, 'index .+ is not between'):
+            make(6)
+
+        with self.assertRaisesRegex(ValueError, 'index .+ is not an integer'):
+            make(2.3)
+
+    def test_fail(self):
+        with self.assertRaisesRegex(ValueError, 'has non-rgb elements'):
+            make('rxb')
+        with self.assertRaisesRegex(ValueError, 'has members not between'):
+            make([0, -1, 1])
+        with self.assertRaisesRegex(ValueError, 'has duplicate elements'):
+            make([0, 2, 2])