[Zope3-checkins] SVN: Zope3/branches/philikon-messages-as-rocks/s Implement TurningMessageIDsIntoRocks.

Philipp von Weitershausen philikon at philikon.de
Sun Sep 19 04:33:49 EDT 2004


Log message for revision 27638:
  Implement TurningMessageIDsIntoRocks.
  
  Two things of that proposal were modified during the implementation:
  - message ids are now simply called Messages (first, because of simplicity,
    2nd because of differentiation to the old message ids)
  - the % operator for filling the message id mapping was not realized
    because it breaks the rule that messages behave like (unicode) strings
    in all circumstances.
  
  This is being checked into a branch because Jim and I couldn't figure out
  what is wrong with the __reduce__ method implementation (it breaks with a bus
  error). Once that is resolved we'll merge.
  


Changed:
  U   Zope3/branches/philikon-messages-as-rocks/setup.py
  D   Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt
  _U  Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/
  U   Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py
  A   Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c
  A   Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py
  A   Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/messages.txt
  U   Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py


-=-
Modified: Zope3/branches/philikon-messages-as-rocks/setup.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/setup.py	2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/setup.py	2004-09-19 08:33:49 UTC (rev 27638)
@@ -256,6 +256,9 @@
                  "src/zope/proxy/_zope_proxy_proxy.c",
                  ]),
     
+    Extension("zope.i18nmessageid._zope_i18nmessageid_message",
+              ["src/zope/i18nmessageid/_zope_i18nmessageid_message.c"]),
+
     ]
 
 # We're using the module docstring as the distutils descriptions.

Deleted: Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt	2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18n/message_id.txt	2004-09-19 08:33:49 UTC (rev 27638)
@@ -1,49 +0,0 @@
-=================
-Translatable Text
-=================
-
-Rationale
----------
-
-To translate any text, we must be able to discover the source
-domain of the text. A source domain is an identifier that identifies a
-project that produces program source strings. Source strings include
-literals in python programs, text in templates, and some text in XML
-data. The project implies a source language and an application
-context.
-
-We can think of a source domain as a collection of message IDs
-and associated translation strings.
-
-We often need to create strings that will be displayed by separate
-views. The view cannot translate the string without knowing its source
-domain. A string literal carries no domain information, so we use
-message IDs. Message IDs are strings which carry a translation source
-domain. These are created by a message ID factory. The message ID
-factory is created by calling zope.i18n.messageIDFactory with the
-source domain::
-
-  from zope import i18n
-  _ = i18n.MessageIDFactory("mydomain")
-
-  class IContact(Interface):
-      "Provides access to basic contact information."
-
-      first = TextLine(title=_(u"First name"))
-      last = TextLine(title=_(u"Last name"))
-      email = TextLine(title=_(u"Electronic mail address"))
-      address = Text(title=_(u"Postal address"))
-      postal_code = TextLine(title=_(u"Postal code"),
-			    constraint=re.compile("\d{5,5}(-\d{4,4})?$").match)
-
-      def name():
-	  """Gets the contact name.
-
-	  The contact name is the first and last name."""
-
-In this example, we create a message ID factory and assign it to
-_. By convention, we use _ as the name of our factory to be compatible
-with translatable string extraction tools such as xgettext. We then
-call _ with each string that needs to be translatable.  The resulting
-message IDs can be used by a translation service.
-


Property changes on: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid
___________________________________________________________________
Name: svn:ignore
   + *.so


Modified: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py	2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/__init__.py	2004-09-19 08:33:49 UTC (rev 27638)
@@ -11,5 +11,9 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"$Id$"
+"""I18n Messages
+
+$Id$
+"""
 from messageid import MessageID, MessageIDFactory
+from message import Message, MessageFactory

