[Erp5-report] r25895 - in /experimental/erp5.buildout: ./ bootstrap/ patches/ profiles/ rec...

nobody at svn.erp5.org nobody at svn.erp5.org
Thu Mar 5 16:49:31 CET 2009


Author: nicolas
Date: Thu Mar  5 16:49:30 2009
New Revision: 25895

URL: http://svn.erp5.org?rev=25895&view=rev
Log:
Initial import of experimental buildout configuration files for ERP5.
 - Multiple profiles are available
 - Some recipes needs to be eggified


Added:
    experimental/erp5.buildout/
    experimental/erp5.buildout/EXTERNALS.TXT   (with props)
    experimental/erp5.buildout/README.txt   (with props)
    experimental/erp5.buildout/bootstrap/
    experimental/erp5.buildout/bootstrap/bootstrap.py
    experimental/erp5.buildout/buildout.cfg
    experimental/erp5.buildout/patches/
    experimental/erp5.buildout/patches/Zope-2.8.0-final-aq_dynamic.patch
    experimental/erp5.buildout/profiles/
    experimental/erp5.buildout/profiles/base.cfg
    experimental/erp5.buildout/profiles/cluster.cfg
    experimental/erp5.buildout/profiles/create-site.cfg
    experimental/erp5.buildout/profiles/development.cfg
    experimental/erp5.buildout/profiles/versions.cfg
    experimental/erp5.buildout/recipes/
    experimental/erp5.buildout/recipes/create_erp5_instance.py
    experimental/erp5.buildout/recipes/create_erp5_site.py
    experimental/erp5.buildout/recipes/run_unit_test.py
    experimental/erp5.buildout/recipes/setup.py
    experimental/erp5.buildout/recipes/zope2install.py
    experimental/erp5.buildout/src/
    experimental/erp5.buildout/src/EXTERNALS.TXT   (with props)
    experimental/erp5.buildout/src/erp5diff/
    experimental/erp5.buildout/src/erp5diff/ERP5Diff.py   (with props)
    experimental/erp5.buildout/src/erp5diff/MAINTAINERS.txt   (with props)
    experimental/erp5.buildout/src/erp5diff/README   (with props)
    experimental/erp5.buildout/src/erp5diff/erp5diff   (with props)
    experimental/erp5.buildout/src/erp5diff/erp5diff.1   (with props)
    experimental/erp5.buildout/src/erp5diff/setup.py   (with props)
    experimental/erp5.buildout/src/timerserver/
    experimental/erp5.buildout/src/timerserver/setup.py
    experimental/erp5.buildout/src/timerserver/timerserver/
    experimental/erp5.buildout/src/timerserver/timerserver/TimerServer.py
    experimental/erp5.buildout/src/timerserver/timerserver/__init__.py
    experimental/erp5.buildout/src/timerserver/timerserver/component.xml
    experimental/erp5.buildout/src/timerserver/version.txt   (with props)
    experimental/erp5.buildout/templates/
    experimental/erp5.buildout/templates/haproxy.cfg.default

Added: experimental/erp5.buildout/EXTERNALS.TXT
URL: http://svn.erp5.org/experimental/erp5.buildout/EXTERNALS.TXT?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/EXTERNALS.TXT (added)
+++ experimental/erp5.buildout/EXTERNALS.TXT [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,10 @@
+#
+# Used for maintenance of external resources in this svn bundle.  Edit
+# this file as appropriate and then run the following command from within
+# the checkout directory where this file lives on your local machine:
+#
+# svn propset svn:externals -F ./EXTERNALS.TXT .
+#
+
+bootstrap svn://svn.zope.org/repos/main/zc.buildout/trunk/bootstrap
+

Propchange: experimental/erp5.buildout/EXTERNALS.TXT
------------------------------------------------------------------------------
    svn:eol-style = native

Added: experimental/erp5.buildout/README.txt
URL: http://svn.erp5.org/experimental/erp5.buildout/README.txt?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/README.txt (added)
+++ experimental/erp5.buildout/README.txt [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,68 @@
+ERP5 buildout
+=============
+
+This is a zc.buildout environment for ERP5 instance. The original use case is
+to help developpers in rapidly creating new instances, or to try out other
+versions of dependancy products.
+
+Requirements (Mandriva package names in paranthesis):
+
+ * python2.4 development packages (python2.4-devel)
+ * Development packages for mysql (mysql-devel)
+ * convert (From ImageMagick) (imagemagick)
+ * patch (patch)
+ * OpenLDAP v2 development packages (openldap2-devel)
+ * SSL development packages (openssl-devel)
+ * XML and XSLT development packages (libxslt-devel libxml2-devel)
+
+Note: Use buildout for python2.4
+
+
+Basic Usage
+-----------
+
+The default use create a development site and install some business templates.
+
+Example use::
+  
+  # in this example, we use -S to prevent importing site packages, you could
+  # also use virtualenv, or you could also want to use your site packages, for
+  # example not to build pysvn again.
+  python2.4 -S bootstrap/bootstrap.py
+  python2.4 -S bin/buildout 
+
+
+Customization
+-------------
+
+Write your own buildout.cfg, extending one of the available profiles
+(cluster.cfg, development.cfg etc).
+You can also change some variables, such as mysql connection string, or install
+more products or business templates, you can do this by extending existing
+parts (adding urls in [products-deps] for example).
+
+
+Notes
+=====
+
+The Acquisition patch
+---------------------
+
+ERP5 uses a patched version of zope for dynamic acquisition. This buildout
+extends plone.recipe.zope2install to patch zope before installing it. The patch
+is in patches subdirectory.
+
+
+TODO:
+  convert
+  Numpy
+  GLPK
+  runUnitTest --save/--load does not work
+  itools 0.20.6 does not compile on 64bits, using itools 0.20.2 workarounds
+
+
+Notes:
+  CMFReportTool is not installed, because reportlab cannot be installed via
+  easy_install.
+
+

Propchange: experimental/erp5.buildout/README.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Added: experimental/erp5.buildout/bootstrap/bootstrap.py
URL: http://svn.erp5.org/experimental/erp5.buildout/bootstrap/bootstrap.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/bootstrap/bootstrap.py (added)
+++ experimental/erp5.buildout/bootstrap/bootstrap.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,77 @@
+##############################################################################
+#
+# Copyright (c) 2006 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 90478 2008-08-27 22:44:46Z georgyberdyshev $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    def quote (c):
+        return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws  = pkg_resources.working_set
+
+if is_jython:
+    import subprocess
+    
+    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', 
+           quote(tmpeggs), 'zc.buildout'], 
+           env=dict(os.environ,
+               PYTHONPATH=
+               ws.find(pkg_resources.Requirement.parse('setuptools')).location
+               ),
+           ).wait() == 0
+
+else:
+    assert os.spawnle(
+        os.P_WAIT, sys.executable, quote (sys.executable),
+        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
+        dict(os.environ,
+            PYTHONPATH=
+            ws.find(pkg_resources.Requirement.parse('setuptools')).location
+            ),
+        ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)

