SEBA-290 fix singularization of non-words

Change-Id: I27c14690a9309ee95d7a6292395b9cd3369f5a8e
diff --git a/VERSION b/VERSION
index 291d0de..2f1a5aa 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.16
+2.1.17
diff --git a/containers/chameleon/Dockerfile.chameleon b/containers/chameleon/Dockerfile.chameleon
index e10df7b..c0fefa3 100644
--- a/containers/chameleon/Dockerfile.chameleon
+++ b/containers/chameleon/Dockerfile.chameleon
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/chameleon
-FROM xosproject/xos-base:2.1.16
+FROM xosproject/xos-base:2.1.17
 
 # xos-base already has protoc and dependencies installed
 
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index 4999830..8b56edc 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-client
-FROM xosproject/xos-libraries:2.1.16
+FROM xosproject/xos-libraries:2.1.17
 
 # Install XOS client
 COPY xos/xos_client /tmp/xos_client
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index 2926dfa..d78140f 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM xosproject/xos-base:2.1.16
+FROM xosproject/xos-base:2.1.17
 
 # Add libraries
 COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index 2743e21..f354c1a 100644
--- a/containers/xos/Dockerfile.synchronizer-base
+++ b/containers/xos/Dockerfile.synchronizer-base
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-synchronizer-base
-FROM xosproject/xos-client:2.1.16
+FROM xosproject/xos-client:2.1.17
 
 COPY xos/synchronizers/new_base /opt/xos/synchronizers/new_base
 COPY xos/xos/logger.py /opt/xos/xos/logger.py
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index 23494e9..e009aea 100644
--- a/containers/xos/Dockerfile.xos-core
+++ b/containers/xos/Dockerfile.xos-core
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 # xosproject/xos-core
-FROM xosproject/xos-libraries:2.1.16
+FROM xosproject/xos-libraries:2.1.17
 
 # Install XOS
 ADD xos /opt/xos
diff --git a/lib/xos-genx/xos-genx-tests/target_test.py b/lib/xos-genx/xos-genx-tests/target_test.py
index dde39dd..1dab47c 100644
--- a/lib/xos-genx/xos-genx-tests/target_test.py
+++ b/lib/xos-genx/xos-genx-tests/target_test.py
@@ -77,7 +77,6 @@
       required int many = 1 [singular = "one"];
       // The following fields have automatically computed singulars
       required int sheep = 2;
-      required int radii = 2;
       required int slices = 2;
       required int networks = 2;
       required int omf_friendlies = 2;
@@ -94,7 +93,7 @@
         args.inputs = proto
         args.target = target
         output = XOSProcessor.process(args)
-        self.assertEqual("one,sheep,radius,slice,network,omf_friendly", output.lstrip().rstrip().rstrip(','))
+        self.assertEqual("one,sheep,slice,network,omf_friendly", output.lstrip().rstrip().rstrip(','))
 
     def test_pluralize(self):
         proto = \
@@ -104,7 +103,6 @@
       required int anecdote = 1 [plural = "data"];
       // The following fields have automatically computed plurals
       required int sheep = 2;
-      required int radius = 2;
       required int slice = 2;
       required int network = 2;
       required int omf_friendly = 2;
@@ -121,7 +119,7 @@
         args.inputs = proto
         args.target = target
         output = XOSProcessor.process(args)
-        self.assertEqual("data,sheep,radii,slices,networks,omf_friendlies", output.lstrip().rstrip().rstrip(','))
+        self.assertEqual("data,sheep,slices,networks,omf_friendlies", output.lstrip().rstrip().rstrip(','))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/lib/xos-genx/xos-genx-tests/test_jinja2_base.py b/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
index 0e7a2d4..4f26ac9 100644
--- a/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
+++ b/lib/xos-genx/xos-genx-tests/test_jinja2_base.py
@@ -16,7 +16,18 @@
 
 import unittest
 from xosgenx.jinja2_extensions.base import *
-from helpers import FakeArgs, XProtoTestHelpers
+
+
+# Several of the base functions require a Field object.
+def _field(name, singular=None, plural=None):
+    f = {}
+    f["name"] = name
+    f["options"] = {}
+    if singular:
+        f["options"]["singular"] = singular
+    if plural:
+        f["options"]["plural"] = plural
+    return f
 
 class Jinja2BaseTests(unittest.TestCase):
     def test_xproto_is_true(self):
@@ -29,6 +40,41 @@
         self.assertFalse(xproto_is_true(None))
         self.assertFalse(xproto_is_true("something else"))
 
+    def test_unquote(self):
+        self.assertEqual(xproto_unquote("foo"), "foo")
+        self.assertEqual(xproto_unquote('"foo"'), "foo")
+
+    def test_pluralize(self):
+        self.assertEqual(xproto_pluralize(_field("sheep")), "sheep")
+        self.assertEqual(xproto_pluralize(_field("slice")), "slices")
+        self.assertEqual(xproto_pluralize(_field("network")), "networks")
+        self.assertEqual(xproto_pluralize(_field("omf_friendly")), "omf_friendlies")
+        # invalid words, should usually return <word>-es
+        self.assertEqual(xproto_pluralize(_field("xxx")), "xxxes")
+        # if a field option is set, use that
+        self.assertEqual(xproto_pluralize(_field("sheep", plural="turtles")), "turtles")
+
+    def test_singularize(self):
+        self.assertEqual(xproto_singularize(_field("sheep")), "sheep")
+        self.assertEqual(xproto_singularize(_field("slices")), "slice")
+        self.assertEqual(xproto_singularize(_field("networks")), "network")
+        self.assertEqual(xproto_singularize(_field("omf_friendlies")), "omf_friendly")
+        # invalid words, return the original word
+        self.assertEqual(xproto_singularize(_field("xxx")), "xxx")
+        # if a field option is set, use that
+        self.assertEqual(xproto_pluralize(_field("sheep", plural="turtles")), "turtles")
+
+    def test_singularize_pluralize(self):
+        self.assertEqual(xproto_singularize_pluralize(_field("sheep")), "sheep")
+        self.assertEqual(xproto_singularize_pluralize(_field("slices")), "slices")
+        self.assertEqual(xproto_singularize_pluralize(_field("networks")), "networks")
+        self.assertEqual(xproto_singularize_pluralize(_field("omf_friendlies")), "omf_friendlies")
+        # invalid words, should usually return <word>-es
+        self.assertEqual(xproto_singularize_pluralize(_field("xxx")), "xxxes")
+        # if a field option is set, use that
+        self.assertEqual(xproto_singularize(_field("sheep", singular="turtle")), "turtle")
+
+
 if __name__ == '__main__':
     unittest.main()
 
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/base.py b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
index 702b554..3bfcd70 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/base.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
@@ -44,6 +44,9 @@
         singular = unquote(singular)
     except KeyError:
         singular = inflect_engine.singular_noun(field['name'])
+        if singular is False:
+            # singular_noun returns False on a noun it can't singularize
+            singular = field["name"]
 
     return singular
 
@@ -53,7 +56,12 @@
         plural = field['options']['plural']
         plural = unquote(plural)
     except KeyError:
-        plural = inflect_engine.plural_noun(inflect_engine.singular_noun(field['name']))
+        singular = inflect_engine.singular_noun(field['name'])
+        if singular is False:
+            # singular_noun returns False on a noun it can't singularize
+            singular = field["name"]
+
+        plural = inflect_engine.plural_noun(singular)
 
     return plural