Added: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c	2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c	2004-09-19 08:33:49 UTC (rev 27638)
@@ -0,0 +1,266 @@
+/*############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+############################################################################*/
+
+/* $Id$ */
+
+#include "Python.h"
+
+/* these macros make gc support easier; they are only available in
+   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);				\
+    }						\
+  } while (0)
+#endif
+
+#ifndef Py_VISIT
+#define Py_VISIT(op)					\
+  do {							\
+    if (op) {						\
+      int vret = visit((op), arg);			\
+      if (vret)						\
+	return vret;					\
+    }							\
+  } while (0)
+#endif
+
+/* ----------------------------------------------------- */
+
+typedef struct {
+  PyUnicodeObject base;
+  PyObject *domain;
+  PyObject *default_;
+  PyObject *mapping;
+} Message;
+
+static PyTypeObject MessageType;
+
+static PyObject *
+Message_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+  static char *kwlist[] = {"value", "domain", "default", "mapping", NULL};
+  PyObject *value, *domain=NULL, *default_=NULL, *mapping=NULL, *s;
+  Message *self;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO", kwlist, 
+                                   &value, &domain, &default_, &mapping))
+    return NULL; 
+
+  args = Py_BuildValue("(O)", value);
+  if (args == NULL)
+    return 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;
+    }
+
+  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 (domain != NULL)
+    self->domain = domain;
+ 
+  if (default_ != NULL)
+    self->default_ = default_;
+
+  if (mapping != NULL)
+    self->mapping = mapping;
+
+  Py_XINCREF(self->mapping);
+  Py_XINCREF(self->default_);
+  Py_XINCREF(self->domain);
+
+  return (PyObject *)self;
+}
+
+/* Code to access structure members by accessing attributes */
+
+#include "structmember.h"
+
+static PyMemberDef Message_members[] = {
+  { "domain", T_OBJECT, offsetof(Message, domain), RO },
+  { "default", T_OBJECT, offsetof(Message, default_), RO },
+  { "mapping", T_OBJECT, offsetof(Message, mapping), RO },
+  {NULL}	/* Sentinel */
+};
+
+static int
+Message_traverse(Message *self, visitproc visit, void *arg)
+{
+  Py_VISIT(self->domain);
+  Py_VISIT(self->default_);
+  Py_VISIT(self->mapping);
+  return 0;
+}
+
+static int
+Message_clear(Message *self)
+{
+  Py_CLEAR(self->domain);
+  Py_CLEAR(self->default_);
+  Py_CLEAR(self->mapping);
+  return 0;
+}
+
+static void
+Message_dealloc(Message *self)
+{
+  Message_clear(self);
+  self->base.ob_type->tp_free((PyObject*)self);
+}
+
+static PyObject *
+Message_reduce(Message *self)
+{
+  PyObject *value, *result;
+  value = PyObject_CallFunctionObjArgs((PyObject *)&PyUnicode_Type, self, NULL);
+  if (value == NULL)
+    return NULL;
+  result = Py_BuildValue("O(OOOO)", self->base.ob_type,
+			 value,
+			 self->domain || Py_None,
+			 self->default_ || Py_None,
+			 self->mapping || Py_None);
+  Py_DECREF(value);
+  return result;
+}
+
+static PyMethodDef Message_methods[] = {
+  {"__reduce__", (PyCFunction)Message_reduce, METH_NOARGS,
+   "Reduce messages to a serializable form."},
+  {NULL}  /* Sentinel */
+};
+
+
+static char MessageType__doc__[] = 
+"Message\n"
+"\n"
+"This is a string used as a message.  It has a domain attribute that is\n"
+"its source domain, and a default attribute that is its default text to\n"
+"display when there is no translation.  domain may be None meaning there is\n"
+"no translation domain.  default may also be None, in which case the\n"
+"message id itself implicitly serves as the default text.\n";
+
+statichere PyTypeObject
+MessageType = {
+	PyObject_HEAD_INIT(NULL)
+	/* ob_size           */ 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        */ (cmpfunc)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 */
+/* -------------------------------------------------------- */
+
+
+/* List of methods defined in the module */
+
+static struct PyMethodDef _zope_i18nmessageid_message_methods[] = {
+  {NULL, (PyCFunction)NULL, 0, NULL}         /* sentinel */
+};
+
+
+static char _zope_i18nmessageid_message_module_documentation[] = 
+"I18n Messages"
+;
+
+#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+PyMODINIT_FUNC
+init_zope_i18nmessageid_message(void)
+{
+  PyObject *m;
+  /* Initialize types: */
+  MessageType.tp_base = &PyUnicode_Type;
+  if (PyType_Ready(&MessageType) < 0)
+    return;
+        
+  /* Create the module and add the functions */
+  m = Py_InitModule3("_zope_i18nmessageid_message",
+                     _zope_i18nmessageid_message_methods,
+                     _zope_i18nmessageid_message_module_documentation);
+
+  if (m == NULL)
+    return;
+       
+  /* Add types: */
+  if (PyModule_AddObject(m, "Message", (PyObject *)&MessageType) < 0)
+    return;
+}


Property changes on: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/_zope_i18nmessageid_message.c
___________________________________________________________________
Name: svn:keywords
   + Id

Added: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py	2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py	2004-09-19 08:33:49 UTC (rev 27638)
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""I18n Messages
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from _zope_i18nmessageid_message import Message
+
+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)


Property changes on: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/message.py
___________________________________________________________________
Name: svn:keywords
   + Id