Added: experimental/erp5.buildout/buildout.cfg
URL: http://svn.erp5.org/experimental/erp5.buildout/buildout.cfg?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/buildout.cfg (added)
+++ experimental/erp5.buildout/buildout.cfg [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,3 @@
+[buildout]
+extends = profiles/create-site.cfg 
+

Added: experimental/erp5.buildout/patches/Zope-2.8.0-final-aq_dynamic.patch
URL: http://svn.erp5.org/experimental/erp5.buildout/patches/Zope-2.8.0-final-aq_dynamic.patch?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/patches/Zope-2.8.0-final-aq_dynamic.patch (added)
+++ experimental/erp5.buildout/patches/Zope-2.8.0-final-aq_dynamic.patch [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,104 @@
+diff -urN Zope-2.8.0-final.orig/lib/python/Acquisition/_Acquisition.c Zope-2.8.0-final/lib/python/Acquisition/_Acquisition.c
+--- Zope-2.8.0-final.orig/lib/python/Acquisition/_Acquisition.c	2005-06-11 08:24:02.000000000 +0200
++++ Zope-2.8.0-final/lib/python/Acquisition/_Acquisition.c	2005-06-17 16:26:00.742556205 +0200
+@@ -410,6 +410,64 @@
+ 		int explicit, int containment);
+ 
+ static PyObject *
++Wrapper_GetAttr(PyObject *self, PyObject *attr_name, PyObject *orig)
++{
++  /* This function retrieves an attribute from an object by PyObject_GetAttr.
++
++     The main difference between Wrapper_GetAttr and PyObject_GetAttr is that
++     Wrapper_GetAttr calls _aq_dynamic to generate an attribute dynamically, if
++     the attribute is not found.
++  */
++  PyObject *r, *v, *tb;
++  PyObject *d, *m;
++  PyObject *o;
++
++  if (isWrapper (self))
++    o = WRAPPER(self)->obj;
++  else
++    o = self;
++
++  /* Try to get an attribute in the normal way first.  */
++  r = PyObject_GetAttr(o, attr_name);
++  if (r)
++    return r;
++
++  /* If an unexpected error happens, return immediately.  */
++  PyErr_Fetch(&r,&v,&tb);
++  if (r != PyExc_AttributeError)
++    {
++      PyErr_Restore(r,v,tb);
++      return NULL;
++    }
++
++  /* Try to get _aq_dynamic.  */
++  m = PyObject_GetAttrString(o, "_aq_dynamic");
++  if (! m) {
++    PyErr_Restore(r,v,tb);
++    return NULL;
++  }
++
++  /* Call _aq_dynamic in the context of the original acquisition wrapper.  */
++  if (PyECMethod_Check(m) && PyECMethod_Self(m)==o)
++    ASSIGN(m,PyECMethod_New(m,OBJECT(self)));
++  else if (has__of__(m)) ASSIGN(m,__of__(m,OBJECT(self)));
++  d = PyObject_CallFunction(m, "O", attr_name);
++  Py_DECREF(m);
++
++  /* In the case of None, assume that the attribute is not found.  */
++  if (d == Py_None) {
++    Py_DECREF(d);
++    PyErr_Restore(r,v,tb);
++    return NULL;
++  }
++
++  Py_XDECREF(r);
++  Py_XDECREF(v);
++  Py_XDECREF(tb);
++  return d;
++}
++
++static PyObject *
+ Wrapper_findattr(Wrapper *self, PyObject *oname,
+ 		PyObject *filter, PyObject *extra, PyObject *orig,
+ 		int sob, int sco, int explicit, int containment)
+@@ -476,7 +534,7 @@
+ 	  Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
+ 	  r=NULL;
+ 	}
+-      else if ((r=PyObject_GetAttr(self->obj,oname)))
++      else if ((r=Wrapper_GetAttr(OBJECT(self),oname,orig)))
+ 	{
+ 	  if (r==Acquired)
+ 	    {
+@@ -550,7 +608,7 @@
+ 	}
+       else
+ 	{
+-	  if ((r=PyObject_GetAttr(self->container,oname))) {
++	  if ((r=Wrapper_GetAttr(self->container,oname,orig))) {
+ 	    if (r == Acquired) {
+ 	      Py_DECREF(r);
+ 	    }
+@@ -587,7 +645,7 @@
+ Wrapper_getattro(Wrapper *self, PyObject *oname)
+ {
+   if (self->obj || self->container)
+-    return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 1, 0, 0);
++    return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 1, 0, 0);
+ 
+   /* Maybe we are getting initialized? */
+   return Py_FindAttr(OBJECT(self),oname);
+@@ -604,7 +662,7 @@
+     return Py_FindAttr(OBJECT(self),oname);
+ 
+   if (self->obj || self->container)
+-    return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0);
++    return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 0, 0, 0);
+ 
+   /* Maybe we are getting initialized? */
+   return Py_FindAttr(OBJECT(self),oname);

Added: experimental/erp5.buildout/profiles/base.cfg
URL: http://svn.erp5.org/experimental/erp5.buildout/profiles/base.cfg?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/profiles/base.cfg (added)
+++ experimental/erp5.buildout/profiles/base.cfg [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,142 @@
+[buildout]
+extends = versions.cfg
+versions = versions
+develop =
+    recipes
+    src/timerserver
+    src/erp5diff
+offline = false
+; index = http://pypi.python.org/simple
+;         http://pypi.netsight.co.uk/
+;         http://pypi.zopyx.com/
+;         http://pypi.inqbus.de
+find-links =
+    http://download.zope.org/distribution/
+    https://svn.erp5.org/repos/public/erp5/trunk/utils/erp5diff
+    http://download.hforge.org/itools/0.20/
+    http://download.hforge.org/itools/
+eggs =
+    MySQL-python
+    python-memcached
+    elementtree
+    PyXML
+    4Suite-XML
+    ply
+    itools
+    python-ldap
+    lxml
+parts =
+    eggs-deps
+    zope2
+    cmf15
+    products-deps
+    products-other
+    mkdir-instances
+
+[eggs-deps]
+recipe = zc.recipe.egg:scripts
+interpreter = python2.4
+eggs =
+    mechanize
+    ClientForm
+    timerserver
+
+[cmf15]
+recipe = plone.recipe.distros
+urls =
+    http://www.zope.org/Products/CMF/CMF-1.5.4/CMF-1.5.4.tar.gz
+version-suffix-packages =
+    CMF-1.5.4.tar.gz
+
+[products-deps]
+recipe = plone.recipe.distros
+urls =
+    http://www.zope.org/Members/shh/ExtFile/1.4.4/ExtFile-1.4.4.tar.gz
+    http://www.zope.org/Members/NIP/ZMailIn/1.0.1/ZMailIn-1-0-1.tgz
+    http://www.zope.org/Members/NIP/ZMailIn/1.0.0/CMFMailIn-1.0.0
+    http://www.zope.org/Products/PluggableAuthService/PluggableAuthService-1.1b2/PluggableAuthService-1.1b2.tar.gz
+    http://www.infrae.com/download/Formulator/1.6.2/Formulator-1.6.2.tgz
+    http://kent.dl.sourceforge.net/sourceforge/fckeditor/FCKeditor.Plone_2.2.tar.gz
+    http://download.ikaaro.org/localizer/Localizer-1.2.3.tar.gz
+version-suffix-packages =
+    Localizer-1.2.3.tar.gz
+
+[products-erp5]
+recipe = infrae.subversion
+# You can Control Revision by adding  @{revision number} in uri
+# eg: https://svn.erp5.org/repos/public/erp5/trunk/products/ERP5@24320 ERP5
+# or use a tag as base eg. base =
+# https://svn.erp5.org/repos/public/erp5/tags/version-5.4/products/
+base = https://svn.erp5.org/repos/public/erp5/trunk/products/
+urls =
+    ${products-erp5:base}/CMFActivity CMFActivity
+    ${products-erp5:base}/CMFCategory CMFCategory
+    ${products-erp5:base}/ERP5 ERP5
+    ${products-erp5:base}/ERP5Banking ERP5Banking
+    ${products-erp5:base}/ERP5Catalog ERP5Catalog
+    ${products-erp5:base}/ERP5Form ERP5Form
+    ${products-erp5:base}/ERP5OOo ERP5OOo
+    ${products-erp5:base}/ERP5SyncML ERP5SyncML
+    ${products-erp5:base}/ERP5Type ERP5Type
+    ${products-erp5:base}/ERP5Security ERP5Security
+    ${products-erp5:base}/MailTemplates MailTemplates
+    ${products-erp5:base}/PortalTransforms PortalTransforms
+    ${products-erp5:base}/TIDStorage TIDStorage
+    ${products-erp5:base}/TimerService TimerService
+    ${products-erp5:base}/ZMySQLDA ZMySQLDA
+    ${products-erp5:base}/ZMySQLDDA ZMySQLDDA
+    ${products-erp5:base}/ZSQLCatalog ZSQLCatalog
+    ${products-erp5:base}/ERP5Subversion ERP5Subversion
+
+[products-other]
+recipe = infrae.subversion
+#Archetypes should be contained in Products Folder
+location = parts/products-other/Products
+urls =
+    svn://svn.zope.org/repos/main/Zelenium/trunk/ Zelenium
+    svn://svn.zope.org/repos/main/PluginRegistry/tags/1.0 PluginRegistry
+    http://svn.plone.org/svn/archetypes/MimetypesRegistry/tags/Archetypes-1.4.0-final MimetypesRegistry
+    https://svn.plone.org/svn/collective/CMFPhoto/trunk/ CMFPhoto
+
+[zope2]
+recipe = recipes:zope2install
+url = http://www.zope.org/Products/Zope/2.8.10/Zope-2.8.10-final.tgz
+patch = patches/Zope-2.8.0-final-aq_dynamic.patch
+#http://www.zope.org/Products/Zope/2.9.8/Zope-2.9.8-final.tgz
+
+
+[erp5_instance]
+recipe = plone.recipe.zope2instance
+zope2-location = ${zope2:location}
+user = zope:zope
+http-address = 18080
+debug-mode = off
+control-script = zopectl
+#Only for zope2.8
+default-zpublisher-encoding = 
+eggs =
+    ${buildout:eggs}
+    timerserver
+    erp5diff
+
+products =
+  ${cmf15:location}/CMF/
+  ${products-deps:location}
+  ${products-erp5:location}
+  ${products-other:location}
+zope-conf-additional=
+  %import timerserver
+  <timer-server>
+    interval 5
+  </timer-server>
+
+#XXX bug in 4Suite
+[mkdir-instances]
+recipe = ore.recipe.fs:mkdir
+createpath = true
+path = ${erp5_instance:location}/lib/python
+
+# [ldap]
+# recipe = z3c.recipe.ldap
+# todo
+

Added: experimental/erp5.buildout/profiles/cluster.cfg
URL: http://svn.erp5.org/experimental/erp5.buildout/profiles/cluster.cfg?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/profiles/cluster.cfg (added)
+++ experimental/erp5.buildout/profiles/cluster.cfg [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,127 @@
+[buildout]
+extends = base.cfg
+parts +=
+  apacheconfig
+  haproxy
+  zeo_server
+  zeo_client_1
+  zeo_client_2
+  cluster
+parts -= erp5_instance
+
+[apachebuild]
+recipe = plone.recipe.apache:build
+url = http://apache.multidist.com/httpd/httpd-2.2.11.tar.gz
+modules = mod_cache
+           mod_mpm
+           mod_proxy
+           mod_deflate
+           mod_conf
+           mod_base
+           mod_modules
+           mod_disk_cache
+           mod_ssl
+           mod_htcacheclean
+
+[apacheconfig]
+recipe = plone.recipe.apache:config
+mainconfig = ${apachebuild:location}/conf/httpd.conf
+bind = 443
+zope2_vhm_map = localhost:/erp5
+backends = localhost:127.0.0.1:2002
+
+
+[haproxy]
+recipe = plone.recipe.haproxy
+url = http://haproxy.1wt.eu/download/1.3/src/haproxy-1.3.15.7.tar.gz
+target = linux26
+
+
+[haproxy_etc_folder]
+recipe = ore.recipe.fs:mkdir
+path = ${haproxy:location}/etc
+
+
+[haproxyconfig]
+recipe = buildout_script:template
+template = haproxy.cfg.default
+output_dir = ${haproxy:location}/etc
+maxconn = 2000
+retries = 3
+port = 2002
+balance = roundrobin
+rules =
+     server  zeo_client_1 127.0.0.1:${zeo_client_1:http-address} cookie zeo_client_1 check inter 3s rise 2 fall 4 maxconn 1
+     server  zeo_client_2 127.0.0.1:${zeo_client_2:http-address} cookie zeo_client_2 check inter 3s rise 2 fall 4 maxconn 1
+httpchk = /erp5/getId
+
+
+[zeo_server]
+recipe = plone.recipe.zope2zeoserver
+zope2-location = ${zope2:location}
+user = admin:admin
+zeo-address = 8100
+eggs =
+    ${buildout:eggs}
+    timerserver
+    erp5diff
+
+
+[zeo_client_1]
+recipe = plone.recipe.zope2instance
+zope2-location = ${zope2:location}
+user = ${zeo_server:user}
+http-address = 18180
+debug-mode = off
+zeo-client = on
+zeo-address = 8100
+control-script = zopectl-zeo_client_1
+eggs = ${zeo_server:eggs}
+products =
+  ${cmf15:location}/CMF/
+  ${products-deps:location}
+  ${products-erp5:location}
+  ${products-other:location}
+zope-conf-additional=
+  %import timerserver
+  <timer-server>
+    interval 5
+  </timer-server>
+
+
+[zeo_client_2]
+recipe = plone.recipe.zope2instance
+zope2-location = ${zope2:location}
+user = ${zeo_server:user}
+http-address = 18280
+debug-mode = off
+zeo-client = on
+zeo-address = 8100
+control-script = zopectl-zeo_client_2
+eggs = ${zeo_server:eggs}
+products =
+  ${cmf15:location}/CMF/
+  ${products-deps:location}
+  ${products-erp5:location}
+  ${products-other:location}
+zope-conf-additional=
+  %import timerserver
+  <timer-server>
+    interval 5
+  </timer-server>
+
+[cluster]
+recipe = plone.recipe.cluster
+start =
+       ${buildout:bin-directory}/zeo_server start
+       ${buildout:bin-directory}/zopectl-zeo_client_1 start
+       ${buildout:bin-directory}/zopectl-zeo_client_2 start
+stop =
+       ${buildout:bin-directory}/zeo_server stop
+       ${buildout:bin-directory}/zopectl-zeo_client_1 stop
+       ${buildout:bin-directory}/zopectl-zeo_client_2 stop
+restart =
+       ${buildout:bin-directory}/zeo_server restart
+       ${buildout:bin-directory}/zopectl-zeo_client_1 restart
+       ${buildout:bin-directory}/zopectl-zeo_client_2 restart
+

Added: experimental/erp5.buildout/profiles/create-site.cfg
URL: http://svn.erp5.org/experimental/erp5.buildout/profiles/create-site.cfg?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/profiles/create-site.cfg (added)
+++ experimental/erp5.buildout/profiles/create-site.cfg [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,70 @@
+; This configuration creates a site and install some business templates inside
+[buildout]
+extends = base.cfg
+parts += create_erp5_site
+
+
+[download_bt5]
+recipe = infrae.subversion
+base = https://svn.erp5.org/repos/public/erp5/trunk/bt5
+location = ${erp5_instance:location}/bt5
+urls =
+  ${download_bt5:base}/erp5_base/ erp5_base/
+  ${download_bt5:base}/erp5_pdm/ erp5_pdm/
+  ${download_bt5:base}/erp5_trade/ erp5_trade/
+  ${download_bt5:base}/erp5_accounting/ erp5_accounting/
+  ${download_bt5:base}/erp5_invoicing/ erp5_invoicing/
+  ${download_bt5:base}/erp5_ods_style/ erp5_ods_style/
+  ${download_bt5:base}/erp5_odt_style/ erp5_odt_style/
+  ${download_bt5:base}/erp5_ingestion_mysql_innodb_catalog/ erp5_ingestion_mysql_innodb_catalog/
+  ${download_bt5:base}/erp5_ingestion/ erp5_ingestion/
+  ${download_bt5:base}/erp5_crm/ erp5_crm/
+  ${download_bt5:base}/erp5_web/ erp5_web/
+  ${download_bt5:base}/erp5_forge/ erp5_forge/
+  ${download_bt5:base}/erp5_dms_mysql_innodb_catalog/ erp5_dms_mysql_innodb_catalog/
+  ${download_bt5:base}/erp5_dms/ erp5_dms/
+  ${download_bt5:base}/erp5_ui_test_core/ erp5_ui_test_core/
+  ${download_bt5:base}/erp5_ui_test/ erp5_ui_test/
+  ${download_bt5:base}/erp5_pdm_ui_test/ erp5_pdm_ui_test/
+  ${download_bt5:base}/erp5_accounting_ui_test/ erp5_accounting_ui_test/
+  ${download_bt5:base}/erp5_web_ui_test/ erp5_web_ui_test/
+  ${download_bt5:base}/erp5_dms_ui_test/ erp5_dms_ui_test/
+  ${download_bt5:base}/erp5_l10n_fr/ erp5_l10n_fr/
+  ${download_bt5:base}/erp5_l10n_ja/ erp5_l10n_ja/
+  ${download_bt5:base}/erp5_l10n_pl_PL/ erp5_l10n_pl_PL/
+  ${download_bt5:base}/erp5_l10n_pt-BR/ erp5_l10n_pt-BR/
+
+
+[create_erp5_site]
+recipe = recipes:create_erp5_site
+portal_id = erp5
+control-script = ${erp5_instance:control-script}
+user = ${erp5_instance:user}
+erp5_sql_connection_string = erp5%20root
+bt5-path = ${download_bt5:location}
+bt5 =
+   erp5_base
+   erp5_pdm
+   erp5_trade
+   erp5_accounting
+   erp5_invoicing
+   erp5_ods_style
+   erp5_odt_style
+   erp5_ingestion_mysql_innodb_catalog
+   erp5_ingestion
+   erp5_crm
+   erp5_forge
+   erp5_web
+   erp5_dms_mysql_innodb_catalog
+   erp5_dms
+   erp5_ui_test_core
+   erp5_ui_test
+   erp5_pdm_ui_test
+   erp5_accounting_ui_test
+   erp5_web_ui_test
+   erp5_dms_ui_test
+   erp5_l10n_fr
+   erp5_l10n_ja
+   erp5_l10n_pl_PL
+   erp5_l10n_pt-BR
+

Added: experimental/erp5.buildout/profiles/development.cfg
URL: http://svn.erp5.org/experimental/erp5.buildout/profiles/development.cfg?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/profiles/development.cfg (added)
+++ experimental/erp5.buildout/profiles/development.cfg [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,59 @@
+[buildout]
+extends = create-site.cfg
+parts +=
+   erp5flakes
+   runUnitTest
+   zopepy
+   pysvn
+
+# We active debug on the instance and add some products.
+[erp5_instance]
+debug-mode = on
+;eggs +=
+;   Products.PDBDebugMode
+;   Products.PTProfiler
+
+[apachebuild]
+recipe = plone.recipe.apache:build
+url = http://apache.multidist.com/httpd/httpd-2.2.11.tar.gz
+ 
+[pysvn]
+recipe=ore.recipe.svnlib
+url=http://subversion.tigris.org/downloads/subversion-1.5.0-rc4.tar.bz2
+download_cache = cache
+extra_options=--with-apr=${apachebuild:location}/bin/ --with-apr-util=${apachebuild:location}/bin/ PYTHON=/usr/bin/python2.4
+
+[eggs-deps]
+extra-paths += ${pysvn:location}/lib/svn-python
+eggs += ipdb
+
+[products-deps]
+urls += http://www.zope.org/Members/panjunyong/DCWorkflowGraph/dcworkflowgraph-0_3/dcworkflowgraph-0_3.tgz
+
+[erp5flakes]
+recipe = zc.recipe.egg
+scripts =
+  pyflakes
+eggs =
+  pyflakes
+  setuptools
+entry-points = erp5flakes=pkg_resources:run_script
+arguments = 'erp5flakes', 'erp5flakes'
+
+[zopepy]
+recipe = zc.recipe.egg
+eggs =
+    ${buildout:eggs}
+    timerserver
+    erp5diff
+interpreter = zopepy
+extra-paths = ${zope2:location}/lib/python
+scripts = zopepy
+
+[runUnitTest]
+recipe = recipes:run_unit_test
+zope2-location = ${zope2:location}
+products = ${erp5_instance:products}
+eggs = ${erp5_instance:eggs}
+bt5_path = ${download_bt5:location}
+

Added: experimental/erp5.buildout/profiles/versions.cfg
URL: http://svn.erp5.org/experimental/erp5.buildout/profiles/versions.cfg?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/profiles/versions.cfg (added)
+++ experimental/erp5.buildout/profiles/versions.cfg [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,12 @@
+[versions]
+MySQL-python = 1.2.2
+python-memcached = 1.43
+elementtree = 1.2.7_20070827_preview
+PyXML = 0.8.4
+4Suite-XML = 1.0.2
+lxml = 2.1.5
+ply = 3.0
+ipdb = 0.1dev_r1716
+itools = 0.20.6
+python-ldap = 2.3.5
+

Added: experimental/erp5.buildout/recipes/create_erp5_instance.py
URL: http://svn.erp5.org/experimental/erp5.buildout/recipes/create_erp5_instance.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/recipes/create_erp5_instance.py (added)
+++ experimental/erp5.buildout/recipes/create_erp5_instance.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,102 @@
+# Create an ERP5 instance 
+# usage: zopectl run create_erp5_instance [options] [business templates]
+
+import os
+from optparse import OptionParser
+from urllib import unquote
+
+from Testing.makerequest import makerequest
+from AccessControl.SecurityManagement import newSecurityManager
+
+parser = OptionParser()
+parser.add_option("-p", "--portal_id", dest="portal_id",
+                  help="The ID of the Portal", default="erp5")
+parser.add_option("--erp5_sql_connection_string",
+                  dest="erp5_sql_connection_string",
+                  help="Connection String used for ZSQLCatalog "
+                       "(use %20 for space)",
+                  default="test test")
+parser.add_option("--cmf_activity_sql_connection_string",
+                  dest="cmf_activity_sql_connection_string",
+                  help="Connection String used for CMFActivity")
+parser.add_option("--erp5_catalog_storage",
+                  dest="erp5_catalog_storage",
+                  help="Business Template for Catalog Storage")
+parser.add_option("-u", "--initial-user",
+                  dest="user_and_pass",
+                  help="User and Password, separated by :",
+                  default="zope:zope")
+parser.add_option("--bt5-path",
+                  dest="bt5_path",
+                  help="Path to folder containing business templates",
+                  default="bt5")
+
+(options, args) = parser.parse_args()
+
+# cmf activity connection string defaults to zsqlcatalog's one
+if not options.cmf_activity_sql_connection_string:
+  options.cmf_activity_sql_connection_string = \
+            options.erp5_sql_connection_string
+
+# connection strings have to contain a space, for conveniance, this space can
+# be replaced by %20 character. It's actually the only way due to (probably) a bug in Cmd
+options.erp5_sql_connection_string =\
+      unquote(options.erp5_sql_connection_string)
+options.cmf_activity_sql_connection_string =\
+      unquote(options.cmf_activity_sql_connection_string)
+
+username, password = options.user_and_pass.split(':')
+
+try:
+  import transaction
+except ImportError:
+  class Transaction:
+    def commit(self):
+      return get_transaction().commit()
+  transaction = Transaction()
+
+app = makerequest(app)
+
+user = app.acl_users.getUserById(username)
+if user is None:
+  uf = app.acl_users
+  uf._doAddUser(username, password, ['Manager', 'Member', 'Assignee',
+                                     'Assignor', 'Author'], [])
+  user = uf.getUserById(username)
+
+newSecurityManager(None, user.__of__(app.acl_users))
+
+print 'Adding ERP5 site %s' % options.portal_id
+portal = getattr(app, options.portal_id, None)
+if portal is None:
+  app.manage_addProduct['ERP5'].manage_addERP5Site(
+              id=options.portal_id,
+              erp5_sql_connection_string=options.erp5_sql_connection_string,
+              erp5_sql_deferred_connection_string=\
+                        options.erp5_sql_connection_string,
+              cmf_activity_sql_connection_string=\
+                        options.cmf_activity_sql_connection_string,
+              erp5_catalog_storage='erp5_mysql_innodb_catalog')
+
+  transaction.commit()
+  portal = app._getOb(options.portal_id)
+
+# set preference for erp5_subversion
+from App.config import getConfiguration
+default_site_preference = portal.portal_preferences.default_site_preference
+instance_home = getConfiguration().instancehome
+default_site_preference.edit(
+  preferred_subversion_working_copy_list=['%s/bt5/' % instance_home,
+                            '%s/Products/ERP5/bootstrap/' % instance_home])
+default_site_preference.enable()
+
+# install our business templates
+bt5_list = []
+for arg in args:
+  bt_path = os.path.join(options.bt5_path, arg)
+  print 'Installing bt %s' % bt_path
+  bt = portal.portal_templates.download(bt_path)
+  bt.install(force=True)
+  transaction.commit()
+
+transaction.commit()

Added: experimental/erp5.buildout/recipes/create_erp5_site.py
URL: http://svn.erp5.org/experimental/erp5.buildout/recipes/create_erp5_site.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/recipes/create_erp5_site.py (added)
+++ experimental/erp5.buildout/recipes/create_erp5_site.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,62 @@
+import os, re, shutil, tempfile, urllib2, urlparse
+import setuptools.archive_util
+
+class Recipe:
+    '''Recipe to create an ERP5 site using zopectl run
+    '''
+
+    def __init__(self, buildout, name, options):
+        self.buildout, self.name, self.options = buildout, name, options
+        # Guess the name of the install script by looking at the section using
+        # zope2instance recipe
+        for k, v in self.buildout.items():
+            if v.get('recipe') == 'plone.recipe.zope2instance':
+                instance_script = v.get('control-script')
+                options['control-script'] = instance_script or k
+                options['instance-location'] = v['location']
+        assert 'control-script' in options, 'Unable to find a zope2 instance'
+
+        options['bin-directory'] = buildout['buildout']['bin-directory']
+
+    def install(self):
+        options = self.options
+        zeo_ctl = options.get('zeo-control-script', None)
+        if zeo_ctl:
+          zeo_ctl_path = os.path.join(options['bin-directory'], zeo_ctl)
+          assert os.spawnl(
+              os.P_WAIT, zeo_ctl_path, *[zeo_ctl_path, 'start']) == 0
+        zopectl_path = os.path.join(options['bin-directory'],
+                                    options['control-script'])
+        script_name = os.path.join(os.path.dirname(__file__),
+                                  'create_erp5_instance.py')
+        argv = [zopectl_path, 'run', script_name]
+
+        if options.get('portal_id'):
+            argv.extend(['--portal_id', options['portal_id']])
+        if options.get('erp5_sql_connection_string'):
+            argv.extend(['--erp5_sql_connection_string',
+                        options['erp5_sql_connection_string']])
+
+        if options.get('cmf_activity_sql_connection_string'):
+            argv.extend(['--cmf_activity_sql_connection_string',
+                 options['cmf_activity_sql_connection_string']])
+        if options.get('erp5_catalog_storage'):
+            argv.extend(['--erp5_catalog_storage',
+                        options['erp5_catalog_storage']])
+        if options.get('user'):
+            # XXX read rom zope2instance section ?
+            argv.extend(['--initial-user',
+                        options['user']])
+
+        argv.extend(['--bt5-path',
+                    os.path.join(options['bt5-path'])])
+        argv.extend([bt for bt in options.get('bt5', '').split('\n') if bt])
+
+        assert os.spawnl(
+              os.P_WAIT, zopectl_path, *argv ) == 0
+        
+        return []
+
+    def update(self):
+        pass
+    

Added: experimental/erp5.buildout/recipes/run_unit_test.py
URL: http://svn.erp5.org/experimental/erp5.buildout/recipes/run_unit_test.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/recipes/run_unit_test.py (added)
+++ experimental/erp5.buildout/recipes/run_unit_test.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2006-2008 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.
+#
+##############################################################################
+
+import os, re, shutil, sys, glob
+import zc.buildout
+import zc.recipe.egg
+
+class Recipe:
+    '''Creates a runUnitTest aware of eggs and custom paths used in this
+    buildbot.
+    '''
+    name = 'runUnitTest'
+
+    def __init__(self, buildout, name, options):
+        self.egg = zc.recipe.egg.Egg(buildout, 'recipes', options)
+        self.buildout, self.options, self.name = buildout, options, name
+
+        options['location'] = os.path.join(
+            buildout['buildout']['parts-directory'],
+            self.name,
+            )
+        options['bin-directory'] = buildout['buildout']['bin-directory']
+        options['scripts'] = '' # suppress script generation. ??? XXX
+
+    def install(self):
+        options = self.options
+        location = options['location']
+
+        extra_paths = []
+        if 'zope2-location' in options:
+          extra_paths.append(
+                os.path.join(options['zope2-location'], 'lib', 'python'))
+        extra_paths.extend(options.get('extra-paths', []))
+
+        init = init_template
+        for product in options.get('products', '').splitlines():
+          if product:
+            init += add_product_path_template % dict(product_path=product)
+            for product_test_path in glob.glob(
+                                        os.path.join(product, '*', 'tests')):
+              init += add_product_tests_path_template % dict(
+                                        product_test_path=product_test_path)
+
+        for bt5_path in options.get('bt5_path', '').splitlines():
+          if bt5_path:
+            init += add_bt5_path_template % dict(bt5_path=bt5_path)
+
+        requirements, ws = self.egg.working_set()
+
+        zc.buildout.easy_install.scripts(
+            [(self.options.get('control-script', self.name),
+                'Products.ERP5Type.tests.runUnitTest', 'main')],
+            ws, options['executable'], options['bin-directory'],
+            extra_paths = extra_paths,
+            initialization = init,
+        )
+
+        return []
+
+init_template = '''
+import Products
+import sys
+
+import Ft # XXX workaround strange bug:
+# File "4Suite_XML-1.0.2-py2.4-linux-i686.egg/Ft/Lib/ImportUtil.py"
+#  line 296, in get_loader
+# return find_loader(fullname)
+# File "4Suite_XML-1.0.2-py2.4-linux-i686.egg/Ft/Lib/ImportUtil.py",
+#  line 306, in find_loader
+# loader = importer.find_module(fullname)
+#    AttributeError: 'NoneType' object has no attribute 'find_module'
+'''
+
+add_product_path_template = '''
+Products.__path__.append('%(product_path)s')'''
+
+add_product_tests_path_template = '''
+sys.path.append('%(product_test_path)s')'''
+
+add_bt5_path_template = '''
+sys.argv.insert(1, '%(bt5_path)s')
+sys.argv.insert(1, '--bt5_path')'''
+

Added: experimental/erp5.buildout/recipes/setup.py
URL: http://svn.erp5.org/experimental/erp5.buildout/recipes/setup.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/recipes/setup.py (added)
+++ experimental/erp5.buildout/recipes/setup.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,9 @@
+from setuptools import setup
+
+setup(name="recipes",
+      version="0.0.1",
+      entry_points={'zc.buildout': ['zope2install = zope2install:Recipe',
+                            'create_erp5_site = create_erp5_site:Recipe',
+                            'run_unit_test = run_unit_test:Recipe']},
+      install_requires=['plone.recipe.zope2install',], )
+

Added: experimental/erp5.buildout/recipes/zope2install.py
URL: http://svn.erp5.org/experimental/erp5.buildout/recipes/zope2install.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/recipes/zope2install.py (added)
+++ experimental/erp5.buildout/recipes/zope2install.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,85 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+#   Contains code from plone.recipe.zope2install
+# 
+# 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.
+#
+##############################################################################
+import os, re, shutil, tempfile, urllib2, urlparse
+import setuptools.archive_util
+import plone.recipe.zope2install
+
+class Recipe(plone.recipe.zope2install.Recipe):
+
+    def __init__(self, buildout, name, options):
+        plone.recipe.zope2install.Recipe.__init__(self, buildout, name, options)
+        self.patch = os.path.join(self.buildout['buildout']['directory'],
+                                  options['patch'])
+
+    def install(self):
+        options = self.options
+        location = options['location']
+        download_dir = self.buildout['buildout']['download-cache']
+
+        if os.path.exists(location):
+            # if the zope installation exists and is shared, then we are done
+            # and don't return a path, so the shared installation doesn't get
+            # deleted on uninstall
+            if options.get('shared-zope') == 'true':
+                return []
+            else:
+                shutil.rmtree(location)
+
+        if self.svn:
+            assert os.system('svn co %s %s' % (options['svn'], location)
+                             ) == 0
+        else:
+            if not os.path.isdir(download_dir):
+                os.mkdir(download_dir)
+
+            _, _, urlpath, _, _, _ = urlparse.urlparse(self.url)
+            tmp = tempfile.mkdtemp('buildout-'+self.name)
+            try:
+                fname = os.path.join(download_dir, urlpath.split('/')[-1])
+                # Have we already downloaded the file
+                if not os.path.exists(fname):
+                    f = open(fname, 'wb')
+                    try:
+                        f.write(urllib2.urlopen(self.url).read())
+                    except:
+                        os.remove(fname)
+                        raise
+                    f.close()
+                
+                setuptools.archive_util.unpack_archive(fname, tmp)
+                # The Zope tarballs have a Zope-<version> folder at the root
+                # level, so we need to move that one into the right place.
+                files = os.listdir(tmp)
+                shutil.move(os.path.join(tmp, files[0]), location)
+            finally:
+                shutil.rmtree(tmp)
+
+        os.chdir(location)
+
+        # erp5: apply a patch to zope before build
+        assert os.system('patch -p1 < %s' % self.patch) == 0
+
+        assert os.spawnl(
+            os.P_WAIT, options['executable'], options['executable'],
+            'setup.py',
+            'build_ext', '-i',
+            ) == 0
+
+        if self.url and options.get('shared-zope') == 'true':
+            # don't return path if the installation is shared
+            return []
+        return location
+

Added: experimental/erp5.buildout/src/EXTERNALS.TXT
URL: http://svn.erp5.org/experimental/erp5.buildout/src/EXTERNALS.TXT?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/EXTERNALS.TXT (added)
+++ experimental/erp5.buildout/src/EXTERNALS.TXT [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,10 @@
+#
+# Used for maintenance of external resources in this svn bundle.  Edit
+# this file as appropriate and then run the following command from within
+# the checkout directory where this file lives on your local machine:
+#
+# svn propset svn:externals -F ./EXTERNALS.TXT .
+#
+
+timerserver https://svn.erp5.org/repos/public/erp5/trunk/products/TimerService/timerserver/
+erp5diff https://svn.erp5.org/repos/public/erp5/trunk/utils/erp5diff/

Propchange: experimental/erp5.buildout/src/EXTERNALS.TXT
------------------------------------------------------------------------------
    svn:eol-style = native

Added: experimental/erp5.buildout/src/erp5diff/ERP5Diff.py
URL: http://svn.erp5.org/experimental/erp5.buildout/src/erp5diff/ERP5Diff.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/erp5diff/ERP5Diff.py (added)
+++ experimental/erp5.buildout/src/erp5diff/ERP5Diff.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,544 @@
+##############################################################################
+#
+# Yoshinori OKUJI <yo at nexedi.com>
+#
+# Copyright (C) 2003 Nexedi SARL
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ?See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ?02111-1307, USA.
+#
+##############################################################################
+
+try:
+  from Ft.Xml import Parse as parse
+  from Ft.Xml.Domlette import NonvalidatingReader, PrettyPrint
+  parseString = NonvalidatingReader.parseString
+  from Ft.Xml.Domlette import implementation
+  from Ft.Xml import EMPTY_NAMESPACE
+  def getDOMImplementation():
+    return implementation
+except ImportError:
+  from xml.dom.minidom import parse, parseString
+  from xml.dom.minidom import getDOMImplementation
+  from xml.dom.ext import PrettyPrint
+  EMPTY_NAMESPACE = None
+
+import sys
+import getopt
+import os
+from StringIO import StringIO
+import re
+import codecs
+
+class ERP5Diff:
+  """
+    Make a difference between two XML documents using XUpdate.
+    Use some assumptions in ERP5's data representation.
+
+    The strategy is:
+      1. Find a matching element among elements of the other XML document at the same depth.
+      2. Use the first matching element, even if there can be other better elements.
+      3. Assume that two elements are matching, if the tag names are identical. If either of
+         them has an attribute 'id', the values of the attributes 'id' also must be identical.
+      4. Don't use xupdate:rename for elements. It should be quite rare to rename tag names
+         in ERP5, and it is too complicated to support this renaming.
+      5. Ignore some types of nodes, such as EntityReference and Comment, because they are not
+         used in ERP5 XML documents.
+  """
+  def __init__(self):
+    """
+      Initialize itself.
+    """
+    self._verbose = 0
+    self._result = None
+    self._ns = 'http://www.xmldb.org/xupdate'
+
+  def setVerbosity(self, verbose):
+    """
+      Set the verbosity.
+    """
+    self._verbose = verbose
+
+  def _p(self, msg):
+    """
+      Print a message only if being verbose.
+    """
+    if self._verbose:
+      sys.stderr.write(str(msg) + os.linesep)
+
+  def _makeDocList(self, *args):
+    """
+      Make a list of Document objects.
+    """
+    doc_list = []
+    for a in args:
+      if type(a) == type(''):
+        doc_list.append(parseString(a))
+      else:
+        doc_list.append(parse(a))
+    return doc_list
+
+  def _concatPath(self, p1, p2, separator='/'):
+    """
+      Concatenate 'p1' and 'p2'. Add a separator between them,
+      only if 'p1' does not end with a separator.
+    """
+    if p1.endswith(separator):
+      return p1 + p2
+    return p1 + separator + p2
+
+  def _getResultRoot(self):
+    """
+      Return the root element of the result document.
+    """
+    return self._result.documentElement
+
+  def _xupdateAppendAttributes(self, dict, path):
+    """
+      Append attributes to the element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    createTextNode = self._result.createTextNode
+    append_element = createElement(self._ns, 'xupdate:append')
+    append_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    for name, val in dict.iteritems():
+      attr_element = createElement(self._ns, 'xupdate:attribute')
+      attr_element.setAttributeNS(EMPTY_NAMESPACE, 'name', name)
+      text_node = createTextNode(val)
+      attr_element.appendChild(text_node)
+      append_element.appendChild(attr_element)
+    root.appendChild(append_element)
+
+  def _xupdateRemoveAttribute(self, name, path):
+    """
+      Remove an attribute from the element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    remove_element = createElement(self._ns, 'xupdate:remove')
+    remove_element.setAttributeNS(EMPTY_NAMESPACE, 'select', self._concatPath(path, 'attribute::' + name))
+    root.appendChild(remove_element)
+
+  def _xupdateUpdateAttribute(self, name, val, path):
+    """
+      Update the value of an attribute of the element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    createTextNode = self._result.createTextNode
+    update_element = createElement(self._ns, 'xupdate:update')
+    update_element.setAttributeNS(EMPTY_NAMESPACE, 'select', self._concatPath(path, 'attribute::' + name))
+    text_node = createTextNode(val)
+    update_element.appendChild(text_node)
+    root.appendChild(update_element)
+
+  def _xupdateRenameElement(self, name, path):
+    """
+      Rename an existing element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    createTextNode = self._result.createTextNode
+    rename_element = createElement(self._ns, 'xupdate:rename')
+    rename_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    text_node = createTextNode(name)
+    rename_element.appendChild(text_node)
+    root.appendChild(rename_element)
+
+  def _xupdateUpdateElement(self, element, path):
+    """
+      Update the contents of an element at 'path' to that of 'element'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    update_element = createElement(self._ns, 'xupdate:update')
+    update_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    for node in element.childNodes:
+      #self._p("node is %s" % repr(node))
+      clone_node = node.cloneNode(1)
+      update_element.appendChild(clone_node)
+    root.appendChild(update_element)
+
+  def _xupdateRemoveElement(self, path):
+    """
+      Remove an element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    remove_element = createElement(self._ns, 'xupdate:remove')
+    remove_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    root.appendChild(remove_element)
+
+  def _xupdateInsertBefore(self, element_list, path):
+    """
+      Insert elements before the element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    createTextNode = self._result.createTextNode
+    insert_element = createElement(self._ns, 'xupdate:insert-before')
+    insert_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    for element in element_list:
+      child_element = createElement(self._ns, 'xupdate:element')
+      child_element.setAttributeNS(EMPTY_NAMESPACE, 'name', element.tagName)
+      attr_map = element.attributes
+      for attr in attr_map.values():
+        attr_element = createElement(self._ns, 'xupdate:attribute')
+        attr_element.setAttributeNS(EMPTY_NAMESPACE, 'name', attr.name)
+        text_node = createTextNode(attr.nodeValue)
+        attr_element.appendChild(text_node)
+        child_element.appendChild(attr_element)
+      for child in element.childNodes:
+        clone_node = child.cloneNode(1)
+        child_element.appendChild(clone_node)
+      insert_element.appendChild(child_element)
+    root.appendChild(insert_element)
+
+  def _xupdateAppendElements(self, element_list, path):
+    """
+      Append elements to the element at 'path'.
+    """
+    root = self._getResultRoot()
+    createElement = self._result.createElementNS
+    createTextNode = self._result.createTextNode
+    append_element = createElement(self._ns, 'xupdate:append')
+    append_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    for element in element_list:
+      child_element = createElement(self._ns, 'xupdate:element')
+      child_element.setAttributeNS(EMPTY_NAMESPACE, 'name', element.tagName)
+      attr_map = element.attributes
+      for attr in attr_map.values():
+        attr_element = createElement(self._ns, 'xupdate:attribute')
+        attr_element.setAttributeNS(EMPTY_NAMESPACE, 'name', attr.name)
+        text_node = createTextNode(attr.nodeValue)
+        attr_element.appendChild(text_node)
+        child_element.appendChild(attr_element)
+      for child in element.childNodes:
+        clone_node = child.cloneNode(1)
+        child_element.appendChild(clone_node)
+      append_element.appendChild(child_element)
+    root.appendChild(append_element)
+
+  def _testElements(self, element1, element2):
+    """
+      Test if two given elements are matching. Matching does not mean that they are identical.
+    """
+    # Make sure that they are elements.
+    if element1.nodeType != element2.nodeType or element1.nodeType != element1.ELEMENT_NODE:
+      return 0
+
+    if element1.tagName != element2.tagName:
+      return 0
+
+    id_list = []
+    for attr_map in (element1.attributes, element2.attributes):
+      for attr in attr_map.values():
+        if attr.name == 'id':
+          id_list.append(attr.nodeValue)
+          break
+
+    if len(id_list) == 0:
+      return 1
+    if len(id_list) == 1:
+      return 0
+    return (id_list[0] == id_list[1])
+
+  def _testAttributes(self, element1, element2, path):
+    """
+      Test attributes of two given elements. Add differences, if any.
+    """
+    # Make a list of dictionaries of the attributes.
+    dict_list = []
+    for attr_map in (element1.attributes, element2.attributes):
+      dict = {}
+      for attr in attr_map.values():
+        dict[attr.name] = attr.nodeValue
+      dict_list.append(dict)
+    dict1, dict2 = dict_list
+
+    # Find all added or removed or changed attributes.
+    for name1, val1 in dict1.iteritems():
+      if name1 in dict2:
+        if val1 != dict2[name1]:
+          # The value is different.
+          self._xupdateUpdateAttribute(name1, dict2[name1], path)
+        # Mark this attribute.
+        dict2[name1] = None
+      else:
+        # This attribute is removed.
+        self._xupdateRemoveAttribute(name1, path)
+    dict = {}
+    for name2, val2 in dict2.iteritems():
+      if val2 is not None:
+        # This attribute is added.
+        dict[name2] = val2
+    if dict != {}:
+      self._xupdateAppendAttributes(dict, path)
+
+  def _checkEmptiness(self, element):
+    """
+      Check if an element has child values.
+    """
+    for child in element.childNodes:
+      if child.nodeType == child.ELEMENT_NODE or child.nodeType == child.TEXT_NODE:
+        return 0
+    return 1
+
+  def _checkIgnoreText(self, element):
+    """
+      Determine if text should be ignored by heuristics,
+      because ERP5 does not define any schema at the moment.
+    """
+    for child in element.childNodes:
+      if child.nodeType == child.ELEMENT_NODE:
+        return 1
+    return 0
+
+  def _makeRelativePathList(self, element_list):
+    """
+      Make a list of relative paths from a list of elements.
+    """
+    num_map = {}
+    count_map = {}
+    for element in element_list:
+      if element.tagName in num_map:
+        num_map[element.tagName] += 1
+      else:
+        num_map[element.tagName] = 1
+        count_map[element.tagName] = 0
+
+    path_list = []
+    for element in element_list:
+      # Check if this element has an attribute 'id'.
+      id_val = None
+      attr_map = element.attributes
+      for attr in attr_map.values():
+        if attr.name == 'id':
+          id_val = attr.nodeValue
+          break
+
+      if id_val is not None:
+        # If an attribute 'id' is present, uses the attribute for convenience.
+        path_list.append("%s[@id='%s']" % (element.tagName, id_val))
+        # Increase the count, for a case where other elements with the same tag name do not have
+        # 'id' attributes.
+        count_map[element.tagName] += 1
+      elif num_map[element.tagName] > 1:
+        path_list.append('%s[%d]' % (element.tagName, count_map[element.tagName]))
+        count_map[element.tagName] += 1
+      else:
+        path_list.append(element.tagName)
+
+    return path_list
+
+  def _aggregateElements(self, element):
+    """
+      Aggregate child elements of an element into a list.
+    """
+    element_list = []
+    for child in element.childNodes:
+      if child.nodeType == child.ELEMENT_NODE:
+        element_list.append(child)
+    return element_list
+
+  def _aggregateText(self, element):
+    """
+      Aggregate child text nodes of an element into a single string.
+    """
+    text = ''
+    for child in element.childNodes:
+      if child.nodeType == child.TEXT_NODE:
+        text += child.nodeValue
+    return text
+
+  def _compareChildNodes(self, old_element, new_element, path):
+    """
+      Compare children of two elements, and add differences into the result, if any.
+      Call itself recursively, if these elements have grandchilden.
+    """
+    self._p("Comparing %s with %s at %s..." % (repr(old_element), repr(new_element), path))
+
+    # First, determine if they are empty.
+    old_is_empty = self._checkEmptiness(old_element)
+    new_is_empty = self._checkEmptiness(new_element)
+
+    if old_is_empty and new_is_empty:
+      # Nothing to do.
+      self._p("Both are empty.")
+      pass
+    elif old_is_empty or new_is_empty:
+      # Perhaps they are very different.
+      self._p("One of them is empty, so just update all the contents.")
+      self._xupdateUpdateElement(new_element, path)
+    else:
+      # Second, determine if text should be ignored.
+      old_ignore_text = self._checkIgnoreText(old_element)
+      new_ignore_text = self._checkIgnoreText(new_element)
+
+      if old_ignore_text != new_ignore_text:
+        # This means that the semantics of this element is quite different.
+        self._p("One of them has only text and the other does not, so just update all the contents.")
+        self._xupdateUpdateElement(new_element, path)
+      elif not old_ignore_text:
+        # The contents are only text.
+        self._p("Both have only text.")
+        old_text = self._aggregateText(old_element)
+        new_text = self._aggregateText(new_element)
+        if old_text != new_text:
+          self._p("They differ, so update the elements.")
+          self._xupdateUpdateElement(new_element, path)
+      else:
+        # The contents are elements.
+        self._p("Both have elements.")
+        old_list = self._aggregateElements(old_element)
+        path_list = self._makeRelativePathList(old_list)
+        new_list = self._aggregateElements(new_element)
+        new_start = 0
+        new_len = len(new_list)
+        for old_node, node_path in zip(old_list, path_list):
+          child_path = self._concatPath(path, node_path)
+          for new_current in range(new_start, new_len):
+            new_node = new_list[new_current]
+            if self._testElements(old_node, new_node):
+              self._testAttributes(old_node, new_node, child_path)
+              self._compareChildNodes(old_node, new_node, child_path)
+              if new_current > new_start:
+                # There are skipped nodes in the new children.
+                self._xupdateInsertBefore(new_list[new_start:new_current], child_path)
+              new_start = new_current + 1
+              break
+          else:
+            # There is no matching node. So this element must be removed.
+            self._xupdateRemoveElement(child_path)
+        if new_len > new_start:
+          # There are remaining nodes in the new children.
+          self._xupdateAppendElements(new_list[new_start:new_len], path)
+
+  def compare(self, old_xml, new_xml):
+    """
+      Compare two given XML documents.
+      If an argument is a string, it is assumed to be a XML document itself.
+      Otherwise, it is assumed to be a file object which contains a XML document.
+    """
+    old_doc, new_doc = self._makeDocList(old_xml, new_xml)
+    old_root_element = old_doc.documentElement
+    new_root_element = new_doc.documentElement
+    try:
+      impl = getDOMImplementation()
+      # XXX this namespace argument won't be handled correctly in minidom.
+      # XXX So work around that problem when outputting the result.
+      if self._result is not None:
+        self._result.close()
+      self._result = impl.createDocument(self._ns, 'xupdate:modifications', None)
+      attr_version = self._result.createAttributeNS(EMPTY_NAMESPACE, 'version')
+      attr_version.value = '1.0'
+      self._result.documentElement.setAttributeNodeNS(attr_version)
+      if self._testElements(old_root_element, new_root_element):
+        self._testAttributes(old_root_element, new_root_element, '/')
+        self._compareChildNodes(old_root_element, new_root_element, '/')
+      else:
+        # These XML documents seem to be completely different...
+        if old_root_element.tagName != new_root_element.tagName:
+          self._xupdateRenameElement(new_root_element.tagName, '/')
+        self._testAttributes(old_root_element, new_root_element, '/')
+        self._xupdateUpdateElement(new_root_element, '/')
+    finally:
+      del old_doc
+      del new_doc
+
+  def output(self, file=None):
+    """
+      Output the result of parsing XML documents to 'file'.
+      If it is not specified, stdout is assumed.
+    """
+    if file is None:
+      file = sys.stdout
+
+    PrettyPrint(self._result.documentElement, stream=file, encoding='UTF-8')
+
+  def outputString(self):
+    """
+      Return the result as a string object.
+    """
+    io = StringIO()
+    self.output(io)
+    ret = io.getvalue()
+    io.close()
+    return ret
+
+def main():
+  """
+    The main routine of ERP5Diff.
+  """
+  try:
+    opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output=", "verbose"])
+  except getopt.GetoptError, msg:
+    print msg
+    print "Try ``erp5diff --help'' for more information."
+    sys.exit(2)
+  output = None
+  verbose = 0
+  for o, a in opts:
+    if o == "-v":
+      verbose = 1
+    elif o in ("-h", "--help"):
+      print '''Usage: erp5diff [OPTION]... OLD_XML NEW_XML
+Make a difference between two XML documents in XUpdate format.
+
+    -h, --help          display this message and exit
+    -o, --output=FILE   output the result to the file FILE
+    -v, --verbose       print verbose messages
+
+Report bugs to <yo at nexedi.com>.'''
+      sys.exit()
+    elif o in ("-o", "--output"):
+      output = a
+
+  if len(args) != 2:
+    if len(args) > 2:
+      print "Too many arguments."
+    else:
+      print "Too few arguments."
+    print "Try ``erp5diff --help'' for more information."
+    sys.exit(2)
+
+  d = ERP5Diff()
+  d.setVerbosity(verbose)
+
+  old_xml = open(args[0])
+  new_xml = open(args[1])
+  d.compare(old_xml, new_xml)
+  old_xml.close()
+  new_xml.close()
+
+  try:
+    if output is not None:
+      file = open(output, 'w')
+    else:
+      file = None
+    d.output(file)
+  except:
+    if output is not None:
+      file.close()
+      os.remove(output)
+    raise
+  else:
+    if file is not None:
+      file.close()
+
+  sys.exit()
+
+if __name__ == '__main__':
+  main()

Propchange: experimental/erp5.buildout/src/erp5diff/ERP5Diff.py
------------------------------------------------------------------------------
    svn:executable = 

Added: experimental/erp5.buildout/src/erp5diff/MAINTAINERS.txt
URL: http://svn.erp5.org/experimental/erp5.buildout/src/erp5diff/MAINTAINERS.txt?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/erp5diff/MAINTAINERS.txt (added)
+++ experimental/erp5.buildout/src/erp5diff/MAINTAINERS.txt [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,2 @@
+yo
+seb

Propchange: experimental/erp5.buildout/src/erp5diff/MAINTAINERS.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Added: experimental/erp5.buildout/src/erp5diff/README
URL: http://svn.erp5.org/experimental/erp5.buildout/src/erp5diff/README?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/erp5diff/README (added)
+++ experimental/erp5.buildout/src/erp5diff/README [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,17 @@
+This is a XUpdate Generator for ERP5.
+
+See <http://www.xmldb.org/xupdate/index.html> for information on
+XUpdate.
+
+See <http://erp5.org/> for information on ERP5.
+
+For the installation, do "python setup.py install".
+
+Once you have installed erp5diff, you can use "erp5diff" in a shell:
+$ erp5diff old.xml new.xml
+See the manpage erp5diff(1) or "erp5diff --help" for more information.
+
+Also, you can use the module ERP5Diff from your Python script.
+Do "pydoc ERP5Diff" for more information.
+
+- 2003-12-04, Yoshinori OKUJI <yo at nexedi.com>

Propchange: experimental/erp5.buildout/src/erp5diff/README
------------------------------------------------------------------------------
    svn:executable = 

Added: experimental/erp5.buildout/src/erp5diff/erp5diff
URL: http://svn.erp5.org/experimental/erp5.buildout/src/erp5diff/erp5diff?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/erp5diff/erp5diff (added)
+++ experimental/erp5.buildout/src/erp5diff/erp5diff [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,27 @@
+#! /usr/bin/python
+
+##############################################################################
+#
+# Yoshinori OKUJI <yo at nexedi.com>
+#
+# Copyright (C) 2003 Nexedi SARL
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ?See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ?02111-1307, USA.
+#
+##############################################################################
+
+from ERP5Diff import main
+
+main()

Propchange: experimental/erp5.buildout/src/erp5diff/erp5diff
------------------------------------------------------------------------------
    svn:executable = 

Added: experimental/erp5.buildout/src/erp5diff/erp5diff.1
URL: http://svn.erp5.org/experimental/erp5.buildout/src/erp5diff/erp5diff.1?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/erp5diff/erp5diff.1 (added)
+++ experimental/erp5.buildout/src/erp5diff/erp5diff.1 [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,32 @@
+.TH ERP5DIFF 1 "4 Dec 2003" "ERP5DIFF version 0.1" Nexedi
+.SH NAME
+erp5diff \- find differences between two XML documents for ERP5
+.SH SYNOPSIS
+.B erp5diff
+[\fIoptions\fR]...
+.LP
+.SH DESCRIPTION
+ERP5Diff is a XUpdate Generator for ERP5. It takes two XML files
+as input data, and generates differences between these two XML
+documents in XUpdate language.
+.LP
+ERP5Diff depends on more or less ERP5's XML data format. So this tool
+cannot be used for general purpose, but might work if your XML files
+are similar to ERP5's.
+.SH OPTIONS
+.TP
+\fB\-o\fR, \fB\-\-output\fR=\fIFILE\fR
+Specify the output file. The standard output is used by default.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display the usage and exit.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Print verbose messages. Only useful for debugging.
+.SH AUTHOR
+Yoshinori OKUJI <yo at nexedi.com>
+.SH "SEE ALSO"
+\fIhttp://www.xmldb.org/xupdate/index.html\fR,
+\fIhttp://www.w3.org/TR/xpath\fR,
+\fIhttp://www.w3.org/TR/REC-xml\fR,
+\fIhttp://erp5.org\fR

Propchange: experimental/erp5.buildout/src/erp5diff/erp5diff.1
------------------------------------------------------------------------------
    svn:executable = 

Added: experimental/erp5.buildout/src/erp5diff/setup.py
URL: http://svn.erp5.org/experimental/erp5.buildout/src/erp5diff/setup.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/erp5diff/setup.py (added)
+++ experimental/erp5.buildout/src/erp5diff/setup.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,15 @@
+#! /usr/bin/env python
+
+from distutils.core import setup
+
+setup(name="erp5diff",
+      version="0.1",
+      description="XUpdate Generator for ERP5",
+      author="Yoshinori OKUJI",
+      author_email="yo at nexedi.com",
+      url="http://nexedi.com",
+      license="GPL",
+      py_modules=["ERP5Diff"],
+      scripts=["erp5diff"],
+      data_files=[('share/man/man1', ['erp5diff.1'])]
+     )

Propchange: experimental/erp5.buildout/src/erp5diff/setup.py
------------------------------------------------------------------------------
    svn:executable = 

Added: experimental/erp5.buildout/src/timerserver/setup.py
URL: http://svn.erp5.org/experimental/erp5.buildout/src/timerserver/setup.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/timerserver/setup.py (added)
+++ experimental/erp5.buildout/src/timerserver/setup.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,35 @@
+
+modname = 'timerserver'
+version = open('version.txt').read().strip()
+numversion = version.split('.') 
+
+license = 'GPL'
+copyright = '''Nikolay Kim (c) 2004'''
+
+author = "Nikolay Kim"
+author_email = "fafhrd at legco.biz"
+
+short_desc = "Timer Server for Zope"
+long_desc = short_desc 
+
+web = ""
+ftp = ""
+mailing_list = ""
+#!/usr/bin/env python
+import sys
+try:
+  from setuptools import setup
+except ImportError:
+  from distutils.core import setup
+
+setup(name='timerserver',
+      version='2.0',
+      license='GPL',
+      description='Timer Server for Zope',
+      long_description='',
+      author='Nikolay Kim',
+      author_email='fafhrd at legco.biz',
+      packages=['timerserver'],
+      zip_safe=False,
+      package_data={'timerserver': ['component.xml']},
+    )

Added: experimental/erp5.buildout/src/timerserver/timerserver/TimerServer.py
URL: http://svn.erp5.org/experimental/erp5.buildout/src/timerserver/timerserver/TimerServer.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/timerserver/timerserver/TimerServer.py (added)
+++ experimental/erp5.buildout/src/timerserver/timerserver/TimerServer.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,147 @@
+# -*- coding: UTF-8 -*-
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# Authors: Nik Kim <fafhrd at legco.biz>
+__version__ = 'TimerServer for Zope 0.1'
+
+import traceback
+
+import thread
+import re
+import sys, os, errno, time, socket
+from StringIO import StringIO
+from zLOG import LOG, INFO
+
+from ZServer.PubCore import handle
+from ZPublisher.BaseRequest import BaseRequest
+from ZPublisher.BaseResponse import BaseResponse
+from ZPublisher.HTTPRequest import HTTPRequest
+import ZPublisher.HTTPRequest
+
+class TimerServer:
+    def __init__(self, module, interval=600):
+        self.module = module
+
+        self.interval = interval
+
+        sync = thread.allocate_lock()
+
+        self._a = sync.acquire
+        self._r = sync.release
+
+        self._a()
+        thread.start_new_thread(self.run, ())
+        self._r()
+
+        LOG('ZServer', INFO,
+            'Timer server started at %s\n'
+            '\tInterval: %s seconds.\n'%(time.ctime(time.time()), interval))
+
+    def run(self):
+        # wait until the zhttp_server exist in socket_map
+        # because TimerService has to be started after the Zope HTTPServer
+        from asyncore import socket_map
+        while 1:
+            time.sleep(5)
+            for k, v in socket_map.items():
+                if hasattr(v, 'port'):
+                        # see Zope/lib/python/App/ApplicationManager.py: def getServers(self)
+                        type = str(getattr(v, '__class__', 'unknown'))
+                        if type == 'ZServer.HTTPServer.zhttp_server':
+                            port = v.port
+                            break
+            if port:
+                break
+
+        ip = socket.gethostbyname(socket.gethostname())
+
+        # To be very sure, try to connect to the HTTPServer
+        # and only start after we are able to connect and got a response
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.settimeout(None)
+        while 1:
+            try:
+                s.connect((ip, port))
+            except socket.error:
+                time.sleep(5)
+                continue
+            s.send('GET / HTTP/1.1\r\n\r\n')
+            s.recv(4096) # blocks until a response is received
+            break
+        s.close()
+
+        module = self.module
+        interval = self.interval
+
+        # minutes = time.gmtime(time.time()[4], seconds = time.gmtime(time.time()[5]
+        # max interval is therefore 59*60 + 59 = 208919 seconds
+
+        wait = ((time.gmtime(time.time())[4] * 60) + time.gmtime(time.time())[5]) % interval
+        sleep = interval - wait
+
+        if sleep > 0:
+            time.sleep(sleep)
+
+        LOG('ZServer', INFO, 'Timerserver ready, starting timer services.')
+
+        while 1:
+            time.sleep(interval)
+            # send message to zope
+            try:
+                out = StringIO()
+                err = StringIO()
+                response = TimerResponse(out, err)
+                handle(module, TimerRequest(response, interval), response)
+            except:
+                pass
+
+
+class TimerResponse(BaseResponse):
+    def _finish(self):
+        pass
+
+    def unauthorized(self):
+        pass
+
+    # This is taken from ZPublisher.HTTPResponse
+    # I don't think it's safe to make TimerResponse a subclass of HTTPResponse,
+    # so I inline here the method . This is required it you want unicode page
+    # templates to be usable by timer service.
+    # This is used by an iHotFix patch on PageTemplate.StringIO method
+    def _encode_unicode(self, body,
+                        charset_re=re.compile(r'(?:application|text)/[-+0-9a-z]+\s*;\s*' +
+                                              r'charset=([-_0-9a-z]+' +
+                                              r')(?:(?:\s*;)|\Z)',
+                                              re.IGNORECASE)):
+        # Encode the Unicode data as requested
+        if self.headers.has_key('content-type'):
+            match = charset_re.match(self.headers['content-type'])
+            if match:
+                encoding = match.group(1)
+                return body.encode(encoding)
+        # Use the default character encoding
+        return body.encode(ZPublisher.HTTPResponse.default_encoding,'replace')
+
+
+class TimerRequest(HTTPRequest):
+
+    retry_max_count = 0
+
+    def __init__(self, response, interval):
+        stdin=StringIO()
+        environ=self._get_env(stdin)
+        HTTPRequest.__init__(self, stdin, environ, response, clean=1)
+
+        self.other['interval'] = interval
+
+    def _get_env(self, stdin):
+        "Returns a CGI style environment"
+        env={}
+        env['REQUEST_METHOD']='GET'
+        env['SERVER_SOFTWARE']= 'TimerServer for Zope'
+        env['SERVER_NAME'] = ''
+        env['SERVER_PORT'] = ''
+        env['REMOTE_ADDR'] = ''
+        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
+
+        env['PATH_INFO']= '/Control_Panel/timer_service/process_timer'
+        return env

Added: experimental/erp5.buildout/src/timerserver/timerserver/__init__.py
URL: http://svn.erp5.org/experimental/erp5.buildout/src/timerserver/timerserver/__init__.py?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/timerserver/timerserver/__init__.py (added)
+++ experimental/erp5.buildout/src/timerserver/timerserver/__init__.py [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,10 @@
+from ZServer.datatypes import ServerFactory
+
+class TimerServerFactory(ServerFactory):
+    def __init__(self, section):
+        ServerFactory.__init__(self)
+        self.interval = section.interval
+
+    def create(self):
+        from timerserver.TimerServer import TimerServer
+	return TimerServer(self.module, self.interval)

Added: experimental/erp5.buildout/src/timerserver/timerserver/component.xml
URL: http://svn.erp5.org/experimental/erp5.buildout/src/timerserver/timerserver/component.xml?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/timerserver/timerserver/component.xml (added)
+++ experimental/erp5.buildout/src/timerserver/timerserver/component.xml [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,14 @@
+<component>
+  <import package="ZServer" />
+  
+  <sectiontype name="timer-server"
+               datatype="timerserver.TimerServerFactory"
+               implements="ZServer.server">
+    <key name="interval" datatype="integer" default="600">
+      <description>
+	Interval in seconds.
+      </description>
+    </key>
+  </sectiontype>
+
+</component>

Added: experimental/erp5.buildout/src/timerserver/version.txt
URL: http://svn.erp5.org/experimental/erp5.buildout/src/timerserver/version.txt?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/src/timerserver/version.txt (added)
+++ experimental/erp5.buildout/src/timerserver/version.txt [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,1 @@
+0.2

Propchange: experimental/erp5.buildout/src/timerserver/version.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Added: experimental/erp5.buildout/templates/haproxy.cfg.default
URL: http://svn.erp5.org/experimental/erp5.buildout/templates/haproxy.cfg.default?rev=25895&view=auto
==============================================================================
--- experimental/erp5.buildout/templates/haproxy.cfg.default (added)
+++ experimental/erp5.buildout/templates/haproxy.cfg.default [utf8] Thu Mar  5 16:49:30 2009
@@ -1,0 +1,36 @@
+# this config needs haproxy-1.1.28 or haproxy-1.2.1
+
+global
+        log 127.0.0.1   local0
+        log 127.0.0.1   local1 notice
+        #log loghost    local0 info
+        maxconn %(maxconn)s
+        uid 99
+        gid 99
+        daemon
+        #debug
+        #quiet
+
+defaults
+        log             global
+        mode            http
+        option          httplog
+        option          dontlognull
+        option          redispatch
+        retries         %(retries)s
+        maxconn         %(maxconn)s
+        contimeout      5000
+        clitimeout      50000
+        srvtimeout      50000
+
+
+listen internal_access localhost:%(port)s
+  cookie  SERVERID insert
+  balance %(balance)s
+
+  %(rules)s
+
+  option httpchk %(httpchk)s
+
+  stats uri /haproxy
+  stats realm Global\ statistics




More information about the Erp5-report mailing list