diff options
author | Sylvain Viollon <thefunny@gmail.com> | 2018-10-18 15:42:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-18 15:42:00 +0200 |
commit | 983fe5e9d1380605ff978302c66c6dc03e48a7a8 (patch) | |
tree | d3f598e591009a638c62c968ecf52edcf2e85c10 | |
parent | ae338be03256136e684b4d5fee2b3b9dccaa6f2b (diff) | |
parent | 13a729411191c94c4384f33c5daed1f408b352fc (diff) | |
download | zope-i18nmessageid-983fe5e9d1380605ff978302c66c6dc03e48a7a8.tar.gz |
Merge pull request #13 from minddistrict/CB-556-FR-translation-with-C
Add support for pluralization to the message id
-rw-r--r-- | CHANGES.rst | 7 | ||||
-rw-r--r-- | docs/narr.rst | 9 | ||||
-rw-r--r-- | setup.py | 5 | ||||
-rw-r--r-- | src/zope/i18nmessageid/_zope_i18nmessageid_message.c | 232 | ||||
-rw-r--r-- | src/zope/i18nmessageid/message.py | 66 | ||||
-rw-r--r-- | src/zope/i18nmessageid/tests.py | 165 |
6 files changed, 329 insertions, 155 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 29a7005..1721aa0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,13 +4,16 @@ Changes 4.3 (unreleased) ---------------- -- Nothing changed yet. +- Add attributes to support pluralization on a Message and update the + MessageFactory accordingly. 4.2 (2018-10-05) ---------------- -- Fix the possibility of a rare crash in the C extension when deallocating items. See `#7 <https://github.com/zopefoundation/zope.i18nmessageid/issues/7>`_. +- Fix the possibility of a rare crash in the C extension when + deallocating items. See `issue 7 + <https://github.com/zopefoundation/zope.i18nmessageid/issues/7>`_. - Drop support for Python 3.3. diff --git a/docs/narr.rst b/docs/narr.rst index e48c53a..5982880 100644 --- a/docs/narr.rst +++ b/docs/narr.rst @@ -54,7 +54,7 @@ exports an already-created factory for that domain: >>> foo = _z_('foo') >>> foo.domain 'zope' - + Example Usage ------------- @@ -135,14 +135,17 @@ Last but not least, messages are reduceable for pickling: >>> args == (u'robot-message', ... 'futurama', ... u'${name} is a robot.', - ... {u'name': u'Bender'}) + ... {u'name': u'Bender'}, + ... None, + ... None, + ... None) True >>> fembot = Message(u'fembot') >>> callable, args = fembot.__reduce__() >>> callable is Message True - >>> args == (u'fembot', None, None, None) + >>> args == (u'fembot', None, None, None, None, None, None) True Pickling and unpickling works, which means we can store message IDs in @@ -58,7 +58,8 @@ if not is_pypy and not is_jython: def read(*rnames): - return open(os.path.join(os.path.dirname(__file__), *rnames)).read() + with open(os.path.join(os.path.dirname(__file__), *rnames)) as stream: + return stream.read() class optional_build_ext(build_ext): @@ -135,7 +136,7 @@ setup( packages=find_packages('src'), package_dir={'': 'src'}, namespace_packages=['zope'], - install_requires=['setuptools'], + install_requires=['setuptools', 'six'], include_package_data=True, test_suite='zope.i18nmessageid.tests.test_suite', zip_safe=False, diff --git a/src/zope/i18nmessageid/_zope_i18nmessageid_message.c b/src/zope/i18nmessageid/_zope_i18nmessageid_message.c index 2fd30ea..c0107e1 100644 --- a/src/zope/i18nmessageid/_zope_i18nmessageid_message.c +++ b/src/zope/i18nmessageid/_zope_i18nmessageid_message.c @@ -22,7 +22,7 @@ #ifndef PyVarObject_HEAD_INIT #define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, + PyObject_HEAD_INIT(type) size, #endif #if PY_MAJOR_VERSION >= 3 @@ -35,24 +35,24 @@ Python 2.4 and borrowed from there */ #ifndef Py_CLEAR -#define Py_CLEAR(op) \ - do { \ - if (op) { \ - PyObject *tmp = (op); \ - (op) = NULL; \ - Py_DECREF(tmp); \ - } \ +#define Py_CLEAR(op) \ + do { \ + if (op) { \ + PyObject *tmp = (op); \ + (op) = NULL; \ + Py_DECREF(tmp); \ + } \ } while (0) #endif #ifndef Py_VISIT -#define Py_VISIT(op) \ - do { \ - if (op) { \ - int vret = visit((op), arg); \ - if (vret) \ - return vret; \ - } \ +#define Py_VISIT(op) \ + do { \ + if (op) { \ + int vret = visit((op), arg); \ + if (vret) \ + return vret; \ + } \ } while (0) #endif @@ -63,6 +63,9 @@ typedef struct { PyObject *domain; PyObject *default_; PyObject *mapping; + PyObject *value_plural; + PyObject *default_plural; + PyObject *number; } Message; static PyTypeObject MessageType; @@ -70,56 +73,89 @@ static PyTypeObject MessageType; static PyObject * Message_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - static char *kwlist[] = {"value", "domain", "default", "mapping", NULL}; + static char *kwlist[] = {"value", "domain", "default", "mapping", + "msgid_plural", "default_plural", "number", NULL}; PyObject *value, *domain=NULL, *default_=NULL, *mapping=NULL, *s; + PyObject *value_plural=NULL, *default_plural=NULL, *number=NULL; Message *self; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist, - &value, &domain, &default_, &mapping)) - return NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOOOOO", kwlist, + &value, &domain, &default_, &mapping, + &value_plural, &default_plural, &number)) + return NULL; + + if (number != NULL && Py_None != number) { +#if PY_MAJOR_VERSION >= 3 + if (!(PyLong_Check(number) || PyFloat_Check(number))) { +#else + if (!(PyLong_Check(number) || PyInt_Check(number) || PyFloat_Check(number))) { +#endif + PyErr_SetString(PyExc_TypeError, + "`number` should be an integer or a float"); + return NULL; + } + } args = Py_BuildValue("(O)", value); if (args == NULL) return NULL; - s = PyUnicode_Type.tp_new(type, args, NULL); + s = PyUnicode_Type.tp_new(type, args, NULL); Py_DECREF(args); if (s == NULL) return NULL; - if (! PyObject_TypeCheck(s, &MessageType)) - { - PyErr_SetString(PyExc_TypeError, - "unicode.__new__ didn't return a Message"); - Py_DECREF(s); - return NULL; - } + if (!PyObject_TypeCheck(s, &MessageType)) { + PyErr_SetString(PyExc_TypeError, "unicode.__new__ didn't return a Message"); + Py_DECREF(s); + return NULL; + } self = (Message*)s; - if (PyObject_TypeCheck(value, &MessageType)) - { - self->domain = ((Message *)value)->domain; - self->default_ = ((Message *)value)->default_; - self->mapping = ((Message *)value)->mapping; - } - else - { - self->domain = self->default_ = self->mapping = NULL; - } + if (PyObject_TypeCheck(value, &MessageType)) { + /* value is a Message so we copy it and use it as base */ + self->domain = ((Message *)value)->domain; + self->default_ = ((Message *)value)->default_; + self->mapping = ((Message *)value)->mapping; + self->value_plural = ((Message *)value)->value_plural; + self->default_plural = ((Message *)value)->default_plural; + self->number = ((Message *)value)->number; + } + else { + self->domain = NULL; + self->default_ = NULL; + self->mapping = NULL; + self->value_plural = NULL; + self->default_plural = NULL; + self->number = NULL; + } if (domain != NULL) self->domain = domain; - + if (default_ != NULL) self->default_ = default_; if (mapping != NULL) self->mapping = mapping; + if (value_plural != NULL) + self->value_plural = value_plural; + + if (default_plural != NULL) + self->default_plural = default_plural; + + if (number != NULL) { + self->number = number; + } + Py_XINCREF(self->mapping); Py_XINCREF(self->default_); Py_XINCREF(self->domain); + Py_XINCREF(self->value_plural); + Py_XINCREF(self->default_plural); + Py_XINCREF(self->number); return (PyObject *)self; } @@ -132,7 +168,10 @@ static PyMemberDef Message_members[] = { { "domain", T_OBJECT, offsetof(Message, domain), READONLY }, { "default", T_OBJECT, offsetof(Message, default_), READONLY }, { "mapping", T_OBJECT, offsetof(Message, mapping), READONLY }, - {NULL} /* Sentinel */ + { "msgid_plural", T_OBJECT, offsetof(Message, value_plural), READONLY }, + { "default_plural", T_OBJECT, offsetof(Message, default_plural), READONLY }, + { "number", T_OBJECT, offsetof(Message, number), READONLY }, + {NULL} /* Sentinel */ }; static int @@ -141,6 +180,9 @@ Message_traverse(Message *self, visitproc visit, void *arg) Py_VISIT(self->domain); Py_VISIT(self->default_); Py_VISIT(self->mapping); + Py_VISIT(self->value_plural); + Py_VISIT(self->default_plural); + Py_VISIT(self->number); return 0; } @@ -150,6 +192,9 @@ Message_clear(Message *self) Py_CLEAR(self->domain); Py_CLEAR(self->default_); Py_CLEAR(self->mapping); + Py_CLEAR(self->value_plural); + Py_CLEAR(self->default_plural); + Py_CLEAR(self->number); return 0; } @@ -168,11 +213,14 @@ Message_reduce(Message *self) value = PyObject_CallFunctionObjArgs((PyObject *)&PyUnicode_Type, self, NULL); if (value == NULL) return NULL; - result = Py_BuildValue("(O(OOOO))", Py_TYPE(&(self->base)), - value, - self->domain ? self->domain : Py_None, - self->default_ ? self->default_ : Py_None, - self->mapping ? self->mapping : Py_None); + result = Py_BuildValue("(O(OOOOOOO))", Py_TYPE(&(self->base)), + value, + self->domain ? self->domain : Py_None, + self->default_ ? self->default_ : Py_None, + self->mapping ? self->mapping : Py_None, + self->value_plural ? self->value_plural : Py_None, + self->default_plural ? self->default_plural : Py_None, + self->number ? self->number : Py_None); Py_DECREF(value); return result; } @@ -184,7 +232,7 @@ static PyMethodDef Message_methods[] = { }; -static char MessageType__doc__[] = +static char MessageType__doc__[] = "Message\n" "\n" "This is a string used as a message. It has a domain attribute that is\n" @@ -195,49 +243,49 @@ static char MessageType__doc__[] = static PyTypeObject MessageType = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "zope.i18nmessageid.message." + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "zope.i18nmessageid.message." "Message", - /* tp_basicsize */ sizeof(Message), - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)&Message_dealloc, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)0, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT - | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_HAVE_GC, - /* tp_doc */ MessageType__doc__, - /* tp_traverse */ (traverseproc)Message_traverse, - /* tp_clear */ (inquiry)Message_clear, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ Message_methods, - /* tp_members */ Message_members, - /* tp_getset */ 0, - /* tp_base */ 0, - /* tp_dict */ 0, /* internal use */ - /* tp_descr_get */ (descrgetfunc)0, - /* tp_descr_set */ (descrsetfunc)0, - /* tp_dictoffset */ 0, - /* tp_init */ (initproc)0, - /* tp_alloc */ (allocfunc)0, - /* tp_new */ (newfunc)Message_new, - /* tp_free */ 0, /* Low-level free-mem routine */ - /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ + /* tp_basicsize */ sizeof(Message), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)&Message_dealloc, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_HAVE_GC, + /* tp_doc */ MessageType__doc__, + /* tp_traverse */ (traverseproc)Message_traverse, + /* tp_clear */ (inquiry)Message_clear, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ Message_methods, + /* tp_members */ Message_members, + /* tp_getset */ 0, + /* tp_base */ 0, + /* tp_dict */ 0, /* internal use */ + /* tp_descr_get */ (descrgetfunc)0, + /* tp_descr_set */ (descrsetfunc)0, + /* tp_dictoffset */ 0, + /* tp_init */ (initproc)0, + /* tp_alloc */ (allocfunc)0, + /* tp_new */ (newfunc)Message_new, + /* tp_free */ 0, /* Low-level free-mem routine */ + /* tp_is_gc */ (inquiry)0, /* For PyObject_IS_GC */ }; /* End of code for Message objects */ @@ -252,7 +300,7 @@ static struct PyMethodDef _zope_i18nmessageid_message_methods[] = { static char _zope_i18nmessageid_message_module_name[] = "_zope_i18nmessageid_message"; -static char _zope_i18nmessageid_message_module_documentation[] = +static char _zope_i18nmessageid_message_module_documentation[] = "I18n Messages"; #if PY_MAJOR_VERSION >= 3 @@ -269,7 +317,7 @@ static char _zope_i18nmessageid_message_module_documentation[] = }; #endif -#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif @@ -285,7 +333,7 @@ PyMODINIT_FUNC MessageType.tp_base = &PyUnicode_Type; if (PyType_Ready(&MessageType) < 0) return MOD_ERROR_VAL; - + /* Create the module and add the functions */ #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&moduledef); @@ -294,10 +342,10 @@ PyMODINIT_FUNC _zope_i18nmessageid_message_methods, _zope_i18nmessageid_message_module_documentation); #endif - + if (m == NULL) return MOD_ERROR_VAL; - + /* Add types: */ if (PyModule_AddObject(m, "Message", (PyObject *)&MessageType) < 0) return MOD_ERROR_VAL; diff --git a/src/zope/i18nmessageid/message.py b/src/zope/i18nmessageid/message.py index 0e62408..4bfcb19 100644 --- a/src/zope/i18nmessageid/message.py +++ b/src/zope/i18nmessageid/message.py @@ -14,14 +14,12 @@ """I18n Messages and factories. """ __docformat__ = "reStructuredText" +_marker = object() -try: - unicode -except NameError: #pragma NO COVER Python3 - unicode = str +import six -class Message(unicode): +class Message(six.text_type): """Message (Python implementation) This is a string used as a message. It has a domain attribute that is @@ -31,18 +29,45 @@ class Message(unicode): message id itself implicitly serves as the default text. """ - __slots__ = ('domain', 'default', 'mapping', '_readonly') + __slots__ = ( + 'domain', 'default', 'mapping', '_readonly', + 'msgid_plural', 'default_plural', 'number') - def __new__(cls, ustr, domain=None, default=None, mapping=None): - self = unicode.__new__(cls, ustr) + def __new__(cls, ustr, domain=_marker, default=_marker, mapping=_marker, + msgid_plural=_marker, default_plural=_marker, number=_marker): + self = six.text_type.__new__(cls, ustr) if isinstance(ustr, self.__class__): - domain = ustr.domain and ustr.domain[:] or domain - default = ustr.default and ustr.default[:] or default - mapping = ustr.mapping and ustr.mapping.copy() or mapping - ustr = unicode(ustr) + domain = ustr.domain[:] if domain is _marker else domain + default = ustr.default[:] if default is _marker else default + mapping = ustr.mapping.copy() if mapping is _marker else mapping + msgid_plural = ( + ustr.msgid_plural[:] if msgid_plural is _marker else + msgid_plural) + default_plural = ( + ustr.default_plural[:] if default_plural is _marker else + default_plural) + number = ustr.number if number is _marker else number + ustr = six.text_type(ustr) + else: + domain = None if domain is _marker else domain + default = None if default is _marker else default + mapping = None if mapping is _marker else mapping + msgid_plural = None if msgid_plural is _marker else msgid_plural + default_plural = (None if default_plural is _marker else + default_plural) + number = None if number is _marker else number + self.domain = domain self.default = default self.mapping = mapping + self.msgid_plural = msgid_plural + self.default_plural = default_plural + + if number is not None and not isinstance( + number, six.integer_types + (float,)): + raise TypeError('`number` should be an integer or a float') + + self.number = number self._readonly = True return self @@ -54,27 +79,34 @@ class Message(unicode): if getattr(self, '_readonly', False): raise TypeError('readonly attribute') else: - return unicode.__setattr__(self, key, value) + return six.text_type.__setattr__(self, key, value) def __getstate__(self): - return unicode(self), self.domain, self.default, self.mapping + return ( + six.text_type(self), self.domain, self.default, self.mapping, + self.msgid_plural, self.default_plural, self.number) def __reduce__(self): return self.__class__, self.__getstate__() + # Name the fallback Python implementation to make it easier to test. pyMessage = Message + try: from ._zope_i18nmessageid_message import Message -except ImportError: # pragma: no cover +except ImportError: # pragma: no cover pass + class MessageFactory(object): """Factory for creating i18n messages.""" def __init__(self, domain): self._domain = domain - def __call__(self, ustr, default=None, mapping=None): - return Message(ustr, self._domain, default, mapping) + def __call__(self, ustr, default=None, mapping=None, + msgid_plural=None, default_plural=None, number=None): + return Message(ustr, self._domain, default, mapping, + msgid_plural, default_plural, number) diff --git a/src/zope/i18nmessageid/tests.py b/src/zope/i18nmessageid/tests.py index 0e8f6b4..56074c1 100644 --- a/src/zope/i18nmessageid/tests.py +++ b/src/zope/i18nmessageid/tests.py @@ -15,12 +15,12 @@ """ import sys import unittest - from zope.i18nmessageid import message as messageid + class PyMessageTests(unittest.TestCase): - _TEST_REAOONLY = True + _TEST_READONLY = True def _getTargetClass(self): return messageid.pyMessage @@ -28,97 +28,178 @@ class PyMessageTests(unittest.TestCase): def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_ctor_defaults(self): + def test_defaults(self): message = self._makeOne('testing') self.assertEqual(message, 'testing') self.assertEqual(message.domain, None) self.assertEqual(message.default, None) self.assertEqual(message.mapping, None) - if self._TEST_REAOONLY: + self.assertEqual(message.msgid_plural, None) + self.assertEqual(message.default_plural, None) + self.assertEqual(message.number, None) + if self._TEST_READONLY: self.assertTrue(message._readonly) - def test_ctor_explicit(self): + def test_values(self): mapping = {'key': 'value'} - message = self._makeOne('testing', 'domain', 'default', mapping) + message = self._makeOne( + 'testing', 'domain', 'default', mapping, + msgid_plural='testings', default_plural="defaults", number=2) self.assertEqual(message, 'testing') self.assertEqual(message.domain, 'domain') self.assertEqual(message.default, 'default') self.assertEqual(message.mapping, mapping) - if self._TEST_REAOONLY: + self.assertEqual(message.msgid_plural, 'testings') + self.assertEqual(message.default_plural, 'defaults') + self.assertEqual(message.number, 2) + if self._TEST_READONLY: self.assertTrue(message._readonly) - def test_ctor_copy(self): + def test_values_without_defaults(self): mapping = {'key': 'value'} - source = self._makeOne('testing', 'domain', 'default', mapping) - message = self._makeOne(source) + message = self._makeOne( + 'testing', 'domain', mapping=mapping, + msgid_plural='testings', number=2) + self.assertEqual(message, 'testing') + self.assertEqual(message.domain, 'domain') + self.assertEqual(message.default, None) + self.assertEqual(message.mapping, mapping) + self.assertEqual(message.msgid_plural, 'testings') + self.assertEqual(message.default_plural, None) + self.assertEqual(message.number, 2) + if self._TEST_READONLY: + self.assertTrue(message._readonly) + + def test_values_with_float_for_number(self): + mapping = {'key': 'value'} + message = self._makeOne( + 'testing', 'domain', 'default', mapping, + msgid_plural='testings', default_plural="defaults", number=2.2) self.assertEqual(message, 'testing') self.assertEqual(message.domain, 'domain') self.assertEqual(message.default, 'default') self.assertEqual(message.mapping, mapping) - if self._TEST_REAOONLY: + self.assertEqual(message.msgid_plural, 'testings') + self.assertEqual(message.default_plural, 'defaults') + self.assertEqual(message.number, 2.2) + if self._TEST_READONLY: self.assertTrue(message._readonly) - def test_ctor_copy_w_overrides(self): + def test_values_with_zero(self): mapping = {'key': 'value'} - source = self._makeOne('testing') - message = self._makeOne(source, 'domain', 'default', mapping) + message = self._makeOne( + 'testing', 'domain', 'default', mapping, + msgid_plural='testings', default_plural="defaults", number=0) self.assertEqual(message, 'testing') self.assertEqual(message.domain, 'domain') self.assertEqual(message.default, 'default') self.assertEqual(message.mapping, mapping) - if self._TEST_REAOONLY: + self.assertEqual(message.msgid_plural, 'testings') + self.assertEqual(message.default_plural, 'defaults') + self.assertEqual(message.number, 0) + if self._TEST_READONLY: + self.assertTrue(message._readonly) + + def test_copy(self): + mapping = {'key': 'value'} + source = self._makeOne( + 'testing', 'domain', 'default', mapping, + msgid_plural='testings', default_plural="defaults", number=0) + message = self._makeOne(source) + self.assertEqual(message, 'testing') + self.assertEqual(message.domain, 'domain') + self.assertEqual(message.default, 'default') + self.assertEqual(message.mapping, mapping) + self.assertEqual(message.msgid_plural, 'testings') + self.assertEqual(message.default_plural, 'defaults') + self.assertEqual(message.number, 0) + if self._TEST_READONLY: + self.assertTrue(message._readonly) + + def test_copy_with_overrides(self): + mapping = {'key': 'value'} + source = self._makeOne( + 'testing', 'domain', default='other', mapping=mapping, + msgid_plural='workings', default_plural='others', number=3) + message = self._makeOne( + source, mapping=None, msgid_plural='override', number=0) + self.assertEqual(message, 'testing') + self.assertEqual(message.domain, 'domain') + self.assertEqual(message.default, 'other') + self.assertEqual(message.mapping, None) + self.assertEqual(message.msgid_plural, 'override') + self.assertEqual(message.default_plural, 'others') + self.assertEqual(message.number, 0) + if self._TEST_READONLY: self.assertTrue(message._readonly) def test_domain_immutable(self): message = self._makeOne('testing') - def _try(): + with self.assertRaises((TypeError, AttributeError)): message.domain = 'domain' - # C version raises AttributeError, Python version TypeError - self.assertRaises((TypeError, AttributeError), _try) def test_default_immutable(self): message = self._makeOne('testing') - def _try(): + with self.assertRaises((TypeError, AttributeError)): message.default = 'default' - # C version raises AttributeError, Python version TypeError - self.assertRaises((TypeError, AttributeError), _try) def test_mapping_immutable(self): mapping = {'key': 'value'} message = self._makeOne('testing') - def _try(): + with self.assertRaises((TypeError, AttributeError)): message.mapping = mapping - # C version raises AttributeError, Python version TypeError - self.assertRaises((TypeError, AttributeError), _try) + + def test_msgid_plural_immutable(self): + message = self._makeOne('testing') + with self.assertRaises((TypeError, AttributeError)): + message.msgid_plural = 'bar' + + def test_default_plural_immutable(self): + message = self._makeOne('testing') + with self.assertRaises((TypeError, AttributeError)): + message.default_plural = 'bar' + + def test_number_immutable(self): + message = self._makeOne('testing') + with self.assertRaises((TypeError, AttributeError)): + message.number = 23 def test_unknown_immutable(self): message = self._makeOne('testing') - def _try(): + with self.assertRaises((TypeError, AttributeError)): message.unknown = 'unknown' - # C version raises AttributeError, Python version TypeError - self.assertRaises((TypeError, AttributeError), _try) def test___reduce__(self): mapping = {'key': 'value'} source = self._makeOne('testing') - message = self._makeOne(source, 'domain', 'default', mapping) + message = self._makeOne( + source, 'domain', 'default', mapping, + msgid_plural='testings', default_plural="defaults", number=2) klass, state = message.__reduce__() self.assertTrue(klass is self._getTargetClass()) - self.assertEqual(state, ('testing', 'domain', 'default', mapping)) + self.assertEqual( + state, + ('testing', 'domain', 'default', {'key': 'value'}, + 'testings', 'defaults', 2)) def test_non_unicode_default(self): message = self._makeOne(u'str', default=123) self.assertEqual(message.default, 123) -@unittest.skipIf(messageid.Message is messageid.pyMessage, - "Duplicate tests") + def test_non_numeric_number(self): + with self.assertRaises((TypeError, AttributeError)): + self._makeOne(u'str', default=123, number="one") + + +@unittest.skipIf(messageid.Message is messageid.pyMessage, "Duplicate tests") class MessageTests(PyMessageTests): - _TEST_REAOONLY = False + _TEST_READONLY = False def _getTargetClass(self): return messageid.Message + @unittest.skipIf('java' in sys.platform or hasattr(sys, 'pypy_version_info'), "We don't expect the C implementation here") class OptimizationTests(unittest.TestCase): @@ -126,35 +207,41 @@ class OptimizationTests(unittest.TestCase): def test_optimizations_available(self): self.assertIsNot(messageid.Message, messageid.pyMessage) + class MessageFactoryTests(unittest.TestCase): def _getTargetClass(self): - from zope.i18nmessageid.message import MessageFactory - return MessageFactory + return messageid.MessageFactory def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test___call___defaults(self): - from zope.i18nmessageid.message import Message factory = self._makeOne('domain') message = factory('testing') - self.assertTrue(isinstance(message, Message)) + self.assertTrue(isinstance(message, messageid.Message)) self.assertEqual(message, 'testing') self.assertEqual(message.domain, 'domain') self.assertEqual(message.default, None) self.assertEqual(message.mapping, None) + self.assertEqual(message.msgid_plural, None) + self.assertEqual(message.default_plural, None) + self.assertEqual(message.number, None) def test___call___explicit(self): - from zope.i18nmessageid.message import Message mapping = {'key': 'value'} factory = self._makeOne('domain') - message = factory('testing', 'default', mapping) - self.assertTrue(isinstance(message, Message)) + message = factory( + 'testing', 'default', mapping, + msgid_plural='testings', default_plural="defaults", number=2) + self.assertTrue(isinstance(message, messageid.Message)) self.assertEqual(message, 'testing') self.assertEqual(message.domain, 'domain') self.assertEqual(message.default, 'default') self.assertEqual(message.mapping, mapping) + self.assertEqual(message.msgid_plural, 'testings') + self.assertEqual(message.default_plural, 'defaults') + self.assertEqual(message.number, 2) def test_suite(): |