Copied: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/messages.txt (from rev 27631, Zope3/trunk/src/zope/i18n/message_id.txt)
===================================================================
--- Zope3/trunk/src/zope/i18n/message_id.txt	2004-09-18 05:23:27 UTC (rev 27631)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/messages.txt	2004-09-19 08:33:49 UTC (rev 27638)
@@ -0,0 +1,113 @@
+=============
+I18n Messages
+=============
+
+Rationale
+---------
+
+To translate any text, we must be able to discover the source domain
+of the text.  A source domain is an identifier that identifies a
+project that produces program source strings.  Source strings occur as
+literals in python programs, text in templates, and some text in XML
+data.  The project implies a source language and an application
+context.
+
+We can think of a source domain as a collection of messages and
+associated translation strings.
+
+We often need to create unicode strings that will be displayed by
+separate views.  The view cannot translate the string without knowing
+its source domain.  A string or unicode literal carries no domain
+information, therefore we use messages.  Messages are unicode strings
+which carry a translation source domain and possibly a default
+translation.  They are created by a message factory. The message
+factory is created by calling ``MessageFactory`` with the source
+domain.
+
+
+Example
+-------
+
+In this example, we create a message factory and assign it to _.  By
+convention, we use _ as the name of our factory to be compatible with
+translatable string extraction tools such as xgettext.  We then call _
+with a string that needs to be translatable:
+
+  >>> from zope.i18nmessageid import MessageFactory, Message
+  >>> _ = MessageFactory("futurama")
+  >>> robot = _(u"robot-message", u"${name} is a robot.")
+
+Messages at first seem like they are unicode strings:
+
+  >>> robot
+  u'robot-message'
+  >>> isinstance(robot, unicode)
+  True
+
+The additional domain, default and mapping information is available
+through attributes:
+
+  >>> robot.default
+  u'${name} is a robot.'
+  >>> robot.mapping
+  >>> 
+
+The messags's attributes are considered part of the immutable message
+object.  They cannot be changed once the message id is created:
+
+  >>> robot.domain = "planetexpress"
+  Traceback (most recent call last):
+  ...
+  TypeError: readonly attribute
+
+  >>> robot.default = u"${name} is not a robot."
+  Traceback (most recent call last):
+  ...
+  TypeError: readonly attribute
+
+  >>> robot.mapping = {u'name': u'Bender'}
+  Traceback (most recent call last):
+  ...
+  TypeError: readonly attribute
+
+If you need to change their information, you'll have to make a new
+message id object:
+
+  >>> new_robot = Message(robot, mapping={u'name': u'Bender'})
+  >>> new_robot
+  u'robot-message'
+  >>> new_robot.domain
+  'futurama'
+  >>> new_robot.default
+  u'${name} is a robot.'
+  >>> new_robot.mapping
+  {u'name': u'Bender'}
+
+Last but not least, messages are reduceable for pickling:
+
+  >>> callable, args = new_robot.__reduce__()
+  >>> args
+  (u'robot-message', 'futurama', u'${name} is a robot', {u'name': u'Bender'})
+
+
+Message IDs and backward compatability
+--------------------------------------
+
+The change to immutability is not a simple refactoring that can be
+coped with backward compatible APIs--it is a change in semantics.
+Because immutability is one of those "you either have it or you don't"
+things (like pregnancy or death), we will not be able to support both
+in one implementation.
+
+The proposed solution for backward compatability is to support both
+implementations in parallel, deprecating the mutable one.  A separate
+factory, 'I18nMessageFactory', will instanciate immutable message ids,
+while the deprecated oldone will continue to work like before.
+
+The roadmap to immutable-only message ids is proposed as follows:
+
+  X3.1: Immutable message ids are introduced.  Mutable message ids are
+  deprecated.  Security declarations for mutable message ids are
+  provided to make the stripping of security proxies unnecessary.
+
+  X3.2: Mutable message ids are removed.

Modified: Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py
===================================================================
--- Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py	2004-09-19 08:15:13 UTC (rev 27637)
+++ Zope3/branches/philikon-messages-as-rocks/src/zope/i18nmessageid/tests.py	2004-09-19 08:33:49 UTC (rev 27638)
@@ -16,10 +16,13 @@
 $Id$
 """
 import unittest
-from zope.testing.doctestunit import DocTestSuite
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
 
 def test_suite():
-    return DocTestSuite('zope.i18nmessageid.messageid')
+    return unittest.TestSuite((
+	    DocTestSuite('zope.i18nmessageid.messageid'),
+	    DocFileSuite('messages.txt', package='zope.i18nmessageid'),
+	    ))
 
 if __name__ == '__main__':
     unittest.main(defaultTest="test_suite")



More information about the Zope3-Checkins mailing list