[Checkins] SVN: GenericSetup/branches/wichert-events/ Cut a branch
where we can experiment with events
Wichert Akkerman
wichert at wiggy.net
Sun Nov 4 09:51:56 EST 2007
Log message for revision 81473:
Cut a branch where we can experiment with events
Changed:
A GenericSetup/branches/wichert-events/
D GenericSetup/branches/wichert-events/CHANGES.txt
A GenericSetup/branches/wichert-events/CHANGES.txt
D GenericSetup/branches/wichert-events/meta.zcml
A GenericSetup/branches/wichert-events/meta.zcml
D GenericSetup/branches/wichert-events/registry.py
A GenericSetup/branches/wichert-events/registry.py
A GenericSetup/branches/wichert-events/tests/test_stepzcml.py
D GenericSetup/branches/wichert-events/tests/test_zcml.py
A GenericSetup/branches/wichert-events/tests/test_zcml.py
D GenericSetup/branches/wichert-events/tool.py
A GenericSetup/branches/wichert-events/tool.py
D GenericSetup/branches/wichert-events/utils.py
A GenericSetup/branches/wichert-events/utils.py
D GenericSetup/branches/wichert-events/www/sutExportSteps.zpt
A GenericSetup/branches/wichert-events/www/sutExportSteps.zpt
D GenericSetup/branches/wichert-events/www/sutImportSteps.zpt
A GenericSetup/branches/wichert-events/www/sutImportSteps.zpt
D GenericSetup/branches/wichert-events/zcml.py
A GenericSetup/branches/wichert-events/zcml.py
-=-
Copied: GenericSetup/branches/wichert-events (from rev 81470, GenericSetup/trunk)
Deleted: GenericSetup/branches/wichert-events/CHANGES.txt
===================================================================
--- GenericSetup/trunk/CHANGES.txt 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/CHANGES.txt 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,209 +0,0 @@
-GenericSetup Product Changelog
-
- GenericSetup 1.3.3 (unreleased)
-
- - tool: Fixed toolset import handler not to initialize tools again, when
- they already exist in the site.
-
- GenericSetup 1.3.2 (unreleased)
-
- - Ignore import and export step handlers that we can not resolve.
-
- - Restore the import context after running steps from a profile
- so we do not break on nested calls.
-
- - components: Provide log output when purging utilities or adapters.
-
- - components: Fixed an undefined variable name in a log message.
-
-
- GenericSetup 1.3.1 (2007/08/08)
-
- - components: correct the object path for the site root to be the
- empty string.
-
- - components: Made output more diff friendly.
-
- - utils: Added warnings to old code.
- ImportConfiguratorBase and ExportConfiguratorBase will become deprecated
- as soon as GenericSetup itself no longer uses them. HandlerBase is now
- deprecated.
-
- - components: Added 'components_xmlconfig.html' form.
- This view allows to inspect and edit component registrations. It is also
- available under the ZMI tab 'manage_components'.
-
-
- GenericSetup 1.3 (2007/07/26)
-
- - components: Removed non-functional support for registering objects in
- nested folders. We only support objects available in the component
- registry's parent now. The component registry needs to be either
- acquisition wrapped or have a __parent__ pointer to get to the parent.
-
-
- GenericSetup 1.3-beta (2007/07/12)
-
- - Guard against situations where encoded text may be compared by the
- differ.
- (http://www.zope.org/Collectors/CMF/471)
-
- - Extend the ZCatalog import/export mechanism to allow removal of
- metadata columns in addition to adding them.
- (http://www.zope.org/Collectors/CMF/483)
-
- - Made sure we register Acquisition free objects as utilities in the
- components handler.
-
- - Profiles now support version numbers; setup tool tracks profile
- versions during upgrades.
-
- - Added support for nested 'upgradeStep' directives; expanded upgrade
- step registry into a real registry object and not just a dictionary.
-
- - Added support for 'metadata.xml' in the profile (read during
- profile registration) to register profile description, version,
- and dependencies.
-
- - Deprecated runImportStep and runAllImportSteps in favor of
- runImportStepFromProfile and runAllImportStepsFromProfile.
-
- - Merged CPS's upgradeStep ZCML directive, w/ corresponding tool support.
-
- - Added a "last imported" date to the list of extension profiles,
- and to the baseline profile.
-
- - Renamed the "Properties" tab to "Profiles".
-
- - Removed the 'create_report' decoy in the ZMI view methods: there was
- never any UI for passing any value other than the default, anyway, and
- the report objects are too useful to omit.
-
- - Refactored the "Properties" tab to separate baseline profiles from
- extension profiles, marking the option to reset the baseline as
- potentially dangerous for sites which already have one. Allow
- importing one or more extension profiles directly (all steps) from the
- "Properties" tab.
-
- - No longer read the toolset xml and update the toolset regustry on
- import context change. Doing this only during the toolset step import
- should be sufficient.
-
- - testing: The test base classes no longer set up any ZCML.
- This change is not backwards compatible. If you are using these base
- classes for testing custom handlers, you have to add the necessary ZCML
- setup and tear down. Using test layers is recommended.
-
- - Added support for importing/exporting Zope 3 component registries
- by folding in Hanno Schlichting's GSLocalAddons product.
-
-
- GenericSetup 1.2-beta (2006/09/20)
-
- - tool: Added support for uploading a tarball on the "Import" tab
- (i.e., one produced on the export tab).
-
- - docs: Added SampleSite demo product.
-
- - ProfileRegistry: Added 'registerProfile' ZCML directive.
- Using the old registerProfile method in initialize() is now deprecated.
- See doc/profiles.txt for details.
-
- - ProfileRegistry: 'product' should now be the module name.
- For backwards compatibility 'product' is still first looked up in
- Products before searching the default module search path.
-
- - ZCTextIndex handler: Fixed 'indexed_attr' import.
- (http://www.zope.org/Collectors/CMF/436)
-
- - docs: Added 'Registering Profiles' section to profiles.txt.
-
- - Added support for PageTemplate import/export, modeled closely after
- existing PythonScript support
-
- - The dependency sorting was highly reliant on steps being added in the
- right order to work. If import step A depends on import step B which
- depends on step C, and step C gets processed early, and they were
- processed in the order A, C, B, then the dependency order would be
- incorrect. This is now fixed by keeping tack of steps with unresolved
- dependencies, and trying again after inserting everything else.
-
- GenericSetup 1.1 (2006/04/16)
-
- - ZCatalog handler: Implemented the 'remove' directive for indexes.
- This allows to write extension profiles that remove or replace indexes.
-
- - getExportStepRegistry had the wrong security declaration
-
- GenericSetup 1.1-beta2 (2006/03/26)
-
- No changes - tag created to coincide with CMF 2.0.0-beta2
-
- GenericSetup 1.1-beta (2006/03/08)
-
- - Allowed subclasses of DAVAwareFileAdapter to override the filename
- in which the file is stored.
-
- - Added a doc directory including some basic documentation.
-
- - Made GenericSetup a standalone package independent of the CMF
-
- - Added 'for_' argument to profile registry operations.
- A profile may be registered and queried as appropriate to a specific
- site interface; the default value, 'None', indicates that the profile
- is relevant to any site. Note that this is essentially an adapter
- lookup; perhaps we should reimplement it so.
-
- - Forward ported changes from GenericSetup 0.11 and 0.12 (which were
- created in a separate repository).
-
- - A sequence property with the purge="False" attribute will not be
- purged, but merged (the sequences are treated as sets, which means
- that duplicates are removed). This is useful in extension profiles.
-
- - Don't export or purge read-only properties. Correctly purge
- non-deletable int/float properties.
-
- - Correctly quote XML on export.
-
- GenericSetup 1.0 (2005/09/23)
-
- - CVS tag: GenericSetup-1_0
-
- - Forward-ported i18n support from CMF 1.5 branch.
-
- - Forward ported BBB for old instances that stored properties as
- lists from CMFSetup.
-
- - Forward ported fix for tools with non unique IDs from CMFSetup.
-
- GenericSetup-0.12 (2005/08/29)
-
- - CVS tag: GenericSetup-0_12
-
- - Import requests now create reports (by default) which record any
- status messages generated by the profile's steps.
-
- GenericSetup-0.11 (2005/08/23)
-
- - CVS tag: GenericSetup-0_11
-
- - Added report of messages generated by import to the "Import" tab.
-
- - Consolidated ISetupContext implementation into base class,
- 'SetupContextBase'.
-
- - Added 'note', 'listNotes', and 'clearNotes' methods to ISetupContext,
- to allow plugins to record information about the state of the operation.
-
- GenericSetup 0.10 (2005/08/11)
-
- - CVS tag: GenericSetup-0_10
-
- - Added TarballImportContext, including full test suite.
-
- GenericSetup 0.9 (2005/08/08)
-
- - CVS tag: GenericSetup-0_9
-
- - Initial version, cut down from CMFSetup-1.5.3
Copied: GenericSetup/branches/wichert-events/CHANGES.txt (from rev 81471, GenericSetup/trunk/CHANGES.txt)
===================================================================
--- GenericSetup/branches/wichert-events/CHANGES.txt (rev 0)
+++ GenericSetup/branches/wichert-events/CHANGES.txt 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,211 @@
+GenericSetup Product Changelog
+
+ GenericSetup 1.4 (unreleased)
+
+ - Use zcml to register import and export steps.
+
+ - tool: Fixed toolset import handler not to initialize tools again, when
+ they already exist in the site.
+
+ GenericSetup 1.3.2 (unreleased)
+
+ - Ignore import and export step handlers that we can not resolve.
+
+ - Restore the import context after running steps from a profile
+ so we do not break on nested calls.
+
+ - components: Provide log output when purging utilities or adapters.
+
+ - components: Fixed an undefined variable name in a log message.
+
+
+ GenericSetup 1.3.1 (2007/08/08)
+
+ - components: correct the object path for the site root to be the
+ empty string.
+
+ - components: Made output more diff friendly.
+
+ - utils: Added warnings to old code.
+ ImportConfiguratorBase and ExportConfiguratorBase will become deprecated
+ as soon as GenericSetup itself no longer uses them. HandlerBase is now
+ deprecated.
+
+ - components: Added 'components_xmlconfig.html' form.
+ This view allows to inspect and edit component registrations. It is also
+ available under the ZMI tab 'manage_components'.
+
+
+ GenericSetup 1.3 (2007/07/26)
+
+ - components: Removed non-functional support for registering objects in
+ nested folders. We only support objects available in the component
+ registry's parent now. The component registry needs to be either
+ acquisition wrapped or have a __parent__ pointer to get to the parent.
+
+
+ GenericSetup 1.3-beta (2007/07/12)
+
+ - Guard against situations where encoded text may be compared by the
+ differ.
+ (http://www.zope.org/Collectors/CMF/471)
+
+ - Extend the ZCatalog import/export mechanism to allow removal of
+ metadata columns in addition to adding them.
+ (http://www.zope.org/Collectors/CMF/483)
+
+ - Made sure we register Acquisition free objects as utilities in the
+ components handler.
+
+ - Profiles now support version numbers; setup tool tracks profile
+ versions during upgrades.
+
+ - Added support for nested 'upgradeStep' directives; expanded upgrade
+ step registry into a real registry object and not just a dictionary.
+
+ - Added support for 'metadata.xml' in the profile (read during
+ profile registration) to register profile description, version,
+ and dependencies.
+
+ - Deprecated runImportStep and runAllImportSteps in favor of
+ runImportStepFromProfile and runAllImportStepsFromProfile.
+
+ - Merged CPS's upgradeStep ZCML directive, w/ corresponding tool support.
+
+ - Added a "last imported" date to the list of extension profiles,
+ and to the baseline profile.
+
+ - Renamed the "Properties" tab to "Profiles".
+
+ - Removed the 'create_report' decoy in the ZMI view methods: there was
+ never any UI for passing any value other than the default, anyway, and
+ the report objects are too useful to omit.
+
+ - Refactored the "Properties" tab to separate baseline profiles from
+ extension profiles, marking the option to reset the baseline as
+ potentially dangerous for sites which already have one. Allow
+ importing one or more extension profiles directly (all steps) from the
+ "Properties" tab.
+
+ - No longer read the toolset xml and update the toolset regustry on
+ import context change. Doing this only during the toolset step import
+ should be sufficient.
+
+ - testing: The test base classes no longer set up any ZCML.
+ This change is not backwards compatible. If you are using these base
+ classes for testing custom handlers, you have to add the necessary ZCML
+ setup and tear down. Using test layers is recommended.
+
+ - Added support for importing/exporting Zope 3 component registries
+ by folding in Hanno Schlichting's GSLocalAddons product.
+
+
+ GenericSetup 1.2-beta (2006/09/20)
+
+ - tool: Added support for uploading a tarball on the "Import" tab
+ (i.e., one produced on the export tab).
+
+ - docs: Added SampleSite demo product.
+
+ - ProfileRegistry: Added 'registerProfile' ZCML directive.
+ Using the old registerProfile method in initialize() is now deprecated.
+ See doc/profiles.txt for details.
+
+ - ProfileRegistry: 'product' should now be the module name.
+ For backwards compatibility 'product' is still first looked up in
+ Products before searching the default module search path.
+
+ - ZCTextIndex handler: Fixed 'indexed_attr' import.
+ (http://www.zope.org/Collectors/CMF/436)
+
+ - docs: Added 'Registering Profiles' section to profiles.txt.
+
+ - Added support for PageTemplate import/export, modeled closely after
+ existing PythonScript support
+
+ - The dependency sorting was highly reliant on steps being added in the
+ right order to work. If import step A depends on import step B which
+ depends on step C, and step C gets processed early, and they were
+ processed in the order A, C, B, then the dependency order would be
+ incorrect. This is now fixed by keeping tack of steps with unresolved
+ dependencies, and trying again after inserting everything else.
+
+ GenericSetup 1.1 (2006/04/16)
+
+ - ZCatalog handler: Implemented the 'remove' directive for indexes.
+ This allows to write extension profiles that remove or replace indexes.
+
+ - getExportStepRegistry had the wrong security declaration
+
+ GenericSetup 1.1-beta2 (2006/03/26)
+
+ No changes - tag created to coincide with CMF 2.0.0-beta2
+
+ GenericSetup 1.1-beta (2006/03/08)
+
+ - Allowed subclasses of DAVAwareFileAdapter to override the filename
+ in which the file is stored.
+
+ - Added a doc directory including some basic documentation.
+
+ - Made GenericSetup a standalone package independent of the CMF
+
+ - Added 'for_' argument to profile registry operations.
+ A profile may be registered and queried as appropriate to a specific
+ site interface; the default value, 'None', indicates that the profile
+ is relevant to any site. Note that this is essentially an adapter
+ lookup; perhaps we should reimplement it so.
+
+ - Forward ported changes from GenericSetup 0.11 and 0.12 (which were
+ created in a separate repository).
+
+ - A sequence property with the purge="False" attribute will not be
+ purged, but merged (the sequences are treated as sets, which means
+ that duplicates are removed). This is useful in extension profiles.
+
+ - Don't export or purge read-only properties. Correctly purge
+ non-deletable int/float properties.
+
+ - Correctly quote XML on export.
+
+ GenericSetup 1.0 (2005/09/23)
+
+ - CVS tag: GenericSetup-1_0
+
+ - Forward-ported i18n support from CMF 1.5 branch.
+
+ - Forward ported BBB for old instances that stored properties as
+ lists from CMFSetup.
+
+ - Forward ported fix for tools with non unique IDs from CMFSetup.
+
+ GenericSetup-0.12 (2005/08/29)
+
+ - CVS tag: GenericSetup-0_12
+
+ - Import requests now create reports (by default) which record any
+ status messages generated by the profile's steps.
+
+ GenericSetup-0.11 (2005/08/23)
+
+ - CVS tag: GenericSetup-0_11
+
+ - Added report of messages generated by import to the "Import" tab.
+
+ - Consolidated ISetupContext implementation into base class,
+ 'SetupContextBase'.
+
+ - Added 'note', 'listNotes', and 'clearNotes' methods to ISetupContext,
+ to allow plugins to record information about the state of the operation.
+
+ GenericSetup 0.10 (2005/08/11)
+
+ - CVS tag: GenericSetup-0_10
+
+ - Added TarballImportContext, including full test suite.
+
+ GenericSetup 0.9 (2005/08/08)
+
+ - CVS tag: GenericSetup-0_9
+
+ - Initial version, cut down from CMFSetup-1.5.3
Deleted: GenericSetup/branches/wichert-events/meta.zcml
===================================================================
--- GenericSetup/trunk/meta.zcml 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/meta.zcml 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,35 +0,0 @@
-<configure
- xmlns="http://namespaces.zope.org/zope"
- xmlns:meta="http://namespaces.zope.org/meta">
-
- <meta:directives namespace="http://namespaces.zope.org/genericsetup">
-
- <meta:directive
- name="registerProfile"
- schema=".zcml.IRegisterProfileDirective"
- handler=".zcml.registerProfile"
- />
-
- <meta:directive
- name="upgradeStep"
- schema=".zcml.IUpgradeStepDirective"
- handler=".zcml.upgradeStep"
- />
-
- <meta:complexDirective
- name="upgradeSteps"
- schema=".zcml.IUpgradeStepsDirective"
- handler=".zcml.upgradeSteps"
- >
-
- <meta:subdirective
- name="upgradeStep"
- schema=".zcml.IUpgradeStepsStepSubDirective"
- />
-
- </meta:complexDirective>
-
-
- </meta:directives>
-
-</configure>
Copied: GenericSetup/branches/wichert-events/meta.zcml (from rev 81471, GenericSetup/trunk/meta.zcml)
===================================================================
--- GenericSetup/branches/wichert-events/meta.zcml (rev 0)
+++ GenericSetup/branches/wichert-events/meta.zcml 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,53 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:meta="http://namespaces.zope.org/meta">
+
+ <meta:directives namespace="http://namespaces.zope.org/genericsetup">
+
+ <meta:directive
+ name="registerProfile"
+ schema=".zcml.IRegisterProfileDirective"
+ handler=".zcml.registerProfile"
+ />
+
+ <meta:directive
+ name="exportStep"
+ schema=".zcml.IExportStepDirective"
+ handler=".zcml.exportStep"
+ />
+ <meta:complexDirective
+ name="importStep"
+ schema=".zcml.IImportStepDirective"
+ handler=".zcml.importStep"
+ >
+
+ <meta:subdirective
+ name="depends"
+ schema=".zcml.IImportStepDependsDirective"
+ />
+
+ </meta:complexDirective>
+
+ <meta:directive
+ name="upgradeStep"
+ schema=".zcml.IUpgradeStepDirective"
+ handler=".zcml.upgradeStep"
+ />
+
+ <meta:complexDirective
+ name="upgradeSteps"
+ schema=".zcml.IUpgradeStepsDirective"
+ handler=".zcml.upgradeSteps"
+ >
+
+ <meta:subdirective
+ name="upgradeStep"
+ schema=".zcml.IUpgradeStepsStepSubDirective"
+ />
+
+ </meta:complexDirective>
+
+
+ </meta:directives>
+
+</configure>
Deleted: GenericSetup/branches/wichert-events/registry.py
===================================================================
--- GenericSetup/trunk/registry.py 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/registry.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,797 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-""" Classes: ImportStepRegistry, ExportStepRegistry
-
-$Id$
-"""
-import os
-from xml.sax import parseString
-from xml.sax.handler import ContentHandler
-
-from AccessControl import ClassSecurityInfo
-from Acquisition import Implicit
-from Globals import InitializeClass
-import App.Product
-from Products.PageTemplates.PageTemplateFile import PageTemplateFile
-from zope.interface import implements
-
-from interfaces import BASE
-from interfaces import IImportStepRegistry
-from interfaces import IExportStepRegistry
-from interfaces import IToolsetRegistry
-from interfaces import IProfileRegistry
-from permissions import ManagePortal
-from metadata import ProfileMetadata
-from utils import _xmldir
-from utils import _getDottedName
-from utils import _resolveDottedName
-from utils import _extractDocstring
-
-
-class BaseStepRegistry( Implicit ):
-
- security = ClassSecurityInfo()
-
- def __init__( self ):
-
- self.clear()
-
- security.declareProtected( ManagePortal, 'listSteps' )
- def listSteps( self ):
-
- """ Return a list of registered step IDs.
- """
- return self._registered.keys()
-
- security.declareProtected( ManagePortal, 'getStepMetadata' )
- def getStepMetadata( self, key, default=None ):
-
- """ Return a mapping of metadata for the step identified by 'key'.
-
- o Return 'default' if no such step is registered.
-
- o The 'handler' metadata is available via 'getStep'.
- """
- result = {}
-
- info = self._registered.get( key )
-
- if info is None:
- return default
-
- result = info.copy()
- result['invalid'] = _resolveDottedName( result[ 'handler' ] ) is None
-
- return result
-
- security.declareProtected( ManagePortal, 'listStepMetadata' )
- def listStepMetadata( self ):
-
- """ Return a sequence of mappings describing registered steps.
-
- o Mappings will be ordered alphabetically.
- """
- step_ids = self.listSteps()
- step_ids.sort()
- return [ self.getStepMetadata( x ) for x in step_ids ]
-
- security.declareProtected( ManagePortal, 'generateXML' )
- def generateXML( self ):
-
- """ Return a round-trippable XML representation of the registry.
-
- o 'handler' values are serialized using their dotted names.
- """
- return self._exportTemplate()
-
- security.declarePrivate( 'getStep' )
- def getStep( self, key, default=None ):
-
- """ Return the I(Export|Import)Plugin registered for 'key'.
-
- o Return 'default' if no such step is registered.
- """
- marker = object()
- info = self._registered.get( key, marker )
-
- if info is marker:
- return default
-
- return _resolveDottedName( info[ 'handler' ] )
-
- security.declarePrivate( 'clear' )
- def clear( self ):
-
- self._registered = {}
-
-InitializeClass( BaseStepRegistry )
-
-class ImportStepRegistry( BaseStepRegistry ):
-
- """ Manage knowledge about steps to create / configure site.
-
- o Steps are composed together to define a site profile.
- """
- implements(IImportStepRegistry)
-
- security = ClassSecurityInfo()
-
-
- security.declareProtected( ManagePortal, 'sortSteps' )
- def sortSteps( self ):
-
- """ Return a sequence of registered step IDs
-
- o Sequence is sorted topologically by dependency, with the dependent
- steps *after* the steps they depend on.
- """
- return self._computeTopologicalSort()
-
- security.declareProtected( ManagePortal, 'checkComplete' )
- def checkComplete( self ):
-
- """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
- """
- result = []
- seen = {}
-
- graph = self._computeTopologicalSort()
-
- for node in graph:
-
- dependencies = self.getStepMetadata( node )[ 'dependencies' ]
-
- for dependency in dependencies:
-
- if seen.get( dependency ) is None:
- result.append( ( node, dependency ) )
-
- seen[ node ] = 1
-
- return result
-
- security.declarePrivate( 'registerStep' )
- def registerStep( self
- , id
- , version
- , handler
- , dependencies=()
- , title=None
- , description=None
- ):
- """ Register a setup step.
-
- o 'id' is a unique name for this step,
-
- o 'version' is a string for comparing versions, it is preferred to
- be a yyyy/mm/dd-ii formatted string (date plus two-digit
- ordinal). when comparing two version strings, the version with
- the lower sort order is considered the older version.
-
- - Newer versions of a step supplant older ones.
-
- - Attempting to register an older one after a newer one results
- in a KeyError.
-
- o 'handler' is the dottoed name of a handler which should implement
- IImportPlugin.
-
- o 'dependencies' is a tuple of step ids which have to run before
- this step in order to be able to run at all. Registration of
- steps that have unmet dependencies are deferred until the
- dependencies have been registered.
-
- o 'title' is a one-line UI description for this step.
- If None, the first line of the documentation string of the handler
- is used, or the id if no docstring can be found.
-
- o 'description' is a one-line UI description for this step.
- If None, the remaining line of the documentation string of
- the handler is used, or default to ''.
- """
- already = self.getStepMetadata( id )
-
- if already and already[ 'version' ] > version:
- raise KeyError( 'Existing registration for step %s, version %s'
- % ( id, already[ 'version' ] ) )
-
- if not isinstance(handler, str):
- handler = _getDottedName( handler )
-
- if title is None or description is None:
-
- method = _resolveDottedName(handler)
- if method is None:
- t,d = id, ''
- else:
- t, d = _extractDocstring( method, id, '' )
-
- title = title or t
- description = description or d
-
- info = { 'id' : id
- , 'version' : version
- , 'handler' : handler
- , 'dependencies' : dependencies
- , 'title' : title
- , 'description' : description
- }
-
- self._registered[ id ] = info
-
- security.declarePrivate( 'parseXML' )
- def parseXML( self, text, encoding=None ):
-
- """ Parse 'text'.
- """
- reader = getattr( text, 'read', None )
-
- if reader is not None:
- text = reader()
-
- parser = _ImportStepRegistryParser( encoding )
- parseString( text, parser )
-
- return parser._parsed
-
- #
- # Helper methods
- #
- security.declarePrivate( '_computeTopologicalSort' )
- def _computeTopologicalSort( self ):
-
- result = []
-
- graph = [ ( x[ 'id' ], x[ 'dependencies' ] )
- for x in self._registered.values() ]
-
- unresolved = []
-
- while 1:
- for node, edges in graph:
-
- after = -1
- resolved = 0
-
- for edge in edges:
-
- if edge in result:
- resolved += 1
- after = max( after, result.index( edge ) )
-
- if len(edges) > resolved:
- unresolved.append((node, edges))
- else:
- result.insert( after + 1, node )
-
- if not unresolved:
- break
- if len(unresolved) == len(graph):
- # Nothing was resolved in this loop. There must be circular or
- # missing dependencies. Just add them to the end. We can't
- # raise an error, because checkComplete relies on this method.
- for node, edges in unresolved:
- result.append(node)
- break
- graph = unresolved
- unresolved = []
-
- return result
-
- security.declarePrivate( '_exportTemplate' )
- _exportTemplate = PageTemplateFile( 'isrExport.xml', _xmldir )
-
-InitializeClass( ImportStepRegistry )
-
-
-class ExportStepRegistry( BaseStepRegistry ):
-
- """ Registry of known site-configuration export steps.
-
- o Each step is registered with a unique id.
-
- o When called, with the portal object passed in as an argument,
- the step must return a sequence of three-tuples,
- ( 'data', 'content_type', 'filename' ), one for each file exported
- by the step.
-
- - 'data' is a string containing the file data;
-
- - 'content_type' is the MIME type of the data;
-
- - 'filename' is a suggested filename for use when downloading.
-
- """
- implements(IExportStepRegistry)
-
- security = ClassSecurityInfo()
-
- security.declarePrivate( 'registerStep' )
- def registerStep( self, id, handler, title=None, description=None ):
-
- """ Register an export step.
-
- o 'id' is the unique identifier for this step
-
- o 'handler' is the dottoed name of a handler which should implement
- IImportPlugin.
-
- o 'title' is a one-line UI description for this step.
- If None, the first line of the documentation string of the step
- is used, or the id if no docstring can be found.
-
- o 'description' is a one-line UI description for this step.
- If None, the remaining line of the documentation string of
- the step is used, or default to ''.
- """
- if not isinstance(handler, str):
- handler = _getDottedName( handler )
-
- if title is None or description is None:
-
- method = _resolveDottedName(handler)
- if method is None:
- t,d = id, ''
- else:
- t, d = _extractDocstring( method, id, '' )
-
- title = title or t
- description = description or d
-
- info = { 'id' : id
- , 'handler' : handler
- , 'title' : title
- , 'description' : description
- }
-
- self._registered[ id ] = info
-
- security.declarePrivate( 'parseXML' )
- def parseXML( self, text, encoding=None ):
-
- """ Parse 'text'.
- """
- reader = getattr( text, 'read', None )
-
- if reader is not None:
- text = reader()
-
- parser = _ExportStepRegistryParser( encoding )
- parseString( text, parser )
-
- return parser._parsed
-
- #
- # Helper methods
- #
- security.declarePrivate( '_exportTemplate' )
- _exportTemplate = PageTemplateFile( 'esrExport.xml', _xmldir )
-
-InitializeClass( ExportStepRegistry )
-
-class ToolsetRegistry( Implicit ):
-
- """ Track required / forbidden tools.
- """
- implements(IToolsetRegistry)
-
- security = ClassSecurityInfo()
- security.setDefaultAccess( 'allow' )
-
- def __init__( self ):
-
- self.clear()
-
- #
- # Toolset API
- #
- security.declareProtected( ManagePortal, 'listForbiddenTools' )
- def listForbiddenTools( self ):
-
- """ See IToolsetRegistry.
- """
- result = list( self._forbidden )
- result.sort()
- return result
-
- security.declareProtected( ManagePortal, 'addForbiddenTool' )
- def addForbiddenTool( self, tool_id ):
-
- """ See IToolsetRegistry.
- """
- if tool_id in self._forbidden:
- return
-
- if self._required.get( tool_id ) is not None:
- raise ValueError, 'Tool %s is required!' % tool_id
-
- self._forbidden.append( tool_id )
-
- security.declareProtected( ManagePortal, 'listRequiredTools' )
- def listRequiredTools( self ):
-
- """ See IToolsetRegistry.
- """
- result = list( self._required.keys() )
- result.sort()
- return result
-
- security.declareProtected( ManagePortal, 'getRequiredToolInfo' )
- def getRequiredToolInfo( self, tool_id ):
-
- """ See IToolsetRegistry.
- """
- return self._required[ tool_id ]
-
- security.declareProtected( ManagePortal, 'listRequiredToolInfo' )
- def listRequiredToolInfo( self ):
-
- """ See IToolsetRegistry.
- """
- return [ self.getRequiredToolInfo( x )
- for x in self.listRequiredTools() ]
-
- security.declareProtected( ManagePortal, 'addRequiredTool' )
- def addRequiredTool( self, tool_id, dotted_name ):
-
- """ See IToolsetRegistry.
- """
- if tool_id in self._forbidden:
- raise ValueError, "Forbidden tool ID: %s" % tool_id
-
- self._required[ tool_id ] = { 'id' : tool_id
- , 'class' : dotted_name
- }
-
- security.declareProtected( ManagePortal, 'generateXML' )
- def generateXML( self ):
-
- """ Pseudo API.
- """
- return self._toolsetConfig()
-
- security.declareProtected( ManagePortal, 'parseXML' )
- def parseXML( self, text, encoding=None ):
-
- """ Pseudo-API
- """
- reader = getattr( text, 'read', None )
-
- if reader is not None:
- text = reader()
-
- parser = _ToolsetParser( encoding )
- parseString( text, parser )
-
- for tool_id in parser._forbidden:
- self.addForbiddenTool( tool_id )
-
- for tool_id, dotted_name in parser._required.items():
- self.addRequiredTool( tool_id, dotted_name )
-
- security.declarePrivate( 'clear' )
- def clear( self ):
-
- self._forbidden = []
- self._required = {}
-
- #
- # Helper methods.
- #
- security.declarePrivate( '_toolsetConfig' )
- _toolsetConfig = PageTemplateFile( 'tscExport.xml'
- , _xmldir
- , __name__='toolsetConfig'
- )
-
-InitializeClass( ToolsetRegistry )
-
-
-class ProfileRegistry( Implicit ):
-
- """ Track registered profiles.
- """
- implements(IProfileRegistry)
-
- security = ClassSecurityInfo()
- security.setDefaultAccess( 'allow' )
-
- def __init__( self ):
-
- self.clear()
-
- security.declareProtected( ManagePortal, 'getProfileInfo' )
- def getProfileInfo( self, profile_id, for_=None ):
-
- """ See IProfileRegistry.
- """
- result = self._profile_info[ profile_id ]
- if for_ is not None:
- if not issubclass( for_, result['for'] ):
- raise KeyError, profile_id
- return result.copy()
-
- security.declareProtected( ManagePortal, 'listProfiles' )
- def listProfiles( self, for_=None ):
-
- """ See IProfileRegistry.
- """
- result = []
- for profile_id in self._profile_ids:
- info = self.getProfileInfo( profile_id )
- if for_ is None or issubclass( for_, info['for'] ):
- result.append( profile_id )
- return tuple( result )
-
- security.declareProtected( ManagePortal, 'listProfileInfo' )
- def listProfileInfo( self, for_=None ):
-
- """ See IProfileRegistry.
- """
- candidates = [ self.getProfileInfo( id )
- for id in self.listProfiles() ]
- return [ x for x in candidates if for_ is None or x['for'] is None or
- issubclass( for_, x['for'] ) ]
-
- security.declareProtected( ManagePortal, 'registerProfile' )
- def registerProfile( self
- , name
- , title
- , description
- , path
- , product=None
- , profile_type=BASE
- , for_=None
- ):
- """ See IProfileRegistry.
- """
- profile_id = '%s:%s' % (product or 'other', name)
- if self._profile_info.get( profile_id ) is not None:
- raise KeyError, 'Duplicate profile ID: %s' % profile_id
-
- self._profile_ids.append( profile_id )
-
- info = { 'id' : profile_id
- , 'title' : title
- , 'description' : description
- , 'path' : path
- , 'product' : product
- , 'type': profile_type
- , 'for': for_
- }
-
- metadata = ProfileMetadata( path )()
-
- version = metadata.get( 'version', None )
- if version is None and product is not None:
- prod_name = product.split('.')[-1]
- prod_module = getattr(App.Product.Products, prod_name, None)
- if prod_module is not None:
- prod_path = prod_module.__path__[0]
-
- # Retrieve version number from any suitable version.txt
- for fname in ('version.txt', 'VERSION.txt', 'VERSION.TXT'):
- try:
- fpath = os.path.join( prod_path, fname )
- fhandle = open( fpath, 'r' )
- version = fhandle.read().strip()
- fhandle.close()
- break
- except IOError:
- continue
-
- if version is not None:
- metadata[ 'version' ] = version
-
- # metadata.xml description trumps ZCML description... awkward
- info.update( metadata )
-
- self._profile_info[ profile_id ] = info
-
- security.declarePrivate( 'clear' )
- def clear( self ):
-
- self._profile_info = {}
- self._profile_ids = []
-
-InitializeClass( ProfileRegistry )
-
-_profile_registry = ProfileRegistry()
-
-
-#
-# XML parser
-#
-
-class _HandlerBase(ContentHandler):
-
- _MARKER = object()
-
- def _extract(self, attrs, key):
- result = attrs.get(key, self._MARKER)
-
- if result is self._MARKER:
- return None
-
- return self._encode(result)
-
- def _encode(self, content):
- if self._encoding is None:
- return content
-
- return content.encode(self._encoding)
-
-
-class _ImportStepRegistryParser(_HandlerBase):
-
- security = ClassSecurityInfo()
- security.declareObjectPrivate()
- security.setDefaultAccess( 'deny' )
-
- def __init__( self, encoding ):
-
- self._encoding = encoding
- self._started = False
- self._pending = None
- self._parsed = []
-
- def startElement( self, name, attrs ):
-
- if name == 'import-steps':
-
- if self._started:
- raise ValueError, 'Duplicated setup-steps element: %s' % name
-
- self._started = True
-
- elif name == 'import-step':
-
- if self._pending is not None:
- raise ValueError, 'Cannot nest setup-step elements'
-
- self._pending = dict( [ ( k, self._extract( attrs, k ) )
- for k in attrs.keys() ] )
-
- self._pending[ 'dependencies' ] = []
-
- elif name == 'dependency':
-
- if not self._pending:
- raise ValueError, 'Dependency outside of step'
-
- depended = self._extract( attrs, 'step' )
- self._pending[ 'dependencies' ].append( depended )
-
- else:
- raise ValueError, 'Unknown element %s' % name
-
- def characters( self, content ):
-
- if self._pending is not None:
- content = self._encode( content )
- self._pending.setdefault( 'description', [] ).append( content )
-
- def endElement(self, name):
-
- if name == 'import-steps':
- pass
-
- elif name == 'import-step':
-
- if self._pending is None:
- raise ValueError, 'No pending step!'
-
- deps = tuple( self._pending[ 'dependencies' ] )
- self._pending[ 'dependencies' ] = deps
-
- desc = ''.join( self._pending[ 'description' ] )
- self._pending[ 'description' ] = desc
-
- self._parsed.append( self._pending )
- self._pending = None
-
-InitializeClass( _ImportStepRegistryParser )
-
-
-class _ExportStepRegistryParser(_HandlerBase):
-
- security = ClassSecurityInfo()
- security.declareObjectPrivate()
- security.setDefaultAccess( 'deny' )
-
- def __init__( self, encoding ):
-
- self._encoding = encoding
- self._started = False
- self._pending = None
- self._parsed = []
-
- def startElement( self, name, attrs ):
-
- if name == 'export-steps':
-
- if self._started:
- raise ValueError, 'Duplicated export-steps element: %s' % name
-
- self._started = True
-
- elif name == 'export-step':
-
- if self._pending is not None:
- raise ValueError, 'Cannot nest export-step elements'
-
- self._pending = dict( [ ( k, self._extract( attrs, k ) )
- for k in attrs.keys() ] )
-
- else:
- raise ValueError, 'Unknown element %s' % name
-
- def characters( self, content ):
-
- if self._pending is not None:
- content = self._encode( content )
- self._pending.setdefault( 'description', [] ).append( content )
-
- def endElement(self, name):
-
- if name == 'export-steps':
- pass
-
- elif name == 'export-step':
-
- if self._pending is None:
- raise ValueError, 'No pending step!'
-
- desc = ''.join( self._pending[ 'description' ] )
- self._pending[ 'description' ] = desc
-
- self._parsed.append( self._pending )
- self._pending = None
-
-InitializeClass( _ExportStepRegistryParser )
-
-
-class _ToolsetParser(_HandlerBase):
-
- security = ClassSecurityInfo()
- security.declareObjectPrivate()
- security.setDefaultAccess( 'deny' )
-
- def __init__( self, encoding ):
-
- self._encoding = encoding
- self._required = {}
- self._forbidden = []
-
- def startElement( self, name, attrs ):
-
- if name == 'tool-setup':
- pass
-
- elif name == 'forbidden':
-
- tool_id = self._extract( attrs, 'tool_id' )
-
- if tool_id not in self._forbidden:
- self._forbidden.append( tool_id )
-
- elif name == 'required':
-
- tool_id = self._extract( attrs, 'tool_id' )
- dotted_name = self._extract( attrs, 'class' )
- self._required[ tool_id ] = dotted_name
-
- else:
- raise ValueError, 'Unknown element %s' % name
-
-InitializeClass( _ToolsetParser )
Copied: GenericSetup/branches/wichert-events/registry.py (from rev 81471, GenericSetup/trunk/registry.py)
===================================================================
--- GenericSetup/branches/wichert-events/registry.py (rev 0)
+++ GenericSetup/branches/wichert-events/registry.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,770 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+""" Classes: ImportStepRegistry, ExportStepRegistry
+
+$Id$
+"""
+import os
+from xml.sax import parseString
+from xml.sax.handler import ContentHandler
+
+from AccessControl import ClassSecurityInfo
+from Acquisition import Implicit
+from Globals import InitializeClass
+import App.Product
+from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+from zope.interface import implements
+
+from interfaces import BASE
+from interfaces import IImportStepRegistry
+from interfaces import IExportStepRegistry
+from interfaces import IToolsetRegistry
+from interfaces import IProfileRegistry
+from permissions import ManagePortal
+from metadata import ProfileMetadata
+from utils import _xmldir
+from utils import _getDottedName
+from utils import _resolveDottedName
+from utils import _extractDocstring
+from utils import _computeTopologicalSort
+
+
+class BaseStepRegistry( Implicit ):
+
+ security = ClassSecurityInfo()
+
+ def __init__( self ):
+
+ self.clear()
+
+ security.declareProtected( ManagePortal, 'listSteps' )
+ def listSteps( self ):
+
+ """ Return a list of registered step IDs.
+ """
+ return self._registered.keys()
+
+ security.declareProtected( ManagePortal, 'getStepMetadata' )
+ def getStepMetadata( self, key, default=None ):
+
+ """ Return a mapping of metadata for the step identified by 'key'.
+
+ o Return 'default' if no such step is registered.
+
+ o The 'handler' metadata is available via 'getStep'.
+ """
+ result = {}
+
+ info = self._registered.get( key )
+
+ if info is None:
+ return default
+
+ result = info.copy()
+ result['invalid'] = _resolveDottedName( result[ 'handler' ] ) is None
+
+ return result
+
+ security.declareProtected( ManagePortal, 'listStepMetadata' )
+ def listStepMetadata( self ):
+
+ """ Return a sequence of mappings describing registered steps.
+
+ o Mappings will be ordered alphabetically.
+ """
+ step_ids = self.listSteps()
+ step_ids.sort()
+ return [ self.getStepMetadata( x ) for x in step_ids ]
+
+ security.declareProtected( ManagePortal, 'generateXML' )
+ def generateXML( self ):
+
+ """ Return a round-trippable XML representation of the registry.
+
+ o 'handler' values are serialized using their dotted names.
+ """
+ return self._exportTemplate()
+
+ security.declarePrivate( 'getStep' )
+ def getStep( self, key, default=None ):
+
+ """ Return the I(Export|Import)Plugin registered for 'key'.
+
+ o Return 'default' if no such step is registered.
+ """
+ marker = object()
+ info = self._registered.get( key, marker )
+
+ if info is marker:
+ return default
+
+ return _resolveDottedName( info[ 'handler' ] )
+
+ security.declarePrivate( 'unregisterStep' )
+ def unregisterStep( self, id ):
+ del self._registered[id]
+
+ security.declarePrivate( 'clear' )
+ def clear( self ):
+
+ self._registered = {}
+
+InitializeClass( BaseStepRegistry )
+
+class ImportStepRegistry( BaseStepRegistry ):
+
+ """ Manage knowledge about steps to create / configure site.
+
+ o Steps are composed together to define a site profile.
+ """
+ implements(IImportStepRegistry)
+
+ security = ClassSecurityInfo()
+
+
+ security.declareProtected( ManagePortal, 'sortSteps' )
+ def sortSteps( self ):
+
+ """ Return a sequence of registered step IDs
+
+ o Sequence is sorted topologically by dependency, with the dependent
+ steps *after* the steps they depend on.
+ """
+ return self._computeTopologicalSort()
+
+ security.declareProtected( ManagePortal, 'checkComplete' )
+ def checkComplete( self ):
+
+ """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
+ """
+ result = []
+ seen = {}
+
+ graph = self._computeTopologicalSort()
+
+ for node in graph:
+
+ dependencies = self.getStepMetadata( node )[ 'dependencies' ]
+
+ for dependency in dependencies:
+
+ if seen.get( dependency ) is None:
+ result.append( ( node, dependency ) )
+
+ seen[ node ] = 1
+
+ return result
+
+ security.declarePrivate( 'registerStep' )
+ def registerStep( self
+ , id
+ , version
+ , handler
+ , dependencies=()
+ , title=None
+ , description=None
+ ):
+ """ Register a setup step.
+
+ o 'id' is a unique name for this step,
+
+ o 'version' is a string for comparing versions, it is preferred to
+ be a yyyy/mm/dd-ii formatted string (date plus two-digit
+ ordinal). when comparing two version strings, the version with
+ the lower sort order is considered the older version.
+
+ - Newer versions of a step supplant older ones.
+
+ - Attempting to register an older one after a newer one results
+ in a KeyError.
+
+ o 'handler' is the dottoed name of a handler which should implement
+ IImportPlugin.
+
+ o 'dependencies' is a tuple of step ids which have to run before
+ this step in order to be able to run at all. Registration of
+ steps that have unmet dependencies are deferred until the
+ dependencies have been registered.
+
+ o 'title' is a one-line UI description for this step.
+ If None, the first line of the documentation string of the handler
+ is used, or the id if no docstring can be found.
+
+ o 'description' is a one-line UI description for this step.
+ If None, the remaining line of the documentation string of
+ the handler is used, or default to ''.
+ """
+ already = self.getStepMetadata( id )
+
+ if already and already[ 'version' ] > version:
+ raise KeyError( 'Existing registration for step %s, version %s'
+ % ( id, already[ 'version' ] ) )
+
+ if not isinstance(handler, str):
+ handler = _getDottedName( handler )
+
+ if title is None or description is None:
+
+ method = _resolveDottedName(handler)
+ if method is None:
+ t,d = id, ''
+ else:
+ t, d = _extractDocstring( method, id, '' )
+
+ title = title or t
+ description = description or d
+
+ info = { 'id' : id
+ , 'version' : version
+ , 'handler' : handler
+ , 'dependencies' : dependencies
+ , 'title' : title
+ , 'description' : description
+ }
+
+ self._registered[ id ] = info
+
+ security.declarePrivate( 'parseXML' )
+ def parseXML( self, text, encoding=None ):
+
+ """ Parse 'text'.
+ """
+ reader = getattr( text, 'read', None )
+
+ if reader is not None:
+ text = reader()
+
+ parser = _ImportStepRegistryParser( encoding )
+ parseString( text, parser )
+
+ return parser._parsed
+
+ #
+ # Helper methods
+ #
+ security.declarePrivate( '_computeTopologicalSort' )
+ def _computeTopologicalSort( self ):
+ return _computeTopologicalSort(self._registered.values())
+
+
+ security.declarePrivate( '_exportTemplate' )
+ _exportTemplate = PageTemplateFile( 'isrExport.xml', _xmldir )
+
+InitializeClass( ImportStepRegistry )
+
+_import_step_registry = ImportStepRegistry()
+
+class ExportStepRegistry( BaseStepRegistry ):
+
+ """ Registry of known site-configuration export steps.
+
+ o Each step is registered with a unique id.
+
+ o When called, with the portal object passed in as an argument,
+ the step must return a sequence of three-tuples,
+ ( 'data', 'content_type', 'filename' ), one for each file exported
+ by the step.
+
+ - 'data' is a string containing the file data;
+
+ - 'content_type' is the MIME type of the data;
+
+ - 'filename' is a suggested filename for use when downloading.
+
+ """
+ implements(IExportStepRegistry)
+
+ security = ClassSecurityInfo()
+
+ security.declarePrivate( 'registerStep' )
+ def registerStep( self, id, handler, title=None, description=None ):
+
+ """ Register an export step.
+
+ o 'id' is the unique identifier for this step
+
+ o 'handler' is the dottoed name of a handler which should implement
+ IImportPlugin.
+
+ o 'title' is a one-line UI description for this step.
+ If None, the first line of the documentation string of the step
+ is used, or the id if no docstring can be found.
+
+ o 'description' is a one-line UI description for this step.
+ If None, the remaining line of the documentation string of
+ the step is used, or default to ''.
+ """
+ if not isinstance(handler, str):
+ handler = _getDottedName( handler )
+
+ if title is None or description is None:
+
+ method = _resolveDottedName(handler)
+ if method is None:
+ t,d = id, ''
+ else:
+ t, d = _extractDocstring( method, id, '' )
+
+ title = title or t
+ description = description or d
+
+ info = { 'id' : id
+ , 'handler' : handler
+ , 'title' : title
+ , 'description' : description
+ }
+
+ self._registered[ id ] = info
+
+ security.declarePrivate( 'parseXML' )
+ def parseXML( self, text, encoding=None ):
+
+ """ Parse 'text'.
+ """
+ reader = getattr( text, 'read', None )
+
+ if reader is not None:
+ text = reader()
+
+ parser = _ExportStepRegistryParser( encoding )
+ parseString( text, parser )
+
+ return parser._parsed
+
+ #
+ # Helper methods
+ #
+ security.declarePrivate( '_exportTemplate' )
+ _exportTemplate = PageTemplateFile( 'esrExport.xml', _xmldir )
+
+InitializeClass( ExportStepRegistry )
+
+_export_step_registry = ExportStepRegistry()
+
+
+class ToolsetRegistry( Implicit ):
+
+ """ Track required / forbidden tools.
+ """
+ implements(IToolsetRegistry)
+
+ security = ClassSecurityInfo()
+ security.setDefaultAccess( 'allow' )
+
+ def __init__( self ):
+
+ self.clear()
+
+ #
+ # Toolset API
+ #
+ security.declareProtected( ManagePortal, 'listForbiddenTools' )
+ def listForbiddenTools( self ):
+
+ """ See IToolsetRegistry.
+ """
+ result = list( self._forbidden )
+ result.sort()
+ return result
+
+ security.declareProtected( ManagePortal, 'addForbiddenTool' )
+ def addForbiddenTool( self, tool_id ):
+
+ """ See IToolsetRegistry.
+ """
+ if tool_id in self._forbidden:
+ return
+
+ if self._required.get( tool_id ) is not None:
+ raise ValueError, 'Tool %s is required!' % tool_id
+
+ self._forbidden.append( tool_id )
+
+ security.declareProtected( ManagePortal, 'listRequiredTools' )
+ def listRequiredTools( self ):
+
+ """ See IToolsetRegistry.
+ """
+ result = list( self._required.keys() )
+ result.sort()
+ return result
+
+ security.declareProtected( ManagePortal, 'getRequiredToolInfo' )
+ def getRequiredToolInfo( self, tool_id ):
+
+ """ See IToolsetRegistry.
+ """
+ return self._required[ tool_id ]
+
+ security.declareProtected( ManagePortal, 'listRequiredToolInfo' )
+ def listRequiredToolInfo( self ):
+
+ """ See IToolsetRegistry.
+ """
+ return [ self.getRequiredToolInfo( x )
+ for x in self.listRequiredTools() ]
+
+ security.declareProtected( ManagePortal, 'addRequiredTool' )
+ def addRequiredTool( self, tool_id, dotted_name ):
+
+ """ See IToolsetRegistry.
+ """
+ if tool_id in self._forbidden:
+ raise ValueError, "Forbidden tool ID: %s" % tool_id
+
+ self._required[ tool_id ] = { 'id' : tool_id
+ , 'class' : dotted_name
+ }
+
+ security.declareProtected( ManagePortal, 'generateXML' )
+ def generateXML( self ):
+
+ """ Pseudo API.
+ """
+ return self._toolsetConfig()
+
+ security.declareProtected( ManagePortal, 'parseXML' )
+ def parseXML( self, text, encoding=None ):
+
+ """ Pseudo-API
+ """
+ reader = getattr( text, 'read', None )
+
+ if reader is not None:
+ text = reader()
+
+ parser = _ToolsetParser( encoding )
+ parseString( text, parser )
+
+ for tool_id in parser._forbidden:
+ self.addForbiddenTool( tool_id )
+
+ for tool_id, dotted_name in parser._required.items():
+ self.addRequiredTool( tool_id, dotted_name )
+
+ security.declarePrivate( 'clear' )
+ def clear( self ):
+
+ self._forbidden = []
+ self._required = {}
+
+ #
+ # Helper methods.
+ #
+ security.declarePrivate( '_toolsetConfig' )
+ _toolsetConfig = PageTemplateFile( 'tscExport.xml'
+ , _xmldir
+ , __name__='toolsetConfig'
+ )
+
+InitializeClass( ToolsetRegistry )
+
+
+class ProfileRegistry( Implicit ):
+
+ """ Track registered profiles.
+ """
+ implements(IProfileRegistry)
+
+ security = ClassSecurityInfo()
+ security.setDefaultAccess( 'allow' )
+
+ def __init__( self ):
+
+ self.clear()
+
+ security.declareProtected( ManagePortal, 'getProfileInfo' )
+ def getProfileInfo( self, profile_id, for_=None ):
+
+ """ See IProfileRegistry.
+ """
+ result = self._profile_info[ profile_id ]
+ if for_ is not None:
+ if not issubclass( for_, result['for'] ):
+ raise KeyError, profile_id
+ return result.copy()
+
+ security.declareProtected( ManagePortal, 'listProfiles' )
+ def listProfiles( self, for_=None ):
+
+ """ See IProfileRegistry.
+ """
+ result = []
+ for profile_id in self._profile_ids:
+ info = self.getProfileInfo( profile_id )
+ if for_ is None or issubclass( for_, info['for'] ):
+ result.append( profile_id )
+ return tuple( result )
+
+ security.declareProtected( ManagePortal, 'listProfileInfo' )
+ def listProfileInfo( self, for_=None ):
+
+ """ See IProfileRegistry.
+ """
+ candidates = [ self.getProfileInfo( id )
+ for id in self.listProfiles() ]
+ return [ x for x in candidates if for_ is None or x['for'] is None or
+ issubclass( for_, x['for'] ) ]
+
+ security.declareProtected( ManagePortal, 'registerProfile' )
+ def registerProfile( self
+ , name
+ , title
+ , description
+ , path
+ , product=None
+ , profile_type=BASE
+ , for_=None
+ ):
+ """ See IProfileRegistry.
+ """
+ profile_id = '%s:%s' % (product or 'other', name)
+ if self._profile_info.get( profile_id ) is not None:
+ raise KeyError, 'Duplicate profile ID: %s' % profile_id
+
+ self._profile_ids.append( profile_id )
+
+ info = { 'id' : profile_id
+ , 'title' : title
+ , 'description' : description
+ , 'path' : path
+ , 'product' : product
+ , 'type': profile_type
+ , 'for': for_
+ }
+
+ metadata = ProfileMetadata( path )()
+
+ version = metadata.get( 'version', None )
+ if version is None and product is not None:
+ prod_name = product.split('.')[-1]
+ prod_module = getattr(App.Product.Products, prod_name, None)
+ if prod_module is not None:
+ prod_path = prod_module.__path__[0]
+
+ # Retrieve version number from any suitable version.txt
+ for fname in ('version.txt', 'VERSION.txt', 'VERSION.TXT'):
+ try:
+ fpath = os.path.join( prod_path, fname )
+ fhandle = open( fpath, 'r' )
+ version = fhandle.read().strip()
+ fhandle.close()
+ break
+ except IOError:
+ continue
+
+ if version is not None:
+ metadata[ 'version' ] = version
+
+ # metadata.xml description trumps ZCML description... awkward
+ info.update( metadata )
+
+ self._profile_info[ profile_id ] = info
+
+ security.declarePrivate( 'clear' )
+ def clear( self ):
+
+ self._profile_info = {}
+ self._profile_ids = []
+
+InitializeClass( ProfileRegistry )
+
+_profile_registry = ProfileRegistry()
+
+
+#
+# XML parser
+#
+
+class _HandlerBase(ContentHandler):
+
+ _MARKER = object()
+
+ def _extract(self, attrs, key):
+ result = attrs.get(key, self._MARKER)
+
+ if result is self._MARKER:
+ return None
+
+ return self._encode(result)
+
+ def _encode(self, content):
+ if self._encoding is None:
+ return content
+
+ return content.encode(self._encoding)
+
+
+class _ImportStepRegistryParser(_HandlerBase):
+
+ security = ClassSecurityInfo()
+ security.declareObjectPrivate()
+ security.setDefaultAccess( 'deny' )
+
+ def __init__( self, encoding ):
+
+ self._encoding = encoding
+ self._started = False
+ self._pending = None
+ self._parsed = []
+
+ def startElement( self, name, attrs ):
+
+ if name == 'import-steps':
+
+ if self._started:
+ raise ValueError, 'Duplicated setup-steps element: %s' % name
+
+ self._started = True
+
+ elif name == 'import-step':
+
+ if self._pending is not None:
+ raise ValueError, 'Cannot nest setup-step elements'
+
+ self._pending = dict( [ ( k, self._extract( attrs, k ) )
+ for k in attrs.keys() ] )
+
+ self._pending[ 'dependencies' ] = []
+
+ elif name == 'dependency':
+
+ if not self._pending:
+ raise ValueError, 'Dependency outside of step'
+
+ depended = self._extract( attrs, 'step' )
+ self._pending[ 'dependencies' ].append( depended )
+
+ else:
+ raise ValueError, 'Unknown element %s' % name
+
+ def characters( self, content ):
+
+ if self._pending is not None:
+ content = self._encode( content )
+ self._pending.setdefault( 'description', [] ).append( content )
+
+ def endElement(self, name):
+
+ if name == 'import-steps':
+ pass
+
+ elif name == 'import-step':
+
+ if self._pending is None:
+ raise ValueError, 'No pending step!'
+
+ deps = tuple( self._pending[ 'dependencies' ] )
+ self._pending[ 'dependencies' ] = deps
+
+ desc = ''.join( self._pending[ 'description' ] )
+ self._pending[ 'description' ] = desc
+
+ self._parsed.append( self._pending )
+ self._pending = None
+
+InitializeClass( _ImportStepRegistryParser )
+
+
+class _ExportStepRegistryParser(_HandlerBase):
+
+ security = ClassSecurityInfo()
+ security.declareObjectPrivate()
+ security.setDefaultAccess( 'deny' )
+
+ def __init__( self, encoding ):
+
+ self._encoding = encoding
+ self._started = False
+ self._pending = None
+ self._parsed = []
+
+ def startElement( self, name, attrs ):
+
+ if name == 'export-steps':
+
+ if self._started:
+ raise ValueError, 'Duplicated export-steps element: %s' % name
+
+ self._started = True
+
+ elif name == 'export-step':
+
+ if self._pending is not None:
+ raise ValueError, 'Cannot nest export-step elements'
+
+ self._pending = dict( [ ( k, self._extract( attrs, k ) )
+ for k in attrs.keys() ] )
+
+ else:
+ raise ValueError, 'Unknown element %s' % name
+
+ def characters( self, content ):
+
+ if self._pending is not None:
+ content = self._encode( content )
+ self._pending.setdefault( 'description', [] ).append( content )
+
+ def endElement(self, name):
+
+ if name == 'export-steps':
+ pass
+
+ elif name == 'export-step':
+
+ if self._pending is None:
+ raise ValueError, 'No pending step!'
+
+ desc = ''.join( self._pending[ 'description' ] )
+ self._pending[ 'description' ] = desc
+
+ self._parsed.append( self._pending )
+ self._pending = None
+
+InitializeClass( _ExportStepRegistryParser )
+
+
+class _ToolsetParser(_HandlerBase):
+
+ security = ClassSecurityInfo()
+ security.declareObjectPrivate()
+ security.setDefaultAccess( 'deny' )
+
+ def __init__( self, encoding ):
+
+ self._encoding = encoding
+ self._required = {}
+ self._forbidden = []
+
+ def startElement( self, name, attrs ):
+
+ if name == 'tool-setup':
+ pass
+
+ elif name == 'forbidden':
+
+ tool_id = self._extract( attrs, 'tool_id' )
+
+ if tool_id not in self._forbidden:
+ self._forbidden.append( tool_id )
+
+ elif name == 'required':
+
+ tool_id = self._extract( attrs, 'tool_id' )
+ dotted_name = self._extract( attrs, 'class' )
+ self._required[ tool_id ] = dotted_name
+
+ else:
+ raise ValueError, 'Unknown element %s' % name
+
+InitializeClass( _ToolsetParser )
Copied: GenericSetup/branches/wichert-events/tests/test_stepzcml.py (from rev 81471, GenericSetup/trunk/tests/test_stepzcml.py)
===================================================================
--- GenericSetup/branches/wichert-events/tests/test_stepzcml.py (rev 0)
+++ GenericSetup/branches/wichert-events/tests/test_stepzcml.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""Unit tests for import/export step zcml module.
+"""
+import unittest
+from Products.GenericSetup.zcml import cleanUpImportSteps
+import Products.GenericSetup
+from Products.GenericSetup.registry import _import_step_registry
+from Products.GenericSetup.testing import ExportImportZCMLLayer
+from Products.Five import zcml
+
+EMPTY_ZCML = '''<configure xmlns:genericsetup="http://namespaces.zope.org/genericsetup">
+</configure>'''
+
+ONE_STEP_ZCML = '''<configure xmlns:genericsetup="http://namespaces.zope.org/genericsetup" i18n_domain="genericsetup">
+<genericsetup:importStep
+ name="Products.GenericSetup.teststep"
+ title="step title"
+ description="step description"
+ handler="Products.GenericSetup.initialize"
+ version="1.0"
+ />
+</configure>'''
+
+class ImportStepTests(unittest.TestCase):
+ layer = ExportImportZCMLLayer
+
+ def setUp(self):
+ zcml.load_config('meta.zcml', Products.GenericSetup)
+
+ def tearDown(self):
+ cleanUpImportSteps()
+
+ def testEmptyImport(self):
+ zcml.load_string(EMPTY_ZCML)
+ self.assertEqual(_import_step_registry._registered, {})
+
+ def testOneStepImport(self):
+ zcml.load_string(ONE_STEP_ZCML)
+ self.assertEqual(_import_step_registry._registered.keys(),
+ [ u'Products.GenericSetup.teststep' ])
+ info = _import_step_registry._registered[ u'Products.GenericSetup.teststep' ]
+ self.assertEqual( info['description'],
+ u'step description' )
+ self.assertEqual( info['title'],
+ u'step title' )
+ self.assertEqual( info['handler'],
+ 'Products.GenericSetup.initialize')
+ self.assertEqual( info['version'],
+ u'1.0' )
+ self.assertEqual( info['id'],
+ u'Products.GenericSetup.teststep' )
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(ImportStepTests),
+ ))
+
+if __name__ == '__main__':
+ from Products.GenericSetup.testing import run
+ run(test_suite())
Deleted: GenericSetup/branches/wichert-events/tests/test_zcml.py
===================================================================
--- GenericSetup/trunk/tests/test_zcml.py 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/tests/test_zcml.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,239 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-"""Unit tests for zcml module.
-
-$Id$
-"""
-
-import unittest
-import Testing
-from zope.testing import doctest
-from zope.testing.doctest import ELLIPSIS
-
-def dummy_upgrade_handler(context):
- pass
-
-def b_dummy_upgrade_handler(context):
- pass
-
-def c_dummy_upgrade_handler(context):
- pass
-
-def test_registerProfile():
- """
- Use the genericsetup:registerProfile directive::
-
- >>> import Products.GenericSetup
- >>> from Products.Five import zcml
- >>> configure_zcml = '''
- ... <configure
- ... xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
- ... i18n_domain="foo">
- ... <genericsetup:registerProfile
- ... name="default"
- ... title="Install Foo Extension"
- ... description="Adds foo support."
- ... provides="Products.GenericSetup.interfaces.EXTENSION"
- ... />
- ... </configure>'''
- >>> zcml.load_config('meta.zcml', Products.GenericSetup)
- >>> zcml.load_string(configure_zcml)
-
- Make sure the profile is registered correctly::
-
- >>> from Products.GenericSetup.registry import _profile_registry
- >>> profile_id = 'Products.GenericSetup:default'
- >>> profile_id in _profile_registry._profile_ids
- True
- >>> info = _profile_registry._profile_info[profile_id]
- >>> info['id']
- u'Products.GenericSetup:default'
- >>> info['title']
- u'Install Foo Extension'
- >>> info['description']
- u'Adds foo support.'
- >>> info['path']
- u'profiles/default'
- >>> info['product']
- 'Products.GenericSetup'
- >>> from Products.GenericSetup.interfaces import EXTENSION
- >>> info['type'] is EXTENSION
- True
- >>> info['for'] is None
- True
-
- Clean up and make sure the cleanup works::
-
- >>> from zope.testing.cleanup import cleanUp
- >>> cleanUp()
- >>> profile_id in _profile_registry._profile_ids
- False
- >>> profile_id in _profile_registry._profile_info
- False
- """
-
-def test_registerUpgradeStep(self):
- """
- Use the genericsetup:upgradeStep directive::
-
- >>> import Products.GenericSetup
- >>> from Products.Five import zcml
- >>> configure_zcml = '''
- ... <configure
- ... xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
- ... i18n_domain="foo">
- ... <genericsetup:upgradeStep
- ... title="Upgrade Foo Product"
- ... description="Upgrades Foo from 1.0 to 1.1."
- ... source="1.0"
- ... destination="1.1"
- ... handler="Products.GenericSetup.tests.test_zcml.dummy_upgrade_handler"
- ... sortkey="1"
- ... profile="default"
- ... />
- ... </configure>'''
- >>> zcml.load_config('meta.zcml', Products.GenericSetup)
- >>> zcml.load_string(configure_zcml)
-
- Make sure the upgrade step is registered correctly::
-
- >>> from Products.GenericSetup.upgrade import _upgrade_registry
- >>> profile_steps = _upgrade_registry.getUpgradeStepsForProfile('default')
- >>> keys = profile_steps.keys()
- >>> len(keys)
- 1
- >>> step = profile_steps[keys[0]]
- >>> step.source
- ('1', '0')
- >>> step.dest
- ('1', '1')
- >>> step.handler
- <function dummy_upgrade_handler at ...>
-
- Clean up and make sure the cleanup works::
-
- >>> from zope.testing.cleanup import cleanUp
- >>> cleanUp()
- """
-
-
-def test_registerUpgradeSteps(self):
- """
- Use the nested genericsetup:upgradeSteps directive::
-
- >>> import Products.GenericSetup
- >>> from Products.Five import zcml
- >>> configure_zcml = '''
- ... <configure
- ... xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
- ... i18n_domain="foo">
- ... <genericsetup:upgradeSteps
- ... profile="default"
- ... source="1.0"
- ... destination="1.1"
- ... sortkey="2"
- ... >
- ... <genericsetup:upgradeStep
- ... title="Foo Upgrade Step 1"
- ... description="Does some Foo upgrade thing."
- ... handler="Products.GenericSetup.tests.test_zcml.b_dummy_upgrade_handler"
- ... />
- ... <genericsetup:upgradeStep
- ... title="Foo Upgrade Step 2"
- ... description="Does another Foo upgrade thing."
- ... handler="Products.GenericSetup.tests.test_zcml.c_dummy_upgrade_handler"
- ... />
- ... </genericsetup:upgradeSteps>
- ... <genericsetup:upgradeSteps
- ... profile="default"
- ... source="1.0"
- ... destination="1.1"
- ... sortkey="1"
- ... >
- ... <genericsetup:upgradeStep
- ... title="Bar Upgrade Step 1"
- ... description="Does some Bar upgrade thing."
- ... handler="Products.GenericSetup.tests.test_zcml.b_dummy_upgrade_handler"
- ... />
- ... <genericsetup:upgradeStep
- ... title="Bar Upgrade Step 2"
- ... description="Does another Bar upgrade thing."
- ... handler="Products.GenericSetup.tests.test_zcml.c_dummy_upgrade_handler"
- ... />
- ... </genericsetup:upgradeSteps>
- ... </configure>'''
- >>> zcml.load_config('meta.zcml', Products.GenericSetup)
- >>> zcml.load_string(configure_zcml)
-
- Make sure the upgrade steps are registered correctly::
-
- >>> from Products.GenericSetup.upgrade import _upgrade_registry
- >>> from Products.GenericSetup.upgrade import listUpgradeSteps
- >>> from Products.GenericSetup.tool import SetupTool
- >>> tool = SetupTool('setup_tool')
- >>> profile_steps = listUpgradeSteps(tool, 'default', '1.0')
- >>> len(profile_steps)
- 2
- >>> steps = profile_steps[0]
- >>> type(steps)
- <type 'list'>
- >>> len(steps)
- 2
- >>> step1, step2 = steps
- >>> step1['source'] == step2['source'] == ('1', '0')
- True
- >>> step1['dest'] == step2['dest'] == ('1', '1')
- True
- >>> step1['step'].handler
- <function b_dummy_upgrade_handler at ...>
- >>> step1['title']
- u'Bar Upgrade Step 1'
- >>> step2['step'].handler
- <function c_dummy_upgrade_handler at ...>
- >>> step2['title']
- u'Bar Upgrade Step 2'
-
- First one listed should be second in the registry due to sortkey:
-
- >>> steps = profile_steps[1]
- >>> type(steps)
- <type 'list'>
- >>> len(steps)
- 2
- >>> step1, step2 = steps
- >>> step1['source'] == step2['source'] == ('1', '0')
- True
- >>> step1['dest'] == step2['dest'] == ('1', '1')
- True
- >>> step1['step'].handler
- <function b_dummy_upgrade_handler at ...>
- >>> step1['title']
- u'Foo Upgrade Step 1'
- >>> step2['step'].handler
- <function c_dummy_upgrade_handler at ...>
- >>> step2['title']
- u'Foo Upgrade Step 2'
-
- Clean up and make sure the cleanup works::
-
- >>> from zope.testing.cleanup import cleanUp
- >>> cleanUp()
- """
-
-def test_suite():
- return unittest.TestSuite((
- doctest.DocTestSuite(optionflags=ELLIPSIS),
- ))
-
-if __name__ == '__main__':
- unittest.main(defaultTest='test_suite')
Copied: GenericSetup/branches/wichert-events/tests/test_zcml.py (from rev 81471, GenericSetup/trunk/tests/test_zcml.py)
===================================================================
--- GenericSetup/branches/wichert-events/tests/test_zcml.py (rev 0)
+++ GenericSetup/branches/wichert-events/tests/test_zcml.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,335 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 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.
+#
+##############################################################################
+"""Unit tests for zcml module.
+
+$Id$
+"""
+
+import unittest
+import Testing
+from zope.testing import doctest
+from zope.testing.doctest import ELLIPSIS
+
+from Products.GenericSetup.testing import ExportImportZCMLLayer
+from Products.GenericSetup.zcml import cleanUpImportSteps
+from Products.GenericSetup.zcml import cleanUpExportSteps
+from Products.GenericSetup.registry import _import_step_registry
+from Products.GenericSetup.registry import _export_step_registry
+from Products.Five import zcml
+
+def dummy_importstep_handler(context):
+ pass
+
+def dummy_exportstep_handler(context):
+ pass
+
+def dummy_upgrade_handler(context):
+ pass
+
+def b_dummy_upgrade_handler(context):
+ pass
+
+def c_dummy_upgrade_handler(context):
+ pass
+
+def test_registerProfile():
+ """
+ Use the genericsetup:registerProfile directive::
+
+ >>> import Products.GenericSetup
+ >>> from Products.Five import zcml
+ >>> configure_zcml = '''
+ ... <configure
+ ... xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ ... i18n_domain="foo">
+ ... <genericsetup:registerProfile
+ ... name="default"
+ ... title="Install Foo Extension"
+ ... description="Adds foo support."
+ ... provides="Products.GenericSetup.interfaces.EXTENSION"
+ ... />
+ ... </configure>'''
+ >>> zcml.load_config('meta.zcml', Products.GenericSetup)
+ >>> zcml.load_string(configure_zcml)
+
+ Make sure the profile is registered correctly::
+
+ >>> from Products.GenericSetup.registry import _profile_registry
+ >>> profile_id = 'Products.GenericSetup:default'
+ >>> profile_id in _profile_registry._profile_ids
+ True
+ >>> info = _profile_registry._profile_info[profile_id]
+ >>> info['id']
+ u'Products.GenericSetup:default'
+ >>> info['title']
+ u'Install Foo Extension'
+ >>> info['description']
+ u'Adds foo support.'
+ >>> info['path']
+ u'profiles/default'
+ >>> info['product']
+ 'Products.GenericSetup'
+ >>> from Products.GenericSetup.interfaces import EXTENSION
+ >>> info['type'] is EXTENSION
+ True
+ >>> info['for'] is None
+ True
+
+ Clean up and make sure the cleanup works::
+
+ >>> from zope.testing.cleanup import cleanUp
+ >>> cleanUp()
+ >>> profile_id in _profile_registry._profile_ids
+ False
+ >>> profile_id in _profile_registry._profile_info
+ False
+ """
+
+def test_registerUpgradeStep(self):
+ """
+ Use the genericsetup:upgradeStep directive::
+
+ >>> import Products.GenericSetup
+ >>> from Products.Five import zcml
+ >>> configure_zcml = '''
+ ... <configure
+ ... xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ ... i18n_domain="foo">
+ ... <genericsetup:upgradeStep
+ ... title="Upgrade Foo Product"
+ ... description="Upgrades Foo from 1.0 to 1.1."
+ ... source="1.0"
+ ... destination="1.1"
+ ... handler="Products.GenericSetup.tests.test_zcml.dummy_upgrade_handler"
+ ... sortkey="1"
+ ... profile="default"
+ ... />
+ ... </configure>'''
+ >>> zcml.load_config('meta.zcml', Products.GenericSetup)
+ >>> zcml.load_string(configure_zcml)
+
+ Make sure the upgrade step is registered correctly::
+
+ >>> from Products.GenericSetup.upgrade import _upgrade_registry
+ >>> profile_steps = _upgrade_registry.getUpgradeStepsForProfile('default')
+ >>> keys = profile_steps.keys()
+ >>> len(keys)
+ 1
+ >>> step = profile_steps[keys[0]]
+ >>> step.source
+ ('1', '0')
+ >>> step.dest
+ ('1', '1')
+ >>> step.handler
+ <function dummy_upgrade_handler at ...>
+
+ Clean up and make sure the cleanup works::
+
+ >>> from zope.testing.cleanup import cleanUp
+ >>> cleanUp()
+ """
+
+
+def test_registerUpgradeSteps(self):
+ """
+ Use the nested genericsetup:upgradeSteps directive::
+
+ >>> import Products.GenericSetup
+ >>> from Products.Five import zcml
+ >>> configure_zcml = '''
+ ... <configure
+ ... xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ ... i18n_domain="foo">
+ ... <genericsetup:upgradeSteps
+ ... profile="default"
+ ... source="1.0"
+ ... destination="1.1"
+ ... sortkey="2"
+ ... >
+ ... <genericsetup:upgradeStep
+ ... title="Foo Upgrade Step 1"
+ ... description="Does some Foo upgrade thing."
+ ... handler="Products.GenericSetup.tests.test_zcml.b_dummy_upgrade_handler"
+ ... />
+ ... <genericsetup:upgradeStep
+ ... title="Foo Upgrade Step 2"
+ ... description="Does another Foo upgrade thing."
+ ... handler="Products.GenericSetup.tests.test_zcml.c_dummy_upgrade_handler"
+ ... />
+ ... </genericsetup:upgradeSteps>
+ ... <genericsetup:upgradeSteps
+ ... profile="default"
+ ... source="1.0"
+ ... destination="1.1"
+ ... sortkey="1"
+ ... >
+ ... <genericsetup:upgradeStep
+ ... title="Bar Upgrade Step 1"
+ ... description="Does some Bar upgrade thing."
+ ... handler="Products.GenericSetup.tests.test_zcml.b_dummy_upgrade_handler"
+ ... />
+ ... <genericsetup:upgradeStep
+ ... title="Bar Upgrade Step 2"
+ ... description="Does another Bar upgrade thing."
+ ... handler="Products.GenericSetup.tests.test_zcml.c_dummy_upgrade_handler"
+ ... />
+ ... </genericsetup:upgradeSteps>
+ ... </configure>'''
+ >>> zcml.load_config('meta.zcml', Products.GenericSetup)
+ >>> zcml.load_string(configure_zcml)
+
+ Make sure the upgrade steps are registered correctly::
+
+ >>> from Products.GenericSetup.upgrade import _upgrade_registry
+ >>> from Products.GenericSetup.upgrade import listUpgradeSteps
+ >>> from Products.GenericSetup.tool import SetupTool
+ >>> tool = SetupTool('setup_tool')
+ >>> profile_steps = listUpgradeSteps(tool, 'default', '1.0')
+ >>> len(profile_steps)
+ 2
+ >>> steps = profile_steps[0]
+ >>> type(steps)
+ <type 'list'>
+ >>> len(steps)
+ 2
+ >>> step1, step2 = steps
+ >>> step1['source'] == step2['source'] == ('1', '0')
+ True
+ >>> step1['dest'] == step2['dest'] == ('1', '1')
+ True
+ >>> step1['step'].handler
+ <function b_dummy_upgrade_handler at ...>
+ >>> step1['title']
+ u'Bar Upgrade Step 1'
+ >>> step2['step'].handler
+ <function c_dummy_upgrade_handler at ...>
+ >>> step2['title']
+ u'Bar Upgrade Step 2'
+
+ First one listed should be second in the registry due to sortkey:
+
+ >>> steps = profile_steps[1]
+ >>> type(steps)
+ <type 'list'>
+ >>> len(steps)
+ 2
+ >>> step1, step2 = steps
+ >>> step1['source'] == step2['source'] == ('1', '0')
+ True
+ >>> step1['dest'] == step2['dest'] == ('1', '1')
+ True
+ >>> step1['step'].handler
+ <function b_dummy_upgrade_handler at ...>
+ >>> step1['title']
+ u'Foo Upgrade Step 1'
+ >>> step2['step'].handler
+ <function c_dummy_upgrade_handler at ...>
+ >>> step2['title']
+ u'Foo Upgrade Step 2'
+
+ Clean up and make sure the cleanup works::
+
+ >>> from zope.testing.cleanup import cleanUp
+ >>> cleanUp()
+ """
+
+
+class ImportStepTests(unittest.TestCase):
+ layer = ExportImportZCMLLayer
+
+ def tearDown(self):
+ cleanUpImportSteps()
+
+ def testNoDependencies(self):
+ zcml.load_string("""<configure
+ xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ i18n_domain="genericsetup">
+ <genericsetup:importStep
+ name="name"
+ title="title"
+ description="description"
+ handler="Products.GenericSetup.tests.test_zcml.dummy_importstep_handler"
+ version="version">
+ </genericsetup:importStep>
+ </configure>""")
+ from Products.GenericSetup.zcml import _import_step_regs
+ self.assertEqual(_import_step_regs, [u'name'])
+ self.assertEqual( _import_step_registry.listSteps(), [u'name'])
+ data=_import_step_registry.getStepMetadata(u'name')
+ self.assertEqual(data["handler"],
+ 'Products.GenericSetup.tests.test_zcml.dummy_importstep_handler')
+ self.assertEqual(data["description"], u"description")
+ self.assertEqual(data["version"], u"version")
+ self.assertEqual(data["title"], u"title")
+ self.assertEqual(data["dependencies"], ())
+ self.assertEqual(data["id"], u"name")
+
+
+ def testWithDependency(self):
+ zcml.load_string("""<configure
+ xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ i18n_domain="genericsetup">
+ <genericsetup:importStep
+ name="name"
+ title="title"
+ description="description"
+ handler="Products.GenericSetup.tests.test_zcml.dummy_importstep_handler"
+ version="version">
+ <depends name="something.else"/>
+ </genericsetup:importStep>
+ </configure>""")
+ data=_import_step_registry.getStepMetadata(u'name')
+ self.assertEqual(data["dependencies"], (u"something.else",))
+
+
+
+class ExportStepTests(unittest.TestCase):
+ layer = ExportImportZCMLLayer
+
+ def tearDown(self):
+ cleanUpExportSteps()
+
+ def testRegistration(self):
+ zcml.load_string("""<configure
+ xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
+ i18n_domain="genericsetup">
+ <genericsetup:exportStep
+ name="name"
+ title="title"
+ description="description"
+ handler="Products.GenericSetup.tests.test_zcml.dummy_exportstep_handler"
+ />
+ </configure>
+ """)
+ from Products.GenericSetup.zcml import _export_step_regs
+ self.assertEqual(_export_step_regs, [u'name'])
+ self.assertEqual( _export_step_registry.listSteps(), [u'name'])
+ data=_export_step_registry.getStepMetadata(u'name')
+ self.assertEqual(data["handler"],
+ 'Products.GenericSetup.tests.test_zcml.dummy_exportstep_handler')
+ self.assertEqual(data["description"], u"description")
+ self.assertEqual(data["title"], u"title")
+ self.assertEqual(data["id"], u"name")
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(doctest.DocTestSuite(optionflags=ELLIPSIS))
+ suite.addTest(unittest.makeSuite(ImportStepTests))
+ suite.addTest(unittest.makeSuite(ExportStepTests))
+ return suite
+
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Deleted: GenericSetup/branches/wichert-events/tool.py
===================================================================
--- GenericSetup/trunk/tool.py 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/tool.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,1097 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-""" Classes: SetupTool
-
-$Id$
-"""
-
-import os
-import time
-import logging
-from warnings import warn
-from cgi import escape
-
-from AccessControl import ClassSecurityInfo
-from Acquisition import aq_base
-from Globals import InitializeClass
-from OFS.Folder import Folder
-from OFS.Image import File
-from Products.PageTemplates.PageTemplateFile import PageTemplateFile
-from ZODB.POSException import ConflictError
-from zope.interface import implements
-from zope.interface import implementedBy
-
-from interfaces import BASE
-from interfaces import EXTENSION
-from interfaces import ISetupTool
-from interfaces import SKIPPED_FILES
-from permissions import ManagePortal
-from context import DirectoryImportContext
-from context import SnapshotImportContext
-from context import TarballExportContext
-from context import TarballImportContext
-from context import SnapshotExportContext
-from differ import ConfigDiff
-from registry import ImportStepRegistry
-from registry import ExportStepRegistry
-from registry import ToolsetRegistry
-from registry import _profile_registry
-
-from upgrade import listUpgradeSteps
-from upgrade import listProfilesWithUpgrades
-from upgrade import _upgrade_registry
-
-from utils import _getDottedName
-from utils import _resolveDottedName
-from utils import _wwwdir
-
-IMPORT_STEPS_XML = 'import_steps.xml'
-EXPORT_STEPS_XML = 'export_steps.xml'
-TOOLSET_XML = 'toolset.xml'
-
-def exportStepRegistries(context):
-
- """ Built-in handler for exporting import / export step registries.
- """
- setup_tool = context.getSetupTool()
- logger = context.getLogger('registries')
-
- import_steps_xml = setup_tool.getImportStepRegistry().generateXML()
- context.writeDataFile('import_steps.xml', import_steps_xml, 'text/xml')
-
- export_steps_xml = setup_tool.getExportStepRegistry().generateXML()
- context.writeDataFile('export_steps.xml', export_steps_xml, 'text/xml')
-
- logger.info('Step registries exported.')
-
-def importToolset(context):
-
- """ Import required / forbidden tools from XML file.
- """
- site = context.getSite()
- encoding = context.getEncoding()
- logger = context.getLogger('toolset')
-
- xml = context.readDataFile(TOOLSET_XML)
- if xml is None:
- logger.info('Nothing to import.')
- return
-
- setup_tool = context.getSetupTool()
- toolset = setup_tool.getToolsetRegistry()
-
- toolset.parseXML(xml, encoding)
-
- existing_ids = site.objectIds()
- existing_values = site.objectValues()
-
- for tool_id in toolset.listForbiddenTools():
-
- if tool_id in existing_ids:
- site._delObject(tool_id)
-
- for info in toolset.listRequiredToolInfo():
-
- tool_id = str(info['id'])
- tool_class = _resolveDottedName(info['class'])
-
- existing = getattr(aq_base(site), tool_id, None)
- # Don't even initialize the tool again, if it already exists.
- if existing is None:
- try:
- new_tool = tool_class()
- except TypeError:
- new_tool = tool_class(tool_id)
- else:
- try:
- new_tool._setId(tool_id)
- except (ConflictError, KeyboardInterrupt):
- raise
- except:
- # XXX: ImmutableId raises result of calling MessageDialog
- pass
-
- site._setObject(tool_id, new_tool)
- else:
- unwrapped = aq_base(existing)
- if not isinstance(unwrapped, tool_class):
- site._delObject(tool_id)
- site._setObject(tool_id, tool_class())
-
- logger.info('Toolset imported.')
-
-def exportToolset(context):
-
- """ Export required / forbidden tools to XML file.
- """
- setup_tool = context.getSetupTool()
- toolset = setup_tool.getToolsetRegistry()
- logger = context.getLogger('toolset')
-
- xml = toolset.generateXML()
- context.writeDataFile(TOOLSET_XML, xml, 'text/xml')
-
- logger.info('Toolset exported.')
-
-
-class SetupTool(Folder):
-
- """ Profile-based site configuration manager.
- """
-
- implements(ISetupTool)
-
- meta_type = 'Generic Setup Tool'
-
- _baseline_context_id = ''
- # BBB _import_context_id is a vestige of a stateful import context
- _import_context_id = ''
-
- _profile_upgrade_versions = {}
-
- security = ClassSecurityInfo()
-
- def __init__(self, id):
- self.id = str(id)
- self._import_registry = ImportStepRegistry()
- self._export_registry = ExportStepRegistry()
- self._export_registry.registerStep('step_registries',
- _getDottedName(exportStepRegistries),
- 'Export import / export steps.',
- )
- self._toolset_registry = ToolsetRegistry()
-
- #
- # ISetupTool API
- #
- security.declareProtected(ManagePortal, 'getEncoding')
- def getEncoding(self):
-
- """ See ISetupTool.
- """
- return 'utf-8'
-
- security.declareProtected(ManagePortal, 'getImportContextID')
- def getImportContextID(self):
-
- """ See ISetupTool.
- """
- warn('getImportContextId, and the very concept of a stateful '
- 'active import context, is deprecated. You can find the '
- 'base profile that was applied using getBaselineContextID.',
- DeprecationWarning, stacklevel=2)
- return self._import_context_id
-
- security.declareProtected(ManagePortal, 'getBaselineContextID')
- def getBaselineContextID(self):
-
- """ See ISetupTool.
- """
- return self._baseline_context_id
-
- security.declareProtected(ManagePortal, 'setImportContext')
- def setImportContext(self, context_id, encoding=None):
- """ See ISetupTool.
- """
- warn('setImportContext is deprecated. Use setBaselineContext to '
- 'specify the baseline context, and/or runImportStepFromProfile '
- 'to run the steps from a specific import context.',
- DeprecationWarning, stacklevel=2)
- self._import_context_id = context_id
-
- context_type = BASE # snapshots are always baseline contexts
- if context_id.startswith('profile-'):
- profile_info = _profile_registry.getProfileInfo(context_id[8:])
- context_type = profile_info['type']
-
- if context_type == BASE:
- self.setBaselineContext(context_id, encoding)
-
- security.declareProtected(ManagePortal, 'setBaselineContext')
- def setBaselineContext(self, context_id, encoding=None):
- """ See ISetupTool.
- """
- self._baseline_context_id = context_id
- self.applyContextById(context_id, encoding)
-
-
- security.declareProtected(ManagePortal, 'applyContextById')
- def applyContextById(self, context_id, encoding=None):
- context = self._getImportContext(context_id)
- self.applyContext(context, encoding)
-
-
- security.declareProtected(ManagePortal, 'applyContext')
- def applyContext(self, context, encoding=None):
- self._updateImportStepsRegistry(context, encoding)
- self._updateExportStepsRegistry(context, encoding)
-
- security.declareProtected(ManagePortal, 'getImportStepRegistry')
- def getImportStepRegistry(self):
-
- """ See ISetupTool.
- """
- return self._import_registry
-
- security.declareProtected(ManagePortal, 'getExportStepRegistry')
- def getExportStepRegistry(self):
-
- """ See ISetupTool.
- """
- return self._export_registry
-
- security.declareProtected(ManagePortal, 'getToolsetRegistry')
- def getToolsetRegistry(self):
-
- """ See ISetupTool.
- """
- return self._toolset_registry
-
- security.declareProtected(ManagePortal, 'runImportStepFromProfile')
- def runImportStepFromProfile(self, profile_id, step_id,
- run_dependencies=True, purge_old=None):
- """ See ISetupTool.
- """
- old_context = self._import_context_id
- context = self._getImportContext(profile_id, purge_old)
-
- self.applyContext(context)
-
- info = self._import_registry.getStepMetadata(step_id)
-
- if info is None:
- self._import_context_id = old_context
- raise ValueError, 'No such import step: %s' % step_id
-
- dependencies = info.get('dependencies', ())
-
- messages = {}
- steps = []
- if run_dependencies:
- for dependency in dependencies:
-
- if dependency not in steps:
- message = self._doRunImportStep(dependency, context)
- messages[dependency] = message or ''
- steps.append(dependency)
-
- message = self._doRunImportStep(step_id, context)
- message_list = filter(None, [message])
- message_list.extend( ['%s: %s' % x[1:] for x in context.listNotes()] )
- messages[step_id] = '\n'.join(message_list)
- steps.append(step_id)
-
- self._import_context_id = old_context
-
- return { 'steps' : steps, 'messages' : messages }
-
- security.declareProtected(ManagePortal, 'runImportStep')
- def runImportStep(self, step_id, run_dependencies=True, purge_old=None):
-
- """ See ISetupTool.
- """
- warn('The runImportStep method is deprecated. Please use '
- 'runImportStepFromProfile instead.',
- DeprecationWarning, stacklevel=2)
- return self.runImportStepFromProfile(self._import_context_id,
- step_id,
- run_dependencies,
- purge_old,
- )
-
- security.declareProtected(ManagePortal, 'runAllImportStepsFromProfile')
- def runAllImportStepsFromProfile(self, profile_id, purge_old=None):
-
- """ See ISetupTool.
- """
- __traceback_info__ = profile_id
-
- old_context = self._import_context_id
- context = self._getImportContext(profile_id, purge_old)
-
- result = self._runImportStepsFromContext(context, purge_old=purge_old)
- prefix = 'import-all-%s' % profile_id.replace(':', '_')
- name = self._mangleTimestampName(prefix, 'log')
- self._createReport(name, result['steps'], result['messages'])
-
- self._import_context_id = old_context
-
- return result
-
- security.declareProtected(ManagePortal, 'runAllImportSteps')
- def runAllImportSteps(self, purge_old=None):
-
- """ See ISetupTool.
- """
- warn('The runAllImportSteps method is deprecated. Please use '
- 'runAllImportStepsFromProfile instead.',
- DeprecationWarning, stacklevel=2)
- context_id = self._import_context_id
- return self.runAllImportStepsFromProfile(self._import_context_id,
- purge_old)
-
- security.declareProtected(ManagePortal, 'runExportStep')
- def runExportStep(self, step_id):
-
- """ See ISetupTool.
- """
- return self._doRunExportSteps([step_id])
-
- security.declareProtected(ManagePortal, 'runAllExportSteps')
- def runAllExportSteps(self):
-
- """ See ISetupTool.
- """
- return self._doRunExportSteps(self._export_registry.listSteps())
-
- security.declareProtected(ManagePortal, 'createSnapshot')
- def createSnapshot(self, snapshot_id):
-
- """ See ISetupTool.
- """
- context = SnapshotExportContext(self, snapshot_id)
- messages = {}
- steps = self._export_registry.listSteps()
-
- for step_id in steps:
-
- handler = self._export_registry.getStep(step_id)
-
- if handler is None:
- logger = logging.getLogger('GenericSetup')
- logger.error('Step %s has an invalid handler' % step_id)
- continue
-
- messages[step_id] = handler(context)
-
-
- return { 'steps' : steps
- , 'messages' : messages
- , 'url' : context.getSnapshotURL()
- , 'snapshot' : context.getSnapshotFolder()
- }
-
- security.declareProtected(ManagePortal, 'compareConfigurations')
- def compareConfigurations(self,
- lhs_context,
- rhs_context,
- missing_as_empty=False,
- ignore_blanks=False,
- skip=SKIPPED_FILES,
- ):
- """ See ISetupTool.
- """
- differ = ConfigDiff(lhs_context,
- rhs_context,
- missing_as_empty,
- ignore_blanks,
- skip,
- )
-
- return differ.compare()
-
- security.declareProtected(ManagePortal, 'markupComparison')
- def markupComparison(self, lines):
-
- """ See ISetupTool.
- """
- result = []
-
- for line in lines.splitlines():
-
- if line.startswith('** '):
-
- if line.find('File') > -1:
- if line.find('replaced') > -1:
- result.append(('file-to-dir', line))
- elif line.find('added') > -1:
- result.append(('file-added', line))
- else:
- result.append(('file-removed', line))
- else:
- if line.find('replaced') > -1:
- result.append(('dir-to-file', line))
- elif line.find('added') > -1:
- result.append(('dir-added', line))
- else:
- result.append(('dir-removed', line))
-
- elif line.startswith('@@'):
- result.append(('diff-range', line))
-
- elif line.startswith(' '):
- result.append(('diff-context', line))
-
- elif line.startswith('+'):
- result.append(('diff-added', line))
-
- elif line.startswith('-'):
- result.append(('diff-removed', line))
-
- elif line == '\ No newline at end of file':
- result.append(('diff-context', line))
-
- else:
- result.append(('diff-header', line))
-
- return '<pre>\n%s\n</pre>' % (
- '\n'.join([('<span class="%s">%s</span>' % (cl, escape(l)))
- for cl, l in result]))
-
- #
- # ZMI
- #
- manage_options = (Folder.manage_options[:1]
- + ({'label' : 'Profiles',
- 'action' : 'manage_tool'
- },
- {'label' : 'Import',
- 'action' : 'manage_importSteps'
- },
- {'label' : 'Export',
- 'action' : 'manage_exportSteps'
- },
- {'label' : 'Upgrades',
- 'action' : 'manage_upgrades'
- },
- {'label' : 'Snapshots',
- 'action' : 'manage_snapshots'
- },
- {'label' : 'Comparison',
- 'action' : 'manage_showDiff'
- },
- )
- + Folder.manage_options[3:] # skip "View", "Properties"
- )
-
- security.declareProtected(ManagePortal, 'manage_tool')
- manage_tool = PageTemplateFile('sutProperties', _wwwdir)
-
- security.declareProtected(ManagePortal, 'manage_updateToolProperties')
- def manage_updateToolProperties(self, context_id, RESPONSE):
- """ Update the tool's settings.
- """
- self.setBaselineContext(context_id)
-
- RESPONSE.redirect('%s/manage_tool?manage_tabs_message=%s'
- % (self.absolute_url(), 'Properties+updated.'))
-
- security.declareProtected(ManagePortal, 'manage_importSteps')
- manage_importSteps = PageTemplateFile('sutImportSteps', _wwwdir)
-
- security.declareProtected(ManagePortal, 'manage_importSelectedSteps')
- def manage_importSelectedSteps(self, ids, run_dependencies, context_id=None):
- """ Import the steps selected by the user.
- """
- messages = {}
- if not ids:
- summary = 'No steps selected.'
-
- else:
- if context_id is None:
- context_id = self.getBaselineContextID()
- steps_run = []
- for step_id in ids:
- result = self.runImportStepFromProfile(context_id,
- step_id,
- run_dependencies)
- steps_run.extend(result['steps'])
- messages.update(result['messages'])
-
- summary = 'Steps run: %s' % ', '.join(steps_run)
-
- name = self._mangleTimestampName('import-selected', 'log')
- self._createReport(name, result['steps'], result['messages'])
-
- return self.manage_importSteps(manage_tabs_message=summary,
- messages=messages)
-
- security.declareProtected(ManagePortal, 'manage_importSelectedSteps')
- def manage_importAllSteps(self, context_id=None):
-
- """ Import all steps.
- """
- if context_id is None:
- context_id = self.getBaselineContextID()
- result = self.runAllImportStepsFromProfile(context_id, purge_old=None)
-
- steps_run = 'Steps run: %s' % ', '.join(result['steps'])
-
- return self.manage_importSteps(manage_tabs_message=steps_run,
- messages=result['messages'])
-
- security.declareProtected(ManagePortal, 'manage_importExtensions')
- def manage_importExtensions(self, RESPONSE, profile_ids=()):
-
- """ Import all steps for the selected extension profiles.
- """
- detail = {}
- if len(profile_ids) == 0:
- message = 'Please select one or more extension profiles.'
- RESPONSE.redirect('%s/manage_tool?manage_tabs_message=%s'
- % (self.absolute_url(), message))
- else:
- message = 'Imported profiles: %s' % ', '.join(profile_ids)
-
- for profile_id in profile_ids:
-
- result = self.runAllImportStepsFromProfile(profile_id)
-
- for k, v in result['messages'].items():
- detail['%s:%s' % (profile_id, k)] = v
-
- return self.manage_importSteps(manage_tabs_message=message,
- messages=detail)
-
- security.declareProtected(ManagePortal, 'manage_importTarball')
- def manage_importTarball(self, tarball):
- """ Import steps from the uploaded tarball.
- """
- if getattr(tarball, 'read', None) is not None:
- tarball = tarball.read()
-
- context = TarballImportContext(tool=self,
- archive_bits=tarball,
- encoding='UTF8',
- should_purge=True,
- )
- result = self._runImportStepsFromContext(context,
- purge_old=True)
- steps_run = 'Steps run: %s' % ', '.join(result['steps'])
-
- name = self._mangleTimestampName('import-all', 'log')
- self._createReport(name, result['steps'], result['messages'])
-
- return self.manage_importSteps(manage_tabs_message=steps_run,
- messages=result['messages'])
-
- security.declareProtected(ManagePortal, 'manage_exportSteps')
- manage_exportSteps = PageTemplateFile('sutExportSteps', _wwwdir)
-
- security.declareProtected(ManagePortal, 'manage_exportSelectedSteps')
- def manage_exportSelectedSteps(self, ids, RESPONSE):
-
- """ Export the steps selected by the user.
- """
- if not ids:
- RESPONSE.redirect('%s/manage_exportSteps?manage_tabs_message=%s'
- % (self.absolute_url(), 'No+steps+selected.'))
-
- result = self._doRunExportSteps(ids)
- RESPONSE.setHeader('Content-type', 'application/x-gzip')
- RESPONSE.setHeader('Content-disposition',
- 'attachment; filename=%s' % result['filename'])
- return result['tarball']
-
- security.declareProtected(ManagePortal, 'manage_exportAllSteps')
- def manage_exportAllSteps(self, RESPONSE):
-
- """ Export all steps.
- """
- result = self.runAllExportSteps()
- RESPONSE.setHeader('Content-type', 'application/x-gzip')
- RESPONSE.setHeader('Content-disposition',
- 'attachment; filename=%s' % result['filename'])
- return result['tarball']
-
- security.declareProtected(ManagePortal, 'manage_upgrades')
- manage_upgrades = PageTemplateFile('setup_upgrades', _wwwdir)
-
- security.declareProtected(ManagePortal, 'upgradeStepMacro')
- upgradeStepMacro = PageTemplateFile('upgradeStep', _wwwdir)
-
- security.declareProtected(ManagePortal, 'manage_snapshots')
- manage_snapshots = PageTemplateFile('sutSnapshots', _wwwdir)
-
- security.declareProtected(ManagePortal, 'listSnapshotInfo')
- def listSnapshotInfo(self):
-
- """ Return a list of mappings describing available snapshots.
-
- o Keys include:
-
- 'id' -- snapshot ID
-
- 'title' -- snapshot title or ID
-
- 'url' -- URL of the snapshot folder
- """
- result = []
- snapshots = self._getOb('snapshots', None)
-
- if snapshots:
-
- for id, folder in snapshots.objectItems('Folder'):
-
- result.append({ 'id' : id
- , 'title' : folder.title_or_id()
- , 'url' : folder.absolute_url()
- })
- return result
-
- security.declareProtected(ManagePortal, 'listProfileInfo')
- def listProfileInfo(self):
-
- """ Return a list of mappings describing registered profiles.
- Base profile is listed first, extensions are sorted.
-
- o Keys include:
-
- 'id' -- profile ID
-
- 'title' -- profile title or ID
-
- 'description' -- description of the profile
-
- 'path' -- path to the profile within its product
-
- 'product' -- name of the registering product
- """
- base = []
- ext = []
- for info in _profile_registry.listProfileInfo():
- if info.get('type', BASE) == BASE:
- base.append(info)
- else:
- ext.append(info)
- ext.sort(lambda x, y: cmp(x['id'], y['id']))
- return base + ext
-
- security.declareProtected(ManagePortal, 'listContextInfos')
- def listContextInfos(self):
-
- """ List registered profiles and snapshots.
- """
- def readableType(x):
- if x is BASE:
- return 'base'
- elif x is EXTENSION:
- return 'extension'
- return 'unknown'
-
- s_infos = [{'id': 'snapshot-%s' % info['id'],
- 'title': info['title'],
- 'type': 'snapshot',
- }
- for info in self.listSnapshotInfo()]
- p_infos = [{'id': 'profile-%s' % info['id'],
- 'title': info['title'],
- 'type': readableType(info['type']),
- }
- for info in self.listProfileInfo()]
-
- return tuple(s_infos + p_infos)
-
- security.declareProtected(ManagePortal, 'getProfileImportDate')
- def getProfileImportDate(self, profile_id):
- """ See ISetupTool.
- """
- prefix = ('import-all-%s-' % profile_id).replace(':', '_')
- candidates = [x for x in self.objectIds('File')
- if x.startswith(prefix)]
- if len(candidates) == 0:
- return None
- candidates.sort()
- last = candidates[-1]
- stamp = last[len(prefix):-4]
- assert(len(stamp) == 14)
- return '%s-%s-%sT%s:%s:%sZ' % (stamp[0:4],
- stamp[4:6],
- stamp[6:8],
- stamp[8:10],
- stamp[10:12],
- stamp[12:14],
- )
-
- security.declareProtected(ManagePortal, 'manage_createSnapshot')
- def manage_createSnapshot(self, RESPONSE, snapshot_id=None):
-
- """ Create a snapshot with the given ID.
-
- o If no ID is passed, generate one.
- """
- if snapshot_id is None:
- snapshot_id = self._mangleTimestampName('snapshot')
-
- self.createSnapshot(snapshot_id)
-
- RESPONSE.redirect('%s/manage_snapshots?manage_tabs_message=%s'
- % (self.absolute_url(), 'Snapshot+created.'))
- return ""
-
- security.declareProtected(ManagePortal, 'manage_showDiff')
- manage_showDiff = PageTemplateFile('sutCompare', _wwwdir)
-
- def manage_downloadDiff(self,
- lhs,
- rhs,
- missing_as_empty,
- ignore_blanks,
- RESPONSE,
- ):
- """ Crack request vars and call compareConfigurations.
-
- o Return the result as a 'text/plain' stream, suitable for framing.
- """
- comparison = self.manage_compareConfigurations(lhs,
- rhs,
- missing_as_empty,
- ignore_blanks,
- )
- RESPONSE.setHeader('Content-Type', 'text/plain')
- return _PLAINTEXT_DIFF_HEADER % (lhs, rhs, comparison)
-
- security.declareProtected(ManagePortal, 'manage_compareConfigurations')
- def manage_compareConfigurations(self,
- lhs,
- rhs,
- missing_as_empty,
- ignore_blanks,
- ):
- """ Crack request vars and call compareConfigurations.
- """
- lhs_context = self._getImportContext(lhs)
- rhs_context = self._getImportContext(rhs)
-
- return self.compareConfigurations(lhs_context,
- rhs_context,
- missing_as_empty,
- ignore_blanks,
- )
-
- #
- # Upgrades management
- #
- security.declareProtected(ManagePortal, 'getLastVersionForProfile')
- def getLastVersionForProfile(self, profile_id):
- """Return the last upgraded version for the specified profile.
- """
- version = self._profile_upgrade_versions.get(profile_id, 'unknown')
- return version
-
- security.declareProtected(ManagePortal, 'setLastVersionForProfile')
- def setLastVersionForProfile(self, profile_id, version):
- """Set the last upgraded version for the specified profile.
- """
- if isinstance(version, basestring):
- version = tuple(version.split('.'))
- prof_versions = self._profile_upgrade_versions.copy()
- prof_versions[profile_id] = version
- self._profile_upgrade_versions = prof_versions
-
- security.declareProtected(ManagePortal, 'getVersionForProfile')
- def getVersionForProfile(self, profile_id):
- """Return the registered filesystem version for the specified
- profile.
- """
- info = _profile_registry.getProfileInfo(profile_id)
- return info.get('version', 'unknown')
-
- security.declareProtected(ManagePortal, 'listProfilesWithUpgrades')
- def listProfilesWithUpgrades(self):
- return listProfilesWithUpgrades()
-
- security.declarePrivate('_massageUpgradeInfo')
- def _massageUpgradeInfo(self, info):
- """Add a couple of data points to the upgrade info dictionary.
- """
- info = info.copy()
- info['haspath'] = info['source'] and info['dest']
- info['ssource'] = '.'.join(info['source'] or ('all',))
- info['sdest'] = '.'.join(info['dest'] or ('all',))
- return info
-
- security.declareProtected(ManagePortal, 'listUpgrades')
- def listUpgrades(self, profile_id, show_old=False):
- """Get the list of available upgrades.
- """
- if show_old:
- source = None
- else:
- source = self.getLastVersionForProfile(profile_id)
- upgrades = listUpgradeSteps(self, profile_id, source)
- res = []
- for info in upgrades:
- if type(info) == list:
- subset = []
- for subinfo in info:
- subset.append(self._massageUpgradeInfo(subinfo))
- res.append(subset)
- else:
- res.append(self._massageUpgradeInfo(info))
- return res
-
- security.declareProtected(ManagePortal, 'manage_doUpgrades')
- def manage_doUpgrades(self, request=None):
- """Perform all selected upgrade steps.
- """
- if request is None:
- request = self.REQUEST
- logger = logging.getLogger('GenericSetup')
- steps_to_run = request.form.get('upgrades', [])
- profile_id = request.get('profile_id', '')
- for step_id in steps_to_run:
- step = _upgrade_registry.getUpgradeStep(profile_id, step_id)
- if step is not None:
- step.doStep(self)
- msg = "Ran upgrade step %s for profile %s" % (step.title,
- profile_id)
- logger.log(logging.INFO, msg)
-
- # XXX should be a bit smarter about deciding when to up the
- # profile version
- profile_info = _profile_registry.getProfileInfo(profile_id)
- version = profile_info.get('version', None)
- if version is not None:
- self.setLastVersionForProfile(profile_id, version)
-
- url = self.absolute_url()
- request.RESPONSE.redirect("%s/manage_upgrades?saved=%s" % (url, profile_id))
-
- #
- # Helper methods
- #
- security.declarePrivate('_getProductPath')
- def _getProductPath(self, product_name):
-
- """ Return the absolute path of the product's directory.
- """
- try:
- # BBB: for GenericSetup 1.1 style product names
- product = __import__('Products.%s' % product_name
- , globals(), {}, ['initialize'])
- except ImportError:
- try:
- product = __import__(product_name
- , globals(), {}, ['initialize'])
- except ImportError:
- raise ValueError('Not a valid product name: %s'
- % product_name)
-
- return product.__path__[0]
-
- security.declarePrivate('_getImportContext')
- def _getImportContext(self, context_id, should_purge=None):
-
- """ Crack ID and generate appropriate import context.
- """
- encoding = self.getEncoding()
-
- if context_id.startswith('profile-'):
-
- context_id = context_id[len('profile-'):]
- info = _profile_registry.getProfileInfo(context_id)
-
- if info.get('product'):
- path = os.path.join(self._getProductPath(info['product'])
- , info['path'])
- else:
- path = info['path']
- if should_purge is None:
- should_purge = (info.get('type') != EXTENSION)
- return DirectoryImportContext(self, path, should_purge, encoding)
-
- # else snapshot
- context_id = context_id[len('snapshot-'):]
- if should_purge is None:
- should_purge = True
- return SnapshotImportContext(self, context_id, should_purge, encoding)
-
- security.declarePrivate('_updateImportStepsRegistry')
- def _updateImportStepsRegistry(self, context, encoding):
-
- """ Update our import steps registry from our profile.
- """
- if context is None:
- context = self._getImportContext(self._import_context_id)
- xml = context.readDataFile(IMPORT_STEPS_XML)
- if xml is None:
- return
-
- info_list = self._import_registry.parseXML(xml, encoding)
-
- for step_info in info_list:
-
- id = step_info['id']
- version = step_info['version']
- handler = step_info['handler']
- dependencies = tuple(step_info.get('dependencies', ()))
- title = step_info.get('title', id)
- description = ''.join(step_info.get('description', []))
-
- self._import_registry.registerStep(id=id,
- version=version,
- handler=handler,
- dependencies=dependencies,
- title=title,
- description=description,
- )
-
- security.declarePrivate('_updateExportStepsRegistry')
- def _updateExportStepsRegistry(self, context, encoding):
-
- """ Update our export steps registry from our profile.
- """
- if context is None:
- context = self._getImportContext(self._import_context_id)
- xml = context.readDataFile(EXPORT_STEPS_XML)
- if xml is None:
- return
-
- info_list = self._export_registry.parseXML(xml, encoding)
-
- for step_info in info_list:
-
- id = step_info['id']
- handler = step_info['handler']
- title = step_info.get('title', id)
- description = ''.join(step_info.get('description', []))
-
- self._export_registry.registerStep(id=id,
- handler=handler,
- title=title,
- description=description,
- )
-
- security.declarePrivate('_doRunImportStep')
- def _doRunImportStep(self, step_id, context):
-
- """ Run a single import step, using a pre-built context.
- """
- __traceback_info__ = step_id
- marker = object()
-
- handler = self._import_registry.getStep(step_id)
-
- if handler is marker:
- raise ValueError('Invalid import step: %s' % step_id)
-
- if handler is None:
- msg = 'Step %s has an invalid import handler' % step_id
- logger = logging.getLogger('GenericSetup')
- logger.error(msg)
- return 'ERROR: ' + msg
-
- return handler(context)
-
- security.declarePrivate('_doRunExportSteps')
- def _doRunExportSteps(self, steps):
-
- """ See ISetupTool.
- """
- context = TarballExportContext(self)
- messages = {}
- marker = object()
-
- for step_id in steps:
-
- handler = self._export_registry.getStep(step_id, marker)
-
- if handler is marker:
- raise ValueError('Invalid export step: %s' % step_id)
-
- if handler is None:
- msg = 'Step %s has an invalid import handler' % step_id
- logger = logging.getLogger('GenericSetup')
- logger.error(msg)
- messages[step_id] = msg
- else:
- messages[step_id] = handler(context)
-
- return { 'steps' : steps
- , 'messages' : messages
- , 'tarball' : context.getArchive()
- , 'filename' : context.getArchiveFilename()
- }
-
- security.declarePrivate('_runImportStepsFromContext')
- def _runImportStepsFromContext(self, context, steps=None, purge_old=None):
- self.applyContext(context)
-
- if steps is None:
- steps = self._import_registry.sortSteps()
- messages = {}
-
- for step in steps:
- message = self._doRunImportStep(step, context)
- message_list = filter(None, [message])
- message_list.extend( ['%s: %s' % x[1:]
- for x in context.listNotes()] )
- messages[step] = '\n'.join(message_list)
- context.clearNotes()
-
- return { 'steps' : steps, 'messages' : messages }
-
- security.declarePrivate('_mangleTimestampName')
- def _mangleTimestampName(self, prefix, ext=None):
-
- """ Create a mangled ID using a timestamp.
- """
- timestamp = time.gmtime()
- items = (prefix,) + timestamp[:6]
-
- if ext is None:
- fmt = '%s-%4d%02d%02d%02d%02d%02d'
- else:
- fmt = '%s-%4d%02d%02d%02d%02d%02d.%s'
- items += (ext,)
-
- return fmt % items
-
- security.declarePrivate('_createReport')
- def _createReport(self, name, steps, messages):
-
- """ Record the results of a run.
- """
- lines = []
- # Create report
- for step in steps:
- lines.append('=' * 65)
- lines.append('Step: %s' % step)
- lines.append('=' * 65)
- msg = messages[step]
- lines.extend(msg.split('\n'))
- lines.append('')
-
- report = '\n'.join(lines)
- if isinstance(report, unicode):
- report = report.encode('latin-1')
-
- # BBB: ObjectManager won't allow unicode IDS
- if isinstance(name, unicode):
- name = name.encode('UTF-8')
-
- file = File(id=name,
- title='',
- file=report,
- content_type='text/plain'
- )
- self._setObject(name, file)
-
-InitializeClass(SetupTool)
-
-_PLAINTEXT_DIFF_HEADER ="""\
-Comparing configurations: '%s' and '%s'
-
-%s"""
-
-_TOOL_ID = 'setup_tool'
-
-addSetupToolForm = PageTemplateFile('toolAdd.zpt', _wwwdir)
-
-def addSetupTool(dispatcher, RESPONSE):
- """
- """
- dispatcher._setObject(_TOOL_ID, SetupTool(_TOOL_ID))
-
- RESPONSE.redirect('%s/manage_main' % dispatcher.absolute_url())
Copied: GenericSetup/branches/wichert-events/tool.py (from rev 81471, GenericSetup/trunk/tool.py)
===================================================================
--- GenericSetup/branches/wichert-events/tool.py (rev 0)
+++ GenericSetup/branches/wichert-events/tool.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,1141 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+""" Classes: SetupTool
+
+$Id$
+"""
+
+import os
+import time
+import logging
+from warnings import warn
+from cgi import escape
+
+from AccessControl import ClassSecurityInfo
+from Acquisition import aq_base
+from Globals import InitializeClass
+from OFS.Folder import Folder
+from OFS.Image import File
+from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+from ZODB.POSException import ConflictError
+from zope.interface import implements
+from zope.interface import implementedBy
+
+from interfaces import BASE
+from interfaces import EXTENSION
+from interfaces import ISetupTool
+from interfaces import SKIPPED_FILES
+from permissions import ManagePortal
+from context import DirectoryImportContext
+from context import SnapshotImportContext
+from context import TarballExportContext
+from context import TarballImportContext
+from context import SnapshotExportContext
+from differ import ConfigDiff
+from registry import ImportStepRegistry
+from registry import ExportStepRegistry
+from registry import ToolsetRegistry
+from registry import _profile_registry
+from registry import _import_step_registry
+from registry import _export_step_registry
+
+from upgrade import listUpgradeSteps
+from upgrade import listProfilesWithUpgrades
+from upgrade import _upgrade_registry
+
+from utils import _getDottedName
+from utils import _resolveDottedName
+from utils import _wwwdir
+from utils import _computeTopologicalSort
+
+IMPORT_STEPS_XML = 'import_steps.xml'
+EXPORT_STEPS_XML = 'export_steps.xml'
+TOOLSET_XML = 'toolset.xml'
+
+def exportStepRegistries(context):
+
+ """ Built-in handler for exporting import / export step registries.
+ """
+ setup_tool = context.getSetupTool()
+ logger = context.getLogger('registries')
+
+ import_steps_xml = setup_tool.getImportStepRegistry().generateXML()
+ context.writeDataFile('import_steps.xml', import_steps_xml, 'text/xml')
+
+ export_steps_xml = setup_tool.getExportStepRegistry().generateXML()
+ context.writeDataFile('export_steps.xml', export_steps_xml, 'text/xml')
+
+ logger.info('Step registries exported.')
+
+def importToolset(context):
+
+ """ Import required / forbidden tools from XML file.
+ """
+ site = context.getSite()
+ encoding = context.getEncoding()
+ logger = context.getLogger('toolset')
+
+ xml = context.readDataFile(TOOLSET_XML)
+ if xml is None:
+ logger.info('Nothing to import.')
+ return
+
+ setup_tool = context.getSetupTool()
+ toolset = setup_tool.getToolsetRegistry()
+
+ toolset.parseXML(xml, encoding)
+
+ existing_ids = site.objectIds()
+ existing_values = site.objectValues()
+
+ for tool_id in toolset.listForbiddenTools():
+
+ if tool_id in existing_ids:
+ site._delObject(tool_id)
+
+ for info in toolset.listRequiredToolInfo():
+
+ tool_id = str(info['id'])
+ tool_class = _resolveDottedName(info['class'])
+
+ existing = getattr(aq_base(site), tool_id, None)
+ # Don't even initialize the tool again, if it already exists.
+ if existing is None:
+ try:
+ new_tool = tool_class()
+ except TypeError:
+ new_tool = tool_class(tool_id)
+ else:
+ try:
+ new_tool._setId(tool_id)
+ except (ConflictError, KeyboardInterrupt):
+ raise
+ except:
+ # XXX: ImmutableId raises result of calling MessageDialog
+ pass
+
+ site._setObject(tool_id, new_tool)
+ else:
+ unwrapped = aq_base(existing)
+ if not isinstance(unwrapped, tool_class):
+ site._delObject(tool_id)
+ site._setObject(tool_id, tool_class())
+
+ logger.info('Toolset imported.')
+
+def exportToolset(context):
+
+ """ Export required / forbidden tools to XML file.
+ """
+ setup_tool = context.getSetupTool()
+ toolset = setup_tool.getToolsetRegistry()
+ logger = context.getLogger('toolset')
+
+ xml = toolset.generateXML()
+ context.writeDataFile(TOOLSET_XML, xml, 'text/xml')
+
+ logger.info('Toolset exported.')
+
+
+class SetupTool(Folder):
+
+ """ Profile-based site configuration manager.
+ """
+
+ implements(ISetupTool)
+
+ meta_type = 'Generic Setup Tool'
+
+ _baseline_context_id = ''
+ # BBB _import_context_id is a vestige of a stateful import context
+ _import_context_id = ''
+
+ _profile_upgrade_versions = {}
+
+ security = ClassSecurityInfo()
+
+ def __init__(self, id):
+ self.id = str(id)
+ self._import_registry = ImportStepRegistry()
+ self._export_registry = ExportStepRegistry()
+ self._export_registry.registerStep('step_registries',
+ _getDottedName(exportStepRegistries),
+ 'Export import / export steps.',
+ )
+ self._toolset_registry = ToolsetRegistry()
+
+ #
+ # ISetupTool API
+ #
+ security.declareProtected(ManagePortal, 'getEncoding')
+ def getEncoding(self):
+
+ """ See ISetupTool.
+ """
+ return 'utf-8'
+
+ security.declareProtected(ManagePortal, 'getImportContextID')
+ def getImportContextID(self):
+
+ """ See ISetupTool.
+ """
+ warn('getImportContextId, and the very concept of a stateful '
+ 'active import context, is deprecated. You can find the '
+ 'base profile that was applied using getBaselineContextID.',
+ DeprecationWarning, stacklevel=2)
+ return self._import_context_id
+
+ security.declareProtected(ManagePortal, 'getBaselineContextID')
+ def getBaselineContextID(self):
+
+ """ See ISetupTool.
+ """
+ return self._baseline_context_id
+
+ security.declareProtected(ManagePortal, 'setImportContext')
+ def setImportContext(self, context_id, encoding=None):
+ """ See ISetupTool.
+ """
+ warn('setImportContext is deprecated. Use setBaselineContext to '
+ 'specify the baseline context, and/or runImportStepFromProfile '
+ 'to run the steps from a specific import context.',
+ DeprecationWarning, stacklevel=2)
+ self._import_context_id = context_id
+
+ context_type = BASE # snapshots are always baseline contexts
+ if context_id.startswith('profile-'):
+ profile_info = _profile_registry.getProfileInfo(context_id[8:])
+ context_type = profile_info['type']
+
+ if context_type == BASE:
+ self.setBaselineContext(context_id, encoding)
+
+ security.declareProtected(ManagePortal, 'setBaselineContext')
+ def setBaselineContext(self, context_id, encoding=None):
+ """ See ISetupTool.
+ """
+ self._baseline_context_id = context_id
+ self.applyContextById(context_id, encoding)
+
+
+ security.declareProtected(ManagePortal, 'applyContextById')
+ def applyContextById(self, context_id, encoding=None):
+ context = self._getImportContext(context_id)
+ self.applyContext(context, encoding)
+
+
+ security.declareProtected(ManagePortal, 'applyContext')
+ def applyContext(self, context, encoding=None):
+ self._updateImportStepsRegistry(context, encoding)
+ self._updateExportStepsRegistry(context, encoding)
+
+ security.declareProtected(ManagePortal, 'getImportStepRegistry')
+ def getImportStepRegistry(self):
+
+ """ See ISetupTool.
+ """
+ return self._import_registry
+
+ security.declareProtected(ManagePortal, 'getExportStepRegistry')
+ def getExportStepRegistry(self):
+
+ """ See ISetupTool.
+ """
+ return self._export_registry
+
+
+ security.declareProtected(ManagePortal, 'getExportStep')
+ def getExportStep(self, step, default=None):
+ """Simple wrapper to query both the global and local step registry."""
+ res=_export_step_registry.getStep(step, default)
+ if res is not default:
+ return res
+ return self._export_registry.getStep(step, default)
+
+
+ security.declareProtected(ManagePortal, 'listExportSteps')
+ def listExportSteps(self):
+ steps = _export_step_registry._registered.keys() + \
+ self._export_registry._registered.keys()
+ return steps
+
+
+ security.declareProtected(ManagePortal, 'getImportStep')
+ def getImportStep(self, step, default=None):
+ """Simple wrapper to query both the global and local step registry."""
+ res=_import_step_registry.getStep(step, default)
+ if res is not default:
+ return res
+ return self._import_registry.getStep(step, default)
+
+
+ security.declareProtected(ManagePortal, 'getSortedImportSteps')
+ def getSortedImportSteps(self):
+ steps = _import_step_registry._registered.values() + \
+ self._import_registry._registered.values()
+ return _computeTopologicalSort(steps)
+
+ security.declareProtected(ManagePortal, 'getImportStep')
+ def getImportStepMetadata(self, step, default=None):
+ """Simple wrapper to query both the global and local step registry."""
+ res=_import_step_registry.getStepMetadata(step, default)
+ if res is not default:
+ return res
+ return self._import_registry.getStepMetadata(step, default)
+
+
+ security.declareProtected(ManagePortal, 'getToolsetRegistry')
+ def getToolsetRegistry(self):
+
+ """ See ISetupTool.
+ """
+ return self._toolset_registry
+
+ security.declareProtected(ManagePortal, 'runImportStepFromProfile')
+ def runImportStepFromProfile(self, profile_id, step_id,
+ run_dependencies=True, purge_old=None):
+ """ See ISetupTool.
+ """
+ old_context = self._import_context_id
+ context = self._getImportContext(profile_id, purge_old)
+
+ self.applyContext(context)
+
+ info = self.getImportStepMetadata(step_id)
+
+ if info is None:
+ self._import_context_id = old_context
+ raise ValueError, 'No such import step: %s' % step_id
+
+ dependencies = info.get('dependencies', ())
+
+ messages = {}
+ steps = []
+ if run_dependencies:
+ for dependency in dependencies:
+
+ if dependency not in steps:
+ message = self._doRunImportStep(dependency, context)
+ messages[dependency] = message or ''
+ steps.append(dependency)
+
+ message = self._doRunImportStep(step_id, context)
+ message_list = filter(None, [message])
+ message_list.extend( ['%s: %s' % x[1:] for x in context.listNotes()] )
+ messages[step_id] = '\n'.join(message_list)
+ steps.append(step_id)
+
+ self._import_context_id = old_context
+
+ return { 'steps' : steps, 'messages' : messages }
+
+ security.declareProtected(ManagePortal, 'runImportStep')
+ def runImportStep(self, step_id, run_dependencies=True, purge_old=None):
+
+ """ See ISetupTool.
+ """
+ warn('The runImportStep method is deprecated. Please use '
+ 'runImportStepFromProfile instead.',
+ DeprecationWarning, stacklevel=2)
+ return self.runImportStepFromProfile(self._import_context_id,
+ step_id,
+ run_dependencies,
+ purge_old,
+ )
+
+ security.declareProtected(ManagePortal, 'runAllImportStepsFromProfile')
+ def runAllImportStepsFromProfile(self, profile_id, purge_old=None):
+
+ """ See ISetupTool.
+ """
+ __traceback_info__ = profile_id
+
+ old_context = self._import_context_id
+ context = self._getImportContext(profile_id, purge_old)
+
+ result = self._runImportStepsFromContext(context, purge_old=purge_old)
+ prefix = 'import-all-%s' % profile_id.replace(':', '_')
+ name = self._mangleTimestampName(prefix, 'log')
+ self._createReport(name, result['steps'], result['messages'])
+
+ self._import_context_id = old_context
+
+ return result
+
+ security.declareProtected(ManagePortal, 'runAllImportSteps')
+ def runAllImportSteps(self, purge_old=None):
+
+ """ See ISetupTool.
+ """
+ warn('The runAllImportSteps method is deprecated. Please use '
+ 'runAllImportStepsFromProfile instead.',
+ DeprecationWarning, stacklevel=2)
+ context_id = self._import_context_id
+ return self.runAllImportStepsFromProfile(self._import_context_id,
+ purge_old)
+
+ security.declareProtected(ManagePortal, 'runExportStep')
+ def runExportStep(self, step_id):
+
+ """ See ISetupTool.
+ """
+ return self._doRunExportSteps([step_id])
+
+ security.declareProtected(ManagePortal, 'runAllExportSteps')
+ def runAllExportSteps(self):
+
+ """ See ISetupTool.
+ """
+ return self._doRunExportSteps(self.listExportSteps())
+
+ security.declareProtected(ManagePortal, 'createSnapshot')
+ def createSnapshot(self, snapshot_id):
+
+ """ See ISetupTool.
+ """
+ context = SnapshotExportContext(self, snapshot_id)
+ messages = {}
+ steps = self.listExportSteps()
+
+ for step_id in steps:
+
+ handler = self._export_registry.getStep(step_id)
+
+ if handler is None:
+ logger = logging.getLogger('GenericSetup')
+ logger.error('Step %s has an invalid handler' % step_id)
+ continue
+
+ messages[step_id] = handler(context)
+
+
+ return { 'steps' : steps
+ , 'messages' : messages
+ , 'url' : context.getSnapshotURL()
+ , 'snapshot' : context.getSnapshotFolder()
+ }
+
+ security.declareProtected(ManagePortal, 'compareConfigurations')
+ def compareConfigurations(self,
+ lhs_context,
+ rhs_context,
+ missing_as_empty=False,
+ ignore_blanks=False,
+ skip=SKIPPED_FILES,
+ ):
+ """ See ISetupTool.
+ """
+ differ = ConfigDiff(lhs_context,
+ rhs_context,
+ missing_as_empty,
+ ignore_blanks,
+ skip,
+ )
+
+ return differ.compare()
+
+ security.declareProtected(ManagePortal, 'markupComparison')
+ def markupComparison(self, lines):
+
+ """ See ISetupTool.
+ """
+ result = []
+
+ for line in lines.splitlines():
+
+ if line.startswith('** '):
+
+ if line.find('File') > -1:
+ if line.find('replaced') > -1:
+ result.append(('file-to-dir', line))
+ elif line.find('added') > -1:
+ result.append(('file-added', line))
+ else:
+ result.append(('file-removed', line))
+ else:
+ if line.find('replaced') > -1:
+ result.append(('dir-to-file', line))
+ elif line.find('added') > -1:
+ result.append(('dir-added', line))
+ else:
+ result.append(('dir-removed', line))
+
+ elif line.startswith('@@'):
+ result.append(('diff-range', line))
+
+ elif line.startswith(' '):
+ result.append(('diff-context', line))
+
+ elif line.startswith('+'):
+ result.append(('diff-added', line))
+
+ elif line.startswith('-'):
+ result.append(('diff-removed', line))
+
+ elif line == '\ No newline at end of file':
+ result.append(('diff-context', line))
+
+ else:
+ result.append(('diff-header', line))
+
+ return '<pre>\n%s\n</pre>' % (
+ '\n'.join([('<span class="%s">%s</span>' % (cl, escape(l)))
+ for cl, l in result]))
+
+ #
+ # ZMI
+ #
+ manage_options = (Folder.manage_options[:1]
+ + ({'label' : 'Profiles',
+ 'action' : 'manage_tool'
+ },
+ {'label' : 'Import',
+ 'action' : 'manage_importSteps'
+ },
+ {'label' : 'Export',
+ 'action' : 'manage_exportSteps'
+ },
+ {'label' : 'Upgrades',
+ 'action' : 'manage_upgrades'
+ },
+ {'label' : 'Snapshots',
+ 'action' : 'manage_snapshots'
+ },
+ {'label' : 'Comparison',
+ 'action' : 'manage_showDiff'
+ },
+ )
+ + Folder.manage_options[3:] # skip "View", "Properties"
+ )
+
+ security.declareProtected(ManagePortal, 'manage_tool')
+ manage_tool = PageTemplateFile('sutProperties', _wwwdir)
+
+ security.declareProtected(ManagePortal, 'manage_updateToolProperties')
+ def manage_updateToolProperties(self, context_id, RESPONSE):
+ """ Update the tool's settings.
+ """
+ self.setBaselineContext(context_id)
+
+ RESPONSE.redirect('%s/manage_tool?manage_tabs_message=%s'
+ % (self.absolute_url(), 'Properties+updated.'))
+
+ security.declareProtected(ManagePortal, 'manage_importSteps')
+ manage_importSteps = PageTemplateFile('sutImportSteps', _wwwdir)
+
+ security.declareProtected(ManagePortal, 'manage_importSelectedSteps')
+ def manage_importSelectedSteps(self, ids, run_dependencies, context_id=None):
+ """ Import the steps selected by the user.
+ """
+ messages = {}
+ if not ids:
+ summary = 'No steps selected.'
+
+ else:
+ if context_id is None:
+ context_id = self.getBaselineContextID()
+ steps_run = []
+ for step_id in ids:
+ result = self.runImportStepFromProfile(context_id,
+ step_id,
+ run_dependencies)
+ steps_run.extend(result['steps'])
+ messages.update(result['messages'])
+
+ summary = 'Steps run: %s' % ', '.join(steps_run)
+
+ name = self._mangleTimestampName('import-selected', 'log')
+ self._createReport(name, result['steps'], result['messages'])
+
+ return self.manage_importSteps(manage_tabs_message=summary,
+ messages=messages)
+
+ security.declareProtected(ManagePortal, 'manage_importSelectedSteps')
+ def manage_importAllSteps(self, context_id=None):
+
+ """ Import all steps.
+ """
+ if context_id is None:
+ context_id = self.getBaselineContextID()
+ result = self.runAllImportStepsFromProfile(context_id, purge_old=None)
+
+ steps_run = 'Steps run: %s' % ', '.join(result['steps'])
+
+ return self.manage_importSteps(manage_tabs_message=steps_run,
+ messages=result['messages'])
+
+ security.declareProtected(ManagePortal, 'manage_importExtensions')
+ def manage_importExtensions(self, RESPONSE, profile_ids=()):
+
+ """ Import all steps for the selected extension profiles.
+ """
+ detail = {}
+ if len(profile_ids) == 0:
+ message = 'Please select one or more extension profiles.'
+ RESPONSE.redirect('%s/manage_tool?manage_tabs_message=%s'
+ % (self.absolute_url(), message))
+ else:
+ message = 'Imported profiles: %s' % ', '.join(profile_ids)
+
+ for profile_id in profile_ids:
+
+ result = self.runAllImportStepsFromProfile(profile_id)
+
+ for k, v in result['messages'].items():
+ detail['%s:%s' % (profile_id, k)] = v
+
+ return self.manage_importSteps(manage_tabs_message=message,
+ messages=detail)
+
+ security.declareProtected(ManagePortal, 'manage_importTarball')
+ def manage_importTarball(self, tarball):
+ """ Import steps from the uploaded tarball.
+ """
+ if getattr(tarball, 'read', None) is not None:
+ tarball = tarball.read()
+
+ context = TarballImportContext(tool=self,
+ archive_bits=tarball,
+ encoding='UTF8',
+ should_purge=True,
+ )
+ result = self._runImportStepsFromContext(context,
+ purge_old=True)
+ steps_run = 'Steps run: %s' % ', '.join(result['steps'])
+
+ name = self._mangleTimestampName('import-all', 'log')
+ self._createReport(name, result['steps'], result['messages'])
+
+ return self.manage_importSteps(manage_tabs_message=steps_run,
+ messages=result['messages'])
+
+ security.declareProtected(ManagePortal, 'manage_exportSteps')
+ manage_exportSteps = PageTemplateFile('sutExportSteps', _wwwdir)
+
+ security.declareProtected(ManagePortal, 'manage_exportSelectedSteps')
+ def manage_exportSelectedSteps(self, ids, RESPONSE):
+
+ """ Export the steps selected by the user.
+ """
+ if not ids:
+ RESPONSE.redirect('%s/manage_exportSteps?manage_tabs_message=%s'
+ % (self.absolute_url(), 'No+steps+selected.'))
+
+ result = self._doRunExportSteps(ids)
+ RESPONSE.setHeader('Content-type', 'application/x-gzip')
+ RESPONSE.setHeader('Content-disposition',
+ 'attachment; filename=%s' % result['filename'])
+ return result['tarball']
+
+ security.declareProtected(ManagePortal, 'manage_exportAllSteps')
+ def manage_exportAllSteps(self, RESPONSE):
+
+ """ Export all steps.
+ """
+ result = self.runAllExportSteps()
+ RESPONSE.setHeader('Content-type', 'application/x-gzip')
+ RESPONSE.setHeader('Content-disposition',
+ 'attachment; filename=%s' % result['filename'])
+ return result['tarball']
+
+ security.declareProtected(ManagePortal, 'manage_upgrades')
+ manage_upgrades = PageTemplateFile('setup_upgrades', _wwwdir)
+
+ security.declareProtected(ManagePortal, 'upgradeStepMacro')
+ upgradeStepMacro = PageTemplateFile('upgradeStep', _wwwdir)
+
+ security.declareProtected(ManagePortal, 'manage_snapshots')
+ manage_snapshots = PageTemplateFile('sutSnapshots', _wwwdir)
+
+ security.declareProtected(ManagePortal, 'listSnapshotInfo')
+ def listSnapshotInfo(self):
+
+ """ Return a list of mappings describing available snapshots.
+
+ o Keys include:
+
+ 'id' -- snapshot ID
+
+ 'title' -- snapshot title or ID
+
+ 'url' -- URL of the snapshot folder
+ """
+ result = []
+ snapshots = self._getOb('snapshots', None)
+
+ if snapshots:
+
+ for id, folder in snapshots.objectItems('Folder'):
+
+ result.append({ 'id' : id
+ , 'title' : folder.title_or_id()
+ , 'url' : folder.absolute_url()
+ })
+ return result
+
+ security.declareProtected(ManagePortal, 'listProfileInfo')
+ def listProfileInfo(self):
+
+ """ Return a list of mappings describing registered profiles.
+ Base profile is listed first, extensions are sorted.
+
+ o Keys include:
+
+ 'id' -- profile ID
+
+ 'title' -- profile title or ID
+
+ 'description' -- description of the profile
+
+ 'path' -- path to the profile within its product
+
+ 'product' -- name of the registering product
+ """
+ base = []
+ ext = []
+ for info in _profile_registry.listProfileInfo():
+ if info.get('type', BASE) == BASE:
+ base.append(info)
+ else:
+ ext.append(info)
+ ext.sort(lambda x, y: cmp(x['id'], y['id']))
+ return base + ext
+
+ security.declareProtected(ManagePortal, 'listContextInfos')
+ def listContextInfos(self):
+
+ """ List registered profiles and snapshots.
+ """
+ def readableType(x):
+ if x is BASE:
+ return 'base'
+ elif x is EXTENSION:
+ return 'extension'
+ return 'unknown'
+
+ s_infos = [{'id': 'snapshot-%s' % info['id'],
+ 'title': info['title'],
+ 'type': 'snapshot',
+ }
+ for info in self.listSnapshotInfo()]
+ p_infos = [{'id': 'profile-%s' % info['id'],
+ 'title': info['title'],
+ 'type': readableType(info['type']),
+ }
+ for info in self.listProfileInfo()]
+
+ return tuple(s_infos + p_infos)
+
+ security.declareProtected(ManagePortal, 'getProfileImportDate')
+ def getProfileImportDate(self, profile_id):
+ """ See ISetupTool.
+ """
+ prefix = ('import-all-%s-' % profile_id).replace(':', '_')
+ candidates = [x for x in self.objectIds('File')
+ if x.startswith(prefix)]
+ if len(candidates) == 0:
+ return None
+ candidates.sort()
+ last = candidates[-1]
+ stamp = last[len(prefix):-4]
+ assert(len(stamp) == 14)
+ return '%s-%s-%sT%s:%s:%sZ' % (stamp[0:4],
+ stamp[4:6],
+ stamp[6:8],
+ stamp[8:10],
+ stamp[10:12],
+ stamp[12:14],
+ )
+
+ security.declareProtected(ManagePortal, 'manage_createSnapshot')
+ def manage_createSnapshot(self, RESPONSE, snapshot_id=None):
+
+ """ Create a snapshot with the given ID.
+
+ o If no ID is passed, generate one.
+ """
+ if snapshot_id is None:
+ snapshot_id = self._mangleTimestampName('snapshot')
+
+ self.createSnapshot(snapshot_id)
+
+ RESPONSE.redirect('%s/manage_snapshots?manage_tabs_message=%s'
+ % (self.absolute_url(), 'Snapshot+created.'))
+ return ""
+
+ security.declareProtected(ManagePortal, 'manage_showDiff')
+ manage_showDiff = PageTemplateFile('sutCompare', _wwwdir)
+
+ def manage_downloadDiff(self,
+ lhs,
+ rhs,
+ missing_as_empty,
+ ignore_blanks,
+ RESPONSE,
+ ):
+ """ Crack request vars and call compareConfigurations.
+
+ o Return the result as a 'text/plain' stream, suitable for framing.
+ """
+ comparison = self.manage_compareConfigurations(lhs,
+ rhs,
+ missing_as_empty,
+ ignore_blanks,
+ )
+ RESPONSE.setHeader('Content-Type', 'text/plain')
+ return _PLAINTEXT_DIFF_HEADER % (lhs, rhs, comparison)
+
+ security.declareProtected(ManagePortal, 'manage_compareConfigurations')
+ def manage_compareConfigurations(self,
+ lhs,
+ rhs,
+ missing_as_empty,
+ ignore_blanks,
+ ):
+ """ Crack request vars and call compareConfigurations.
+ """
+ lhs_context = self._getImportContext(lhs)
+ rhs_context = self._getImportContext(rhs)
+
+ return self.compareConfigurations(lhs_context,
+ rhs_context,
+ missing_as_empty,
+ ignore_blanks,
+ )
+
+ #
+ # Upgrades management
+ #
+ security.declareProtected(ManagePortal, 'getLastVersionForProfile')
+ def getLastVersionForProfile(self, profile_id):
+ """Return the last upgraded version for the specified profile.
+ """
+ version = self._profile_upgrade_versions.get(profile_id, 'unknown')
+ return version
+
+ security.declareProtected(ManagePortal, 'setLastVersionForProfile')
+ def setLastVersionForProfile(self, profile_id, version):
+ """Set the last upgraded version for the specified profile.
+ """
+ if isinstance(version, basestring):
+ version = tuple(version.split('.'))
+ prof_versions = self._profile_upgrade_versions.copy()
+ prof_versions[profile_id] = version
+ self._profile_upgrade_versions = prof_versions
+
+ security.declareProtected(ManagePortal, 'getVersionForProfile')
+ def getVersionForProfile(self, profile_id):
+ """Return the registered filesystem version for the specified
+ profile.
+ """
+ info = _profile_registry.getProfileInfo(profile_id)
+ return info.get('version', 'unknown')
+
+ security.declareProtected(ManagePortal, 'listProfilesWithUpgrades')
+ def listProfilesWithUpgrades(self):
+ return listProfilesWithUpgrades()
+
+ security.declarePrivate('_massageUpgradeInfo')
+ def _massageUpgradeInfo(self, info):
+ """Add a couple of data points to the upgrade info dictionary.
+ """
+ info = info.copy()
+ info['haspath'] = info['source'] and info['dest']
+ info['ssource'] = '.'.join(info['source'] or ('all',))
+ info['sdest'] = '.'.join(info['dest'] or ('all',))
+ return info
+
+ security.declareProtected(ManagePortal, 'listUpgrades')
+ def listUpgrades(self, profile_id, show_old=False):
+ """Get the list of available upgrades.
+ """
+ if show_old:
+ source = None
+ else:
+ source = self.getLastVersionForProfile(profile_id)
+ upgrades = listUpgradeSteps(self, profile_id, source)
+ res = []
+ for info in upgrades:
+ if type(info) == list:
+ subset = []
+ for subinfo in info:
+ subset.append(self._massageUpgradeInfo(subinfo))
+ res.append(subset)
+ else:
+ res.append(self._massageUpgradeInfo(info))
+ return res
+
+ security.declareProtected(ManagePortal, 'manage_doUpgrades')
+ def manage_doUpgrades(self, request=None):
+ """Perform all selected upgrade steps.
+ """
+ if request is None:
+ request = self.REQUEST
+ logger = logging.getLogger('GenericSetup')
+ steps_to_run = request.form.get('upgrades', [])
+ profile_id = request.get('profile_id', '')
+ for step_id in steps_to_run:
+ step = _upgrade_registry.getUpgradeStep(profile_id, step_id)
+ if step is not None:
+ step.doStep(self)
+ msg = "Ran upgrade step %s for profile %s" % (step.title,
+ profile_id)
+ logger.log(logging.INFO, msg)
+
+ # XXX should be a bit smarter about deciding when to up the
+ # profile version
+ profile_info = _profile_registry.getProfileInfo(profile_id)
+ version = profile_info.get('version', None)
+ if version is not None:
+ self.setLastVersionForProfile(profile_id, version)
+
+ url = self.absolute_url()
+ request.RESPONSE.redirect("%s/manage_upgrades?saved=%s" % (url, profile_id))
+
+ #
+ # Helper methods
+ #
+ security.declarePrivate('_getProductPath')
+ def _getProductPath(self, product_name):
+
+ """ Return the absolute path of the product's directory.
+ """
+ try:
+ # BBB: for GenericSetup 1.1 style product names
+ product = __import__('Products.%s' % product_name
+ , globals(), {}, ['initialize'])
+ except ImportError:
+ try:
+ product = __import__(product_name
+ , globals(), {}, ['initialize'])
+ except ImportError:
+ raise ValueError('Not a valid product name: %s'
+ % product_name)
+
+ return product.__path__[0]
+
+ security.declarePrivate('_getImportContext')
+ def _getImportContext(self, context_id, should_purge=None):
+
+ """ Crack ID and generate appropriate import context.
+ """
+ encoding = self.getEncoding()
+
+ if context_id.startswith('profile-'):
+
+ context_id = context_id[len('profile-'):]
+ info = _profile_registry.getProfileInfo(context_id)
+
+ if info.get('product'):
+ path = os.path.join(self._getProductPath(info['product'])
+ , info['path'])
+ else:
+ path = info['path']
+ if should_purge is None:
+ should_purge = (info.get('type') != EXTENSION)
+ return DirectoryImportContext(self, path, should_purge, encoding)
+
+ # else snapshot
+ context_id = context_id[len('snapshot-'):]
+ if should_purge is None:
+ should_purge = True
+ return SnapshotImportContext(self, context_id, should_purge, encoding)
+
+ security.declarePrivate('_updateImportStepsRegistry')
+ def _updateImportStepsRegistry(self, context, encoding):
+
+ """ Update our import steps registry from our profile.
+ """
+ if context is None:
+ context = self._getImportContext(self._import_context_id)
+ xml = context.readDataFile(IMPORT_STEPS_XML)
+ if xml is None:
+ return
+
+ info_list = self._import_registry.parseXML(xml, encoding)
+
+ for step_info in info_list:
+
+ id = step_info['id']
+ version = step_info['version']
+ handler = step_info['handler']
+ dependencies = tuple(step_info.get('dependencies', ()))
+ title = step_info.get('title', id)
+ description = ''.join(step_info.get('description', []))
+
+ self._import_registry.registerStep(id=id,
+ version=version,
+ handler=handler,
+ dependencies=dependencies,
+ title=title,
+ description=description,
+ )
+
+ security.declarePrivate('_updateExportStepsRegistry')
+ def _updateExportStepsRegistry(self, context, encoding):
+
+ """ Update our export steps registry from our profile.
+ """
+ if context is None:
+ context = self._getImportContext(self._import_context_id)
+ xml = context.readDataFile(EXPORT_STEPS_XML)
+ if xml is None:
+ return
+
+ info_list = self._export_registry.parseXML(xml, encoding)
+
+ for step_info in info_list:
+
+ id = step_info['id']
+ handler = step_info['handler']
+ title = step_info.get('title', id)
+ description = ''.join(step_info.get('description', []))
+
+ self._export_registry.registerStep(id=id,
+ handler=handler,
+ title=title,
+ description=description,
+ )
+
+ security.declarePrivate('_doRunImportStep')
+ def _doRunImportStep(self, step_id, context):
+
+ """ Run a single import step, using a pre-built context.
+ """
+ __traceback_info__ = step_id
+ marker = object()
+
+ handler = self.getImportStep(step_id)
+
+ if handler is marker:
+ raise ValueError('Invalid import step: %s' % step_id)
+
+ if handler is None:
+ msg = 'Step %s has an invalid import handler' % step_id
+ logger = logging.getLogger('GenericSetup')
+ logger.error(msg)
+ return 'ERROR: ' + msg
+
+ return handler(context)
+
+ security.declarePrivate('_doRunExportSteps')
+ def _doRunExportSteps(self, steps):
+
+ """ See ISetupTool.
+ """
+ context = TarballExportContext(self)
+ messages = {}
+ marker = object()
+
+ for step_id in steps:
+
+ handler = self._export_registry.getStep(step_id, marker)
+
+ if handler is marker:
+ raise ValueError('Invalid export step: %s' % step_id)
+
+ if handler is None:
+ msg = 'Step %s has an invalid import handler' % step_id
+ logger = logging.getLogger('GenericSetup')
+ logger.error(msg)
+ messages[step_id] = msg
+ else:
+ messages[step_id] = handler(context)
+
+ return { 'steps' : steps
+ , 'messages' : messages
+ , 'tarball' : context.getArchive()
+ , 'filename' : context.getArchiveFilename()
+ }
+
+ security.declarePrivate('_runImportStepsFromContext')
+ def _runImportStepsFromContext(self, context, steps=None, purge_old=None):
+ self.applyContext(context)
+
+ if steps is None:
+ steps = self.getSortedImportSteps()
+ messages = {}
+
+ for step in steps:
+ message = self._doRunImportStep(step, context)
+ message_list = filter(None, [message])
+ message_list.extend( ['%s: %s' % x[1:]
+ for x in context.listNotes()] )
+ messages[step] = '\n'.join(message_list)
+ context.clearNotes()
+
+ return { 'steps' : steps, 'messages' : messages }
+
+ security.declarePrivate('_mangleTimestampName')
+ def _mangleTimestampName(self, prefix, ext=None):
+
+ """ Create a mangled ID using a timestamp.
+ """
+ timestamp = time.gmtime()
+ items = (prefix,) + timestamp[:6]
+
+ if ext is None:
+ fmt = '%s-%4d%02d%02d%02d%02d%02d'
+ else:
+ fmt = '%s-%4d%02d%02d%02d%02d%02d.%s'
+ items += (ext,)
+
+ return fmt % items
+
+ security.declarePrivate('_createReport')
+ def _createReport(self, name, steps, messages):
+
+ """ Record the results of a run.
+ """
+ lines = []
+ # Create report
+ for step in steps:
+ lines.append('=' * 65)
+ lines.append('Step: %s' % step)
+ lines.append('=' * 65)
+ msg = messages[step]
+ lines.extend(msg.split('\n'))
+ lines.append('')
+
+ report = '\n'.join(lines)
+ if isinstance(report, unicode):
+ report = report.encode('latin-1')
+
+ # BBB: ObjectManager won't allow unicode IDS
+ if isinstance(name, unicode):
+ name = name.encode('UTF-8')
+
+ file = File(id=name,
+ title='',
+ file=report,
+ content_type='text/plain'
+ )
+ self._setObject(name, file)
+
+InitializeClass(SetupTool)
+
+_PLAINTEXT_DIFF_HEADER ="""\
+Comparing configurations: '%s' and '%s'
+
+%s"""
+
+_TOOL_ID = 'setup_tool'
+
+addSetupToolForm = PageTemplateFile('toolAdd.zpt', _wwwdir)
+
+def addSetupTool(dispatcher, RESPONSE):
+ """
+ """
+ dispatcher._setObject(_TOOL_ID, SetupTool(_TOOL_ID))
+
+ RESPONSE.redirect('%s/manage_main' % dispatcher.absolute_url())
Deleted: GenericSetup/branches/wichert-events/utils.py
===================================================================
--- GenericSetup/trunk/utils.py 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/utils.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,790 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-""" GenericSetup product utilities
-
-$Id$
-"""
-
-import os
-import sys
-from cgi import escape
-from inspect import getdoc
-from xml.dom.minidom import _nssplit
-from xml.dom.minidom import Document
-from xml.dom.minidom import Element
-from xml.dom.minidom import Node
-from xml.dom.minidom import parseString
-from xml.sax.handler import ContentHandler
-from xml.parsers.expat import ExpatError
-
-import Products
-from AccessControl import ClassSecurityInfo
-from Acquisition import Implicit
-from Globals import InitializeClass
-from Globals import package_home
-from OFS.interfaces import IOrderedContainer
-from zope.component import queryMultiAdapter
-from zope.deprecation import deprecated
-from zope.interface import implements
-from zope.interface import implementsOnly
-from zope.interface import providedBy
-from ZPublisher.HTTPRequest import default_encoding
-
-from exceptions import BadRequest
-from interfaces import IBody
-from interfaces import INode
-from interfaces import ISetupContext
-from interfaces import ISetupTool
-from permissions import ManagePortal
-
-
-_pkgdir = package_home( globals() )
-_wwwdir = os.path.join( _pkgdir, 'www' )
-_xmldir = os.path.join( _pkgdir, 'xml' )
-
-# Please note that these values may change. Always import
-# the values from here instead of using the values directly.
-CONVERTER, DEFAULT, KEY = 1, 2, 3
-I18NURI = 'http://xml.zope.org/namespaces/i18n'
-
-
-def _getDottedName( named ):
-
- if isinstance( named, basestring ):
- return str( named )
-
- try:
- dotted = '%s.%s' % (named.__module__, named.__name__)
- except AttributeError:
- raise ValueError('Cannot compute dotted name: %s' % named)
-
- # remove leading underscore names if possible
-
- # Step 1: check if there is a short version
- short_dotted = '.'.join([ n for n in dotted.split('.')
- if not n.startswith('_') ])
- if short_dotted == dotted:
- return dotted
-
- # Step 2: check if short version can be resolved
- try:
- short_resolved = _resolveDottedName(short_dotted)
- except (ValueError, ImportError):
- return dotted
-
- # Step 3: check if long version resolves to the same object
- try:
- resolved = _resolveDottedName(dotted)
- except (ValueError, ImportError):
- raise ValueError('Cannot compute dotted name: %s' % named)
- if short_resolved is not resolved:
- return dotted
-
- return short_dotted
-
-def _resolveDottedName( dotted ):
-
- __traceback_info__ = dotted
-
- parts = dotted.split( '.' )
-
- if not parts:
- raise ValueError, "incomplete dotted name: %s" % dotted
-
- parts_copy = parts[:]
-
- while parts_copy:
- try:
- module = __import__( '.'.join( parts_copy ) )
- break
-
- except ImportError:
- # Reraise if the import error was caused inside the imported file
- if sys.exc_info()[2].tb_next is not None: raise
-
- del parts_copy[ -1 ]
-
- if not parts_copy:
- return None
-
- parts = parts[ 1: ] # Funky semantics of __import__'s return value
-
- obj = module
-
- for part in parts:
- try:
- obj = getattr( obj, part )
- except AttributeError:
- return None
-
- return obj
-
-def _extractDocstring( func, default_title, default_description ):
-
- try:
- doc = getdoc( func )
- lines = doc.split( '\n' )
-
- except AttributeError:
-
- title = default_title
- description = default_description
-
- else:
- title = lines[ 0 ]
-
- if len( lines ) > 1 and lines[ 1 ].strip() == '':
- del lines[ 1 ]
-
- description = '\n'.join( lines[ 1: ] )
-
- return title, description
-
-
-deprecated('HandlerBase',
- 'SAX based XML parsing is no longer supported by GenericSetup. '
- 'HandlerBase will be removed in GenericSetup 1.5.')
-
-class HandlerBase( ContentHandler ):
-
- _encoding = None
- _MARKER = object()
-
- def _extract( self, attrs, key, default=None ):
-
- result = attrs.get( key, self._MARKER )
-
- if result is self._MARKER:
- return default
-
- return self._encode( result )
-
- def _extractBoolean( self, attrs, key, default ):
-
- result = attrs.get( key, self._MARKER )
-
- if result is self._MARKER:
- return default
-
- result = result.lower()
- return result in ( '1', 'yes', 'true' )
-
- def _encode( self, content ):
-
- if self._encoding is None:
- return content
-
- return content.encode( self._encoding )
-
-
-##############################################################################
-# WARNING: PLEASE DON'T USE THE CONFIGURATOR PATTERN. THE RELATED BASE CLASSES
-# WILL BECOME DEPRECATED AS SOON AS GENERICSETUP ITSELF NO LONGER USES THEM.
-
-class ImportConfiguratorBase(Implicit):
- # old code, will become deprecated
- """ Synthesize data from XML description.
- """
- security = ClassSecurityInfo()
- security.setDefaultAccess('allow')
-
- def __init__(self, site, encoding=None):
-
- self._site = site
- self._encoding = encoding
-
- security.declareProtected(ManagePortal, 'parseXML')
- def parseXML(self, xml):
- """ Pseudo API.
- """
- reader = getattr(xml, 'read', None)
-
- if reader is not None:
- xml = reader()
-
- dom = parseString(xml)
- root = dom.documentElement
-
- return self._extractNode(root)
-
- def _extractNode(self, node):
- """ Please see docs/configurator.txt for information about the
- import mapping syntax.
- """
- nodes_map = self._getImportMapping()
- if node.nodeName not in nodes_map:
- nodes_map = self._getSharedImportMapping()
- if node.nodeName not in nodes_map:
- raise ValueError('Unknown node: %s' % node.nodeName)
- node_map = nodes_map[node.nodeName]
- info = {}
-
- for name, val in node.attributes.items():
- key = node_map[name].get( KEY, str(name) )
- val = self._encoding and val.encode(self._encoding) or val
- info[key] = val
-
- for child in node.childNodes:
- name = child.nodeName
-
- if name == '#comment':
- continue
-
- if not name == '#text':
- key = node_map[name].get(KEY, str(name) )
- info[key] = info.setdefault( key, () ) + (
- self._extractNode(child),)
-
- elif '#text' in node_map:
- key = node_map['#text'].get(KEY, 'value')
- val = child.nodeValue.lstrip()
- val = self._encoding and val.encode(self._encoding) or val
- info[key] = info.setdefault(key, '') + val
-
- for k, v in node_map.items():
- key = v.get(KEY, k)
-
- if DEFAULT in v and not key in info:
- if isinstance( v[DEFAULT], basestring ):
- info[key] = v[DEFAULT] % info
- else:
- info[key] = v[DEFAULT]
-
- elif CONVERTER in v and key in info:
- info[key] = v[CONVERTER]( info[key] )
-
- if key is None:
- info = info[key]
-
- return info
-
- def _getSharedImportMapping(self):
-
- return {
- 'object':
- { 'i18n:domain': {},
- 'name': {KEY: 'id'},
- 'meta_type': {},
- 'insert-before': {},
- 'insert-after': {},
- 'property': {KEY: 'properties', DEFAULT: ()},
- 'object': {KEY: 'objects', DEFAULT: ()},
- 'xmlns:i18n': {} },
- 'property':
- { 'name': {KEY: 'id'},
- '#text': {KEY: 'value', DEFAULT: ''},
- 'element': {KEY: 'elements', DEFAULT: ()},
- 'type': {},
- 'select_variable': {},
- 'i18n:translate': {} },
- 'element':
- { 'value': {KEY: None} },
- 'description':
- { '#text': {KEY: None, DEFAULT: ''} } }
-
- def _convertToBoolean(self, val):
-
- return val.lower() in ('true', 'yes', '1')
-
- def _convertToUnique(self, val):
-
- assert len(val) == 1
- return val[0]
-
-InitializeClass(ImportConfiguratorBase)
-
-
-class ExportConfiguratorBase(Implicit):
- # old code, will become deprecated
- """ Synthesize XML description.
- """
- security = ClassSecurityInfo()
- security.setDefaultAccess('allow')
-
- def __init__(self, site, encoding=None):
-
- self._site = site
- self._encoding = encoding
- self._template = self._getExportTemplate()
-
- security.declareProtected(ManagePortal, 'generateXML')
- def generateXML(self, **kw):
- """ Pseudo API.
- """
- return self._template(**kw)
-
-InitializeClass(ExportConfiguratorBase)
-
-#
-##############################################################################
-
-class _LineWrapper:
-
- def __init__(self, writer, indent, addindent, newl, max):
- self._writer = writer
- self._indent = indent
- self._addindent = addindent
- self._newl = newl
- self._max = max
- self._length = 0
- self._queue = self._indent
-
- def queue(self, text):
- self._queue += text
-
- def write(self, text='', enforce=False):
- self._queue += text
-
- if 0 < self._length > self._max - len(self._queue):
- self._writer.write(self._newl)
- self._length = 0
- self._queue = '%s%s %s' % (self._indent, self._addindent,
- self._queue)
-
- if self._queue != self._indent:
- self._writer.write(self._queue)
- self._length += len(self._queue)
- self._queue = ''
-
- if 0 < self._length and enforce:
- self._writer.write(self._newl)
- self._length = 0
- self._queue = self._indent
-
-
-class _Element(Element):
-
- """minidom element with 'pretty' XML output.
- """
-
- def writexml(self, writer, indent="", addindent="", newl=""):
- # indent = current indentation
- # addindent = indentation to add to higher levels
- # newl = newline string
- wrapper = _LineWrapper(writer, indent, addindent, newl, 78)
- wrapper.write('<%s' % self.tagName)
-
- # move 'name', 'meta_type' and 'title' to the top, sort the rest
- attrs = self._get_attributes()
- a_names = attrs.keys()
- a_names.sort()
- if 'title' in a_names:
- a_names.remove('title')
- a_names.insert(0, 'title')
- if 'meta_type' in a_names:
- a_names.remove('meta_type')
- a_names.insert(0, 'meta_type')
- if 'name' in a_names:
- a_names.remove('name')
- a_names.insert(0, 'name')
-
- for a_name in a_names:
- wrapper.write()
- a_value = escape(attrs[a_name].value.encode('utf-8'), quote=True)
- wrapper.queue(' %s="%s"' % (a_name, a_value))
-
- if self.childNodes:
- wrapper.queue('>')
- for node in self.childNodes:
- if node.nodeType == Node.TEXT_NODE:
- data = escape(node.data.encode('utf-8'))
- textlines = data.splitlines()
- if textlines:
- wrapper.queue(textlines.pop(0))
- if textlines:
- for textline in textlines:
- wrapper.write('', True)
- wrapper.queue('%s%s' % (addindent, textline))
- else:
- wrapper.write('', True)
- node.writexml(writer, indent+addindent, addindent, newl)
- wrapper.write('</%s>' % self.tagName, True)
- else:
- wrapper.write('/>', True)
-
-
-class PrettyDocument(Document):
-
- """minidom document with 'pretty' XML output.
- """
-
- def createElement(self, tagName):
- e = _Element(tagName)
- e.ownerDocument = self
- return e
-
- def createElementNS(self, namespaceURI, qualifiedName):
- prefix, localName = _nssplit(qualifiedName)
- e = _Element(qualifiedName, namespaceURI, prefix)
- e.ownerDocument = self
- return e
-
- def writexml(self, writer, indent="", addindent="", newl="",
- encoding = None):
- if encoding is None:
- writer.write('<?xml version="1.0"?>\n')
- else:
- writer.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
- for node in self.childNodes:
- node.writexml(writer, indent, addindent, newl)
-
-
-class NodeAdapterBase(object):
-
- """Node im- and exporter base.
- """
-
- implements(INode)
-
- _LOGGER_ID = ''
-
- def __init__(self, context, environ):
- self.context = context
- self.environ = environ
- self._logger = environ.getLogger(self._LOGGER_ID)
- self._doc = PrettyDocument()
-
- def _getObjectNode(self, name, i18n=True):
- node = self._doc.createElement(name)
- node.setAttribute('name', self.context.getId())
- node.setAttribute('meta_type', self.context.meta_type)
- i18n_domain = getattr(self.context, 'i18n_domain', None)
- if i18n and i18n_domain:
- node.setAttributeNS(I18NURI, 'i18n:domain', i18n_domain)
- self._i18n_props = ('title', 'description')
- return node
-
- def _getNodeText(self, node):
- text = ''
- for child in node.childNodes:
- if child.nodeName != '#text':
- continue
- lines = [ line.lstrip() for line in child.nodeValue.splitlines() ]
- text += '\n'.join(lines)
- return text
-
- def _convertToBoolean(self, val):
- return val.lower() in ('true', 'yes', '1')
-
-
-class BodyAdapterBase(NodeAdapterBase):
-
- """Body im- and exporter base.
- """
-
- implementsOnly(IBody)
-
- def _exportSimpleNode(self):
- """Export the object as a DOM node.
- """
- if ISetupTool.providedBy(self.context):
- return None
- return self._getObjectNode('object', False)
-
- def _importSimpleNode(self, node):
- """Import the object from the DOM node.
- """
-
- node = property(_exportSimpleNode, _importSimpleNode)
-
- def _exportBody(self):
- """Export the object as a file body.
- """
- return ''
-
- def _importBody(self, body):
- """Import the object from the file body.
- """
-
- body = property(_exportBody, _importBody)
-
- mime_type = 'text/plain'
-
- name = ''
-
- suffix = ''
-
-
-class XMLAdapterBase(BodyAdapterBase):
-
- """XML im- and exporter base.
- """
-
- implementsOnly(IBody)
-
- def _exportBody(self):
- """Export the object as a file body.
- """
- self._doc.appendChild(self._exportNode())
- xml = self._doc.toprettyxml(' ')
- self._doc.unlink()
- return xml
-
- def _importBody(self, body):
- """Import the object from the file body.
- """
- try:
- dom = parseString(body)
- except ExpatError, e:
- filename = (self.filename or
- '/'.join(self.context.getPhysicalPath()))
- raise ExpatError('%s: %s' % (filename, e))
- self._importNode(dom.documentElement)
-
- body = property(_exportBody, _importBody)
-
- mime_type = 'text/xml'
-
- name = ''
-
- suffix = '.xml'
-
- filename = '' # for error reporting during import
-
-
-class ObjectManagerHelpers(object):
-
- """ObjectManager im- and export helpers.
- """
-
- def _extractObjects(self):
- fragment = self._doc.createDocumentFragment()
- objects = self.context.objectValues()
- if not IOrderedContainer.providedBy(self.context):
- objects = list(objects)
- objects.sort(lambda x,y: cmp(x.getId(), y.getId()))
- for obj in objects:
- exporter = queryMultiAdapter((obj, self.environ), INode)
- if exporter:
- node = exporter.node
- if node is not None:
- fragment.appendChild(exporter.node)
- return fragment
-
- def _purgeObjects(self):
- for obj_id, obj in self.context.objectItems():
- if ISetupTool.providedBy(obj):
- continue
- self.context._delObject(obj_id)
-
- def _initObjects(self, node):
- for child in node.childNodes:
- if child.nodeName != 'object':
- continue
- if child.hasAttribute('deprecated'):
- continue
- parent = self.context
-
- obj_id = str(child.getAttribute('name'))
- if child.hasAttribute('remove'):
- parent._delObject(obj_id)
- continue
-
- if obj_id not in parent.objectIds():
- meta_type = str(child.getAttribute('meta_type'))
- __traceback_info__ = obj_id, meta_type
- for mt_info in Products.meta_types:
- if mt_info['name'] == meta_type:
- parent._setObject(obj_id, mt_info['instance'](obj_id))
- break
- else:
- raise ValueError("unknown meta_type '%s'" % meta_type)
-
- if child.hasAttribute('insert-before'):
- insert_before = child.getAttribute('insert-before')
- if insert_before == '*':
- parent.moveObjectsToTop(obj_id)
- else:
- try:
- position = parent.getObjectPosition(insert_before)
- if parent.getObjectPosition(obj_id) < position:
- position -= 1
- parent.moveObjectToPosition(obj_id, position)
- except ValueError:
- pass
- elif child.hasAttribute('insert-after'):
- insert_after = child.getAttribute('insert-after')
- if insert_after == '*':
- parent.moveObjectsToBottom(obj_id)
- else:
- try:
- position = parent.getObjectPosition(insert_after)
- if parent.getObjectPosition(obj_id) < position:
- position -= 1
- parent.moveObjectToPosition(obj_id, position+1)
- except ValueError:
- pass
-
- obj = getattr(self.context, obj_id)
- importer = queryMultiAdapter((obj, self.environ), INode)
- if importer:
- importer.node = child
-
-
-class PropertyManagerHelpers(object):
-
- """PropertyManager im- and export helpers.
- """
-
- _encoding = default_encoding
-
- def _extractProperties(self):
- fragment = self._doc.createDocumentFragment()
-
- for prop_map in self.context._propertyMap():
- prop_id = prop_map['id']
- if prop_id == 'i18n_domain':
- continue
-
- # Don't export read-only nodes
- if 'w' not in prop_map.get('mode', 'wd'):
- continue
-
- node = self._doc.createElement('property')
- node.setAttribute('name', prop_id)
-
- prop = self.context.getProperty(prop_id)
- if isinstance(prop, (tuple, list)):
- for value in prop:
- if isinstance(value, str):
- value.decode(self._encoding)
- child = self._doc.createElement('element')
- child.setAttribute('value', value)
- node.appendChild(child)
- else:
- if prop_map.get('type') == 'boolean':
- prop = unicode(bool(prop))
- elif isinstance(prop, str):
- prop = prop.decode(self._encoding)
- elif not isinstance(prop, basestring):
- prop = unicode(prop)
- child = self._doc.createTextNode(prop)
- node.appendChild(child)
-
- if 'd' in prop_map.get('mode', 'wd') and not prop_id == 'title':
- prop_type = prop_map.get('type', 'string')
- node.setAttribute('type', unicode(prop_type))
- select_variable = prop_map.get('select_variable', None)
- if select_variable is not None:
- node.setAttribute('select_variable', select_variable)
-
- if hasattr(self, '_i18n_props') and prop_id in self._i18n_props:
- node.setAttribute('i18n:translate', '')
-
- fragment.appendChild(node)
-
- return fragment
-
- def _purgeProperties(self):
- for prop_map in self.context._propertyMap():
- mode = prop_map.get('mode', 'wd')
- if 'w' not in mode:
- continue
- prop_id = prop_map['id']
- if 'd' in mode and not prop_id == 'title':
- self.context._delProperty(prop_id)
- else:
- prop_type = prop_map.get('type')
- if prop_type == 'multiple selection':
- prop_value = ()
- elif prop_type in ('int', 'float'):
- prop_value = 0
- else:
- prop_value = ''
- self.context._updateProperty(prop_id, prop_value)
-
- def _initProperties(self, node):
- obj = self.context
- if node.hasAttribute('i18n:domain'):
- i18n_domain = str(node.getAttribute('i18n:domain'))
- obj._updateProperty('i18n_domain', i18n_domain)
- for child in node.childNodes:
- if child.nodeName != 'property':
- continue
- prop_id = str(child.getAttribute('name'))
- prop_map = obj.propdict().get(prop_id, None)
-
- if prop_map is None:
- if child.hasAttribute('type'):
- val = str(child.getAttribute('select_variable'))
- prop_type = str(child.getAttribute('type'))
- obj._setProperty(prop_id, val, prop_type)
- prop_map = obj.propdict().get(prop_id, None)
- else:
- raise ValueError("undefined property '%s'" % prop_id)
-
- if not 'w' in prop_map.get('mode', 'wd'):
- raise BadRequest('%s cannot be changed' % prop_id)
-
- elements = []
- for sub in child.childNodes:
- if sub.nodeName == 'element':
- value = sub.getAttribute('value')
- elements.append(value.encode(self._encoding))
-
- if elements or prop_map.get('type') == 'multiple selection':
- prop_value = tuple(elements) or ()
- elif prop_map.get('type') == 'boolean':
- prop_value = self._convertToBoolean(self._getNodeText(child))
- else:
- # if we pass a *string* to _updateProperty, all other values
- # are converted to the right type
- prop_value = self._getNodeText(child).encode(self._encoding)
-
- if not self._convertToBoolean(child.getAttribute('purge')
- or 'True'):
- # If the purge attribute is False, merge sequences
- prop = obj.getProperty(prop_id)
- if isinstance(prop, (tuple, list)):
- prop_value = (tuple([p for p in prop
- if p not in prop_value]) +
- tuple(prop_value))
-
- obj._updateProperty(prop_id, prop_value)
-
-
-def exportObjects(obj, parent_path, context):
- """ Export subobjects recursively.
- """
- exporter = queryMultiAdapter((obj, context), IBody)
- path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
- if exporter:
- if exporter.name:
- path = '%s%s' % (parent_path, exporter.name)
- filename = '%s%s' % (path, exporter.suffix)
- body = exporter.body
- if body is not None:
- context.writeDataFile(filename, body, exporter.mime_type)
-
- if getattr(obj, 'objectValues', False):
- for sub in obj.objectValues():
- exportObjects(sub, path+'/', context)
-
-def importObjects(obj, parent_path, context):
- """ Import subobjects recursively.
- """
- importer = queryMultiAdapter((obj, context), IBody)
- path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
- __traceback_info__ = path
- if importer:
- if importer.name:
- path = '%s%s' % (parent_path, importer.name)
- filename = '%s%s' % (path, importer.suffix)
- body = context.readDataFile(filename)
- if body is not None:
- importer.filename = filename # for error reporting
- importer.body = body
-
- if getattr(obj, 'objectValues', False):
- for sub in obj.objectValues():
- importObjects(sub, path+'/', context)
Copied: GenericSetup/branches/wichert-events/utils.py (from rev 81471, GenericSetup/trunk/utils.py)
===================================================================
--- GenericSetup/branches/wichert-events/utils.py (rev 0)
+++ GenericSetup/branches/wichert-events/utils.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,828 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+""" GenericSetup product utilities
+
+$Id$
+"""
+
+import os
+import sys
+from cgi import escape
+from inspect import getdoc
+from xml.dom.minidom import _nssplit
+from xml.dom.minidom import Document
+from xml.dom.minidom import Element
+from xml.dom.minidom import Node
+from xml.dom.minidom import parseString
+from xml.sax.handler import ContentHandler
+from xml.parsers.expat import ExpatError
+
+import Products
+from AccessControl import ClassSecurityInfo
+from Acquisition import Implicit
+from Globals import InitializeClass
+from Globals import package_home
+from OFS.interfaces import IOrderedContainer
+from zope.component import queryMultiAdapter
+from zope.deprecation import deprecated
+from zope.interface import implements
+from zope.interface import implementsOnly
+from zope.interface import providedBy
+from ZPublisher.HTTPRequest import default_encoding
+
+from exceptions import BadRequest
+from interfaces import IBody
+from interfaces import INode
+from interfaces import ISetupContext
+from interfaces import ISetupTool
+from permissions import ManagePortal
+
+
+_pkgdir = package_home( globals() )
+_wwwdir = os.path.join( _pkgdir, 'www' )
+_xmldir = os.path.join( _pkgdir, 'xml' )
+
+# Please note that these values may change. Always import
+# the values from here instead of using the values directly.
+CONVERTER, DEFAULT, KEY = 1, 2, 3
+I18NURI = 'http://xml.zope.org/namespaces/i18n'
+
+
+def _getDottedName( named ):
+
+ if isinstance( named, basestring ):
+ return str( named )
+
+ try:
+ dotted = '%s.%s' % (named.__module__, named.__name__)
+ except AttributeError:
+ raise ValueError('Cannot compute dotted name: %s' % named)
+
+ # remove leading underscore names if possible
+
+ # Step 1: check if there is a short version
+ short_dotted = '.'.join([ n for n in dotted.split('.')
+ if not n.startswith('_') ])
+ if short_dotted == dotted:
+ return dotted
+
+ # Step 2: check if short version can be resolved
+ try:
+ short_resolved = _resolveDottedName(short_dotted)
+ except (ValueError, ImportError):
+ return dotted
+
+ # Step 3: check if long version resolves to the same object
+ try:
+ resolved = _resolveDottedName(dotted)
+ except (ValueError, ImportError):
+ raise ValueError('Cannot compute dotted name: %s' % named)
+ if short_resolved is not resolved:
+ return dotted
+
+ return short_dotted
+
+def _resolveDottedName( dotted ):
+
+ __traceback_info__ = dotted
+
+ parts = dotted.split( '.' )
+
+ if not parts:
+ raise ValueError, "incomplete dotted name: %s" % dotted
+
+ parts_copy = parts[:]
+
+ while parts_copy:
+ try:
+ module = __import__( '.'.join( parts_copy ) )
+ break
+
+ except ImportError:
+ # Reraise if the import error was caused inside the imported file
+ if sys.exc_info()[2].tb_next is not None: raise
+
+ del parts_copy[ -1 ]
+
+ if not parts_copy:
+ return None
+
+ parts = parts[ 1: ] # Funky semantics of __import__'s return value
+
+ obj = module
+
+ for part in parts:
+ try:
+ obj = getattr( obj, part )
+ except AttributeError:
+ return None
+
+ return obj
+
+def _extractDocstring( func, default_title, default_description ):
+
+ try:
+ doc = getdoc( func )
+ lines = doc.split( '\n' )
+
+ except AttributeError:
+
+ title = default_title
+ description = default_description
+
+ else:
+ title = lines[ 0 ]
+
+ if len( lines ) > 1 and lines[ 1 ].strip() == '':
+ del lines[ 1 ]
+
+ description = '\n'.join( lines[ 1: ] )
+
+ return title, description
+
+
+deprecated('HandlerBase',
+ 'SAX based XML parsing is no longer supported by GenericSetup. '
+ 'HandlerBase will be removed in GenericSetup 1.5.')
+
+class HandlerBase( ContentHandler ):
+
+ _encoding = None
+ _MARKER = object()
+
+ def _extract( self, attrs, key, default=None ):
+
+ result = attrs.get( key, self._MARKER )
+
+ if result is self._MARKER:
+ return default
+
+ return self._encode( result )
+
+ def _extractBoolean( self, attrs, key, default ):
+
+ result = attrs.get( key, self._MARKER )
+
+ if result is self._MARKER:
+ return default
+
+ result = result.lower()
+ return result in ( '1', 'yes', 'true' )
+
+ def _encode( self, content ):
+
+ if self._encoding is None:
+ return content
+
+ return content.encode( self._encoding )
+
+
+##############################################################################
+# WARNING: PLEASE DON'T USE THE CONFIGURATOR PATTERN. THE RELATED BASE CLASSES
+# WILL BECOME DEPRECATED AS SOON AS GENERICSETUP ITSELF NO LONGER USES THEM.
+
+class ImportConfiguratorBase(Implicit):
+ # old code, will become deprecated
+ """ Synthesize data from XML description.
+ """
+ security = ClassSecurityInfo()
+ security.setDefaultAccess('allow')
+
+ def __init__(self, site, encoding=None):
+
+ self._site = site
+ self._encoding = encoding
+
+ security.declareProtected(ManagePortal, 'parseXML')
+ def parseXML(self, xml):
+ """ Pseudo API.
+ """
+ reader = getattr(xml, 'read', None)
+
+ if reader is not None:
+ xml = reader()
+
+ dom = parseString(xml)
+ root = dom.documentElement
+
+ return self._extractNode(root)
+
+ def _extractNode(self, node):
+ """ Please see docs/configurator.txt for information about the
+ import mapping syntax.
+ """
+ nodes_map = self._getImportMapping()
+ if node.nodeName not in nodes_map:
+ nodes_map = self._getSharedImportMapping()
+ if node.nodeName not in nodes_map:
+ raise ValueError('Unknown node: %s' % node.nodeName)
+ node_map = nodes_map[node.nodeName]
+ info = {}
+
+ for name, val in node.attributes.items():
+ key = node_map[name].get( KEY, str(name) )
+ val = self._encoding and val.encode(self._encoding) or val
+ info[key] = val
+
+ for child in node.childNodes:
+ name = child.nodeName
+
+ if name == '#comment':
+ continue
+
+ if not name == '#text':
+ key = node_map[name].get(KEY, str(name) )
+ info[key] = info.setdefault( key, () ) + (
+ self._extractNode(child),)
+
+ elif '#text' in node_map:
+ key = node_map['#text'].get(KEY, 'value')
+ val = child.nodeValue.lstrip()
+ val = self._encoding and val.encode(self._encoding) or val
+ info[key] = info.setdefault(key, '') + val
+
+ for k, v in node_map.items():
+ key = v.get(KEY, k)
+
+ if DEFAULT in v and not key in info:
+ if isinstance( v[DEFAULT], basestring ):
+ info[key] = v[DEFAULT] % info
+ else:
+ info[key] = v[DEFAULT]
+
+ elif CONVERTER in v and key in info:
+ info[key] = v[CONVERTER]( info[key] )
+
+ if key is None:
+ info = info[key]
+
+ return info
+
+ def _getSharedImportMapping(self):
+
+ return {
+ 'object':
+ { 'i18n:domain': {},
+ 'name': {KEY: 'id'},
+ 'meta_type': {},
+ 'insert-before': {},
+ 'insert-after': {},
+ 'property': {KEY: 'properties', DEFAULT: ()},
+ 'object': {KEY: 'objects', DEFAULT: ()},
+ 'xmlns:i18n': {} },
+ 'property':
+ { 'name': {KEY: 'id'},
+ '#text': {KEY: 'value', DEFAULT: ''},
+ 'element': {KEY: 'elements', DEFAULT: ()},
+ 'type': {},
+ 'select_variable': {},
+ 'i18n:translate': {} },
+ 'element':
+ { 'value': {KEY: None} },
+ 'description':
+ { '#text': {KEY: None, DEFAULT: ''} } }
+
+ def _convertToBoolean(self, val):
+
+ return val.lower() in ('true', 'yes', '1')
+
+ def _convertToUnique(self, val):
+
+ assert len(val) == 1
+ return val[0]
+
+InitializeClass(ImportConfiguratorBase)
+
+
+class ExportConfiguratorBase(Implicit):
+ # old code, will become deprecated
+ """ Synthesize XML description.
+ """
+ security = ClassSecurityInfo()
+ security.setDefaultAccess('allow')
+
+ def __init__(self, site, encoding=None):
+
+ self._site = site
+ self._encoding = encoding
+ self._template = self._getExportTemplate()
+
+ security.declareProtected(ManagePortal, 'generateXML')
+ def generateXML(self, **kw):
+ """ Pseudo API.
+ """
+ return self._template(**kw)
+
+InitializeClass(ExportConfiguratorBase)
+
+#
+##############################################################################
+
+class _LineWrapper:
+
+ def __init__(self, writer, indent, addindent, newl, max):
+ self._writer = writer
+ self._indent = indent
+ self._addindent = addindent
+ self._newl = newl
+ self._max = max
+ self._length = 0
+ self._queue = self._indent
+
+ def queue(self, text):
+ self._queue += text
+
+ def write(self, text='', enforce=False):
+ self._queue += text
+
+ if 0 < self._length > self._max - len(self._queue):
+ self._writer.write(self._newl)
+ self._length = 0
+ self._queue = '%s%s %s' % (self._indent, self._addindent,
+ self._queue)
+
+ if self._queue != self._indent:
+ self._writer.write(self._queue)
+ self._length += len(self._queue)
+ self._queue = ''
+
+ if 0 < self._length and enforce:
+ self._writer.write(self._newl)
+ self._length = 0
+ self._queue = self._indent
+
+
+class _Element(Element):
+
+ """minidom element with 'pretty' XML output.
+ """
+
+ def writexml(self, writer, indent="", addindent="", newl=""):
+ # indent = current indentation
+ # addindent = indentation to add to higher levels
+ # newl = newline string
+ wrapper = _LineWrapper(writer, indent, addindent, newl, 78)
+ wrapper.write('<%s' % self.tagName)
+
+ # move 'name', 'meta_type' and 'title' to the top, sort the rest
+ attrs = self._get_attributes()
+ a_names = attrs.keys()
+ a_names.sort()
+ if 'title' in a_names:
+ a_names.remove('title')
+ a_names.insert(0, 'title')
+ if 'meta_type' in a_names:
+ a_names.remove('meta_type')
+ a_names.insert(0, 'meta_type')
+ if 'name' in a_names:
+ a_names.remove('name')
+ a_names.insert(0, 'name')
+
+ for a_name in a_names:
+ wrapper.write()
+ a_value = escape(attrs[a_name].value.encode('utf-8'), quote=True)
+ wrapper.queue(' %s="%s"' % (a_name, a_value))
+
+ if self.childNodes:
+ wrapper.queue('>')
+ for node in self.childNodes:
+ if node.nodeType == Node.TEXT_NODE:
+ data = escape(node.data.encode('utf-8'))
+ textlines = data.splitlines()
+ if textlines:
+ wrapper.queue(textlines.pop(0))
+ if textlines:
+ for textline in textlines:
+ wrapper.write('', True)
+ wrapper.queue('%s%s' % (addindent, textline))
+ else:
+ wrapper.write('', True)
+ node.writexml(writer, indent+addindent, addindent, newl)
+ wrapper.write('</%s>' % self.tagName, True)
+ else:
+ wrapper.write('/>', True)
+
+
+class PrettyDocument(Document):
+
+ """minidom document with 'pretty' XML output.
+ """
+
+ def createElement(self, tagName):
+ e = _Element(tagName)
+ e.ownerDocument = self
+ return e
+
+ def createElementNS(self, namespaceURI, qualifiedName):
+ prefix, localName = _nssplit(qualifiedName)
+ e = _Element(qualifiedName, namespaceURI, prefix)
+ e.ownerDocument = self
+ return e
+
+ def writexml(self, writer, indent="", addindent="", newl="",
+ encoding = None):
+ if encoding is None:
+ writer.write('<?xml version="1.0"?>\n')
+ else:
+ writer.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
+ for node in self.childNodes:
+ node.writexml(writer, indent, addindent, newl)
+
+
+class NodeAdapterBase(object):
+
+ """Node im- and exporter base.
+ """
+
+ implements(INode)
+
+ _LOGGER_ID = ''
+
+ def __init__(self, context, environ):
+ self.context = context
+ self.environ = environ
+ self._logger = environ.getLogger(self._LOGGER_ID)
+ self._doc = PrettyDocument()
+
+ def _getObjectNode(self, name, i18n=True):
+ node = self._doc.createElement(name)
+ node.setAttribute('name', self.context.getId())
+ node.setAttribute('meta_type', self.context.meta_type)
+ i18n_domain = getattr(self.context, 'i18n_domain', None)
+ if i18n and i18n_domain:
+ node.setAttributeNS(I18NURI, 'i18n:domain', i18n_domain)
+ self._i18n_props = ('title', 'description')
+ return node
+
+ def _getNodeText(self, node):
+ text = ''
+ for child in node.childNodes:
+ if child.nodeName != '#text':
+ continue
+ lines = [ line.lstrip() for line in child.nodeValue.splitlines() ]
+ text += '\n'.join(lines)
+ return text
+
+ def _convertToBoolean(self, val):
+ return val.lower() in ('true', 'yes', '1')
+
+
+class BodyAdapterBase(NodeAdapterBase):
+
+ """Body im- and exporter base.
+ """
+
+ implementsOnly(IBody)
+
+ def _exportSimpleNode(self):
+ """Export the object as a DOM node.
+ """
+ if ISetupTool.providedBy(self.context):
+ return None
+ return self._getObjectNode('object', False)
+
+ def _importSimpleNode(self, node):
+ """Import the object from the DOM node.
+ """
+
+ node = property(_exportSimpleNode, _importSimpleNode)
+
+ def _exportBody(self):
+ """Export the object as a file body.
+ """
+ return ''
+
+ def _importBody(self, body):
+ """Import the object from the file body.
+ """
+
+ body = property(_exportBody, _importBody)
+
+ mime_type = 'text/plain'
+
+ name = ''
+
+ suffix = ''
+
+
+class XMLAdapterBase(BodyAdapterBase):
+
+ """XML im- and exporter base.
+ """
+
+ implementsOnly(IBody)
+
+ def _exportBody(self):
+ """Export the object as a file body.
+ """
+ self._doc.appendChild(self._exportNode())
+ xml = self._doc.toprettyxml(' ')
+ self._doc.unlink()
+ return xml
+
+ def _importBody(self, body):
+ """Import the object from the file body.
+ """
+ try:
+ dom = parseString(body)
+ except ExpatError, e:
+ filename = (self.filename or
+ '/'.join(self.context.getPhysicalPath()))
+ raise ExpatError('%s: %s' % (filename, e))
+ self._importNode(dom.documentElement)
+
+ body = property(_exportBody, _importBody)
+
+ mime_type = 'text/xml'
+
+ name = ''
+
+ suffix = '.xml'
+
+ filename = '' # for error reporting during import
+
+
+class ObjectManagerHelpers(object):
+
+ """ObjectManager im- and export helpers.
+ """
+
+ def _extractObjects(self):
+ fragment = self._doc.createDocumentFragment()
+ objects = self.context.objectValues()
+ if not IOrderedContainer.providedBy(self.context):
+ objects = list(objects)
+ objects.sort(lambda x,y: cmp(x.getId(), y.getId()))
+ for obj in objects:
+ exporter = queryMultiAdapter((obj, self.environ), INode)
+ if exporter:
+ node = exporter.node
+ if node is not None:
+ fragment.appendChild(exporter.node)
+ return fragment
+
+ def _purgeObjects(self):
+ for obj_id, obj in self.context.objectItems():
+ if ISetupTool.providedBy(obj):
+ continue
+ self.context._delObject(obj_id)
+
+ def _initObjects(self, node):
+ for child in node.childNodes:
+ if child.nodeName != 'object':
+ continue
+ if child.hasAttribute('deprecated'):
+ continue
+ parent = self.context
+
+ obj_id = str(child.getAttribute('name'))
+ if child.hasAttribute('remove'):
+ parent._delObject(obj_id)
+ continue
+
+ if obj_id not in parent.objectIds():
+ meta_type = str(child.getAttribute('meta_type'))
+ __traceback_info__ = obj_id, meta_type
+ for mt_info in Products.meta_types:
+ if mt_info['name'] == meta_type:
+ parent._setObject(obj_id, mt_info['instance'](obj_id))
+ break
+ else:
+ raise ValueError("unknown meta_type '%s'" % meta_type)
+
+ if child.hasAttribute('insert-before'):
+ insert_before = child.getAttribute('insert-before')
+ if insert_before == '*':
+ parent.moveObjectsToTop(obj_id)
+ else:
+ try:
+ position = parent.getObjectPosition(insert_before)
+ if parent.getObjectPosition(obj_id) < position:
+ position -= 1
+ parent.moveObjectToPosition(obj_id, position)
+ except ValueError:
+ pass
+ elif child.hasAttribute('insert-after'):
+ insert_after = child.getAttribute('insert-after')
+ if insert_after == '*':
+ parent.moveObjectsToBottom(obj_id)
+ else:
+ try:
+ position = parent.getObjectPosition(insert_after)
+ if parent.getObjectPosition(obj_id) < position:
+ position -= 1
+ parent.moveObjectToPosition(obj_id, position+1)
+ except ValueError:
+ pass
+
+ obj = getattr(self.context, obj_id)
+ importer = queryMultiAdapter((obj, self.environ), INode)
+ if importer:
+ importer.node = child
+
+
+class PropertyManagerHelpers(object):
+
+ """PropertyManager im- and export helpers.
+ """
+
+ _encoding = default_encoding
+
+ def _extractProperties(self):
+ fragment = self._doc.createDocumentFragment()
+
+ for prop_map in self.context._propertyMap():
+ prop_id = prop_map['id']
+ if prop_id == 'i18n_domain':
+ continue
+
+ # Don't export read-only nodes
+ if 'w' not in prop_map.get('mode', 'wd'):
+ continue
+
+ node = self._doc.createElement('property')
+ node.setAttribute('name', prop_id)
+
+ prop = self.context.getProperty(prop_id)
+ if isinstance(prop, (tuple, list)):
+ for value in prop:
+ if isinstance(value, str):
+ value.decode(self._encoding)
+ child = self._doc.createElement('element')
+ child.setAttribute('value', value)
+ node.appendChild(child)
+ else:
+ if prop_map.get('type') == 'boolean':
+ prop = unicode(bool(prop))
+ elif isinstance(prop, str):
+ prop = prop.decode(self._encoding)
+ elif not isinstance(prop, basestring):
+ prop = unicode(prop)
+ child = self._doc.createTextNode(prop)
+ node.appendChild(child)
+
+ if 'd' in prop_map.get('mode', 'wd') and not prop_id == 'title':
+ prop_type = prop_map.get('type', 'string')
+ node.setAttribute('type', unicode(prop_type))
+ select_variable = prop_map.get('select_variable', None)
+ if select_variable is not None:
+ node.setAttribute('select_variable', select_variable)
+
+ if hasattr(self, '_i18n_props') and prop_id in self._i18n_props:
+ node.setAttribute('i18n:translate', '')
+
+ fragment.appendChild(node)
+
+ return fragment
+
+ def _purgeProperties(self):
+ for prop_map in self.context._propertyMap():
+ mode = prop_map.get('mode', 'wd')
+ if 'w' not in mode:
+ continue
+ prop_id = prop_map['id']
+ if 'd' in mode and not prop_id == 'title':
+ self.context._delProperty(prop_id)
+ else:
+ prop_type = prop_map.get('type')
+ if prop_type == 'multiple selection':
+ prop_value = ()
+ elif prop_type in ('int', 'float'):
+ prop_value = 0
+ else:
+ prop_value = ''
+ self.context._updateProperty(prop_id, prop_value)
+
+ def _initProperties(self, node):
+ obj = self.context
+ if node.hasAttribute('i18n:domain'):
+ i18n_domain = str(node.getAttribute('i18n:domain'))
+ obj._updateProperty('i18n_domain', i18n_domain)
+ for child in node.childNodes:
+ if child.nodeName != 'property':
+ continue
+ prop_id = str(child.getAttribute('name'))
+ prop_map = obj.propdict().get(prop_id, None)
+
+ if prop_map is None:
+ if child.hasAttribute('type'):
+ val = str(child.getAttribute('select_variable'))
+ prop_type = str(child.getAttribute('type'))
+ obj._setProperty(prop_id, val, prop_type)
+ prop_map = obj.propdict().get(prop_id, None)
+ else:
+ raise ValueError("undefined property '%s'" % prop_id)
+
+ if not 'w' in prop_map.get('mode', 'wd'):
+ raise BadRequest('%s cannot be changed' % prop_id)
+
+ elements = []
+ for sub in child.childNodes:
+ if sub.nodeName == 'element':
+ value = sub.getAttribute('value')
+ elements.append(value.encode(self._encoding))
+
+ if elements or prop_map.get('type') == 'multiple selection':
+ prop_value = tuple(elements) or ()
+ elif prop_map.get('type') == 'boolean':
+ prop_value = self._convertToBoolean(self._getNodeText(child))
+ else:
+ # if we pass a *string* to _updateProperty, all other values
+ # are converted to the right type
+ prop_value = self._getNodeText(child).encode(self._encoding)
+
+ if not self._convertToBoolean(child.getAttribute('purge')
+ or 'True'):
+ # If the purge attribute is False, merge sequences
+ prop = obj.getProperty(prop_id)
+ if isinstance(prop, (tuple, list)):
+ prop_value = (tuple([p for p in prop
+ if p not in prop_value]) +
+ tuple(prop_value))
+
+ obj._updateProperty(prop_id, prop_value)
+
+
+def exportObjects(obj, parent_path, context):
+ """ Export subobjects recursively.
+ """
+ exporter = queryMultiAdapter((obj, context), IBody)
+ path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
+ if exporter:
+ if exporter.name:
+ path = '%s%s' % (parent_path, exporter.name)
+ filename = '%s%s' % (path, exporter.suffix)
+ body = exporter.body
+ if body is not None:
+ context.writeDataFile(filename, body, exporter.mime_type)
+
+ if getattr(obj, 'objectValues', False):
+ for sub in obj.objectValues():
+ exportObjects(sub, path+'/', context)
+
+def importObjects(obj, parent_path, context):
+ """ Import subobjects recursively.
+ """
+ importer = queryMultiAdapter((obj, context), IBody)
+ path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
+ __traceback_info__ = path
+ if importer:
+ if importer.name:
+ path = '%s%s' % (parent_path, importer.name)
+ filename = '%s%s' % (path, importer.suffix)
+ body = context.readDataFile(filename)
+ if body is not None:
+ importer.filename = filename # for error reporting
+ importer.body = body
+
+ if getattr(obj, 'objectValues', False):
+ for sub in obj.objectValues():
+ importObjects(sub, path+'/', context)
+
+
+def _computeTopologicalSort( steps ):
+ result = []
+ graph = [ ( x[ 'id' ], x[ 'dependencies' ] ) for x in steps ]
+
+ unresolved = []
+
+ while 1:
+ for node, edges in graph:
+
+ after = -1
+ resolved = 0
+
+ for edge in edges:
+
+ if edge in result:
+ resolved += 1
+ after = max( after, result.index( edge ) )
+
+ if len(edges) > resolved:
+ unresolved.append((node, edges))
+ else:
+ result.insert( after + 1, node )
+
+ if not unresolved:
+ break
+ if len(unresolved) == len(graph):
+ # Nothing was resolved in this loop. There must be circular or
+ # missing dependencies. Just add them to the end. We can't
+ # raise an error, because checkComplete relies on this method.
+ for node, edges in unresolved:
+ result.append(node)
+ break
+ graph = unresolved
+ unresolved = []
+
+ return result
Deleted: GenericSetup/branches/wichert-events/www/sutExportSteps.zpt
===================================================================
--- GenericSetup/trunk/www/sutExportSteps.zpt 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/www/sutExportSteps.zpt 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,75 +0,0 @@
-<h1 tal:replace="structure here/manage_page_header"> PAGE HEADER </h1>
-<h2 tal:replace="structure here/manage_tabs"> PAGE HEADER </h2>
-
-<h3> Site Configuration Export Steps </h3>
-
-
-<p class="form-help">
-Download selected export steps as tarball.
-</p>
-
-<h3>Available Export Steps</h3>
-
-<form action="." method="POST"
- tal:attributes="action here/absolute_url" >
-<input type="hidden" name="ids:default:tokens" value="" />
-
-<table cellspacing="0" cellpadding="4">
-
- <thead>
- <tr class="list-header">
- <td class="list-item">Sel.</td>
- <td class="list-item">#</td>
- <td class="list-item">Title / Description</td>
- <td class="list-item">Handler</td>
- </tr>
- </thead>
-
- <tbody tal:define="registry here/getExportStepRegistry;
- step_ids registry/listSteps;
- ">
- <tal:loop tal:repeat="step_id step_ids">
- <tr valign="top"
- tal:define="info python: registry.getStepMetadata( step_id );"
- tal:attributes="class python:
- repeat[ 'step_id' ].odd and 'row-normal' or 'row-hilite'" >
- <td class="list-item" width="16">
- <input type="checkbox" name="ids:list" value="STEP_ID"
- tal:attributes="value step_id" />
- </td>
- <td align="right" class="list-item"
- tal:content="repeat/step_id/number">1</td>
- <td class="list-item">
- <span tal:content="info/title">STEP TITLE</span><br />
- <em tal:content="info/description">STEP DESCRIPTION</em>
- </td>
- <td class="list-item"
- tal:content="info/handler">DOTTED.NAME</td>
- </tr>
- </tal:loop>
-
- <tr valign="top" class="list-header">
- <td colspan="4"> </td>
- </tr>
-
- <tr valign="top">
- <td />
- <td colspan="3">
-
- <input class="form-element" type="submit"
- name="manage_exportSelectedSteps:method"
- value=" Export selected steps " />
-
- <input class="form-element" type="submit"
- name="manage_exportAllSteps:method"
- value=" Export all steps " />
-
- </td>
- </tr>
- </tbody>
-
-</table>
-</form>
-
-
-<h1 tal:replace="structure here/manage_page_footer"> PAGE FOOTER </h1>
Copied: GenericSetup/branches/wichert-events/www/sutExportSteps.zpt (from rev 81471, GenericSetup/trunk/www/sutExportSteps.zpt)
===================================================================
--- GenericSetup/branches/wichert-events/www/sutExportSteps.zpt (rev 0)
+++ GenericSetup/branches/wichert-events/www/sutExportSteps.zpt 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,74 @@
+<h1 tal:replace="structure here/manage_page_header"> PAGE HEADER </h1>
+<h2 tal:replace="structure here/manage_tabs"> PAGE HEADER </h2>
+
+<h3> Site Configuration Export Steps </h3>
+
+
+<p class="form-help">
+Download selected export steps as tarball.
+</p>
+
+<h3>Available Export Steps</h3>
+
+<form action="." method="POST"
+ tal:attributes="action here/absolute_url" >
+<input type="hidden" name="ids:default:tokens" value="" />
+
+<table cellspacing="0" cellpadding="4">
+
+ <thead>
+ <tr class="list-header">
+ <td class="list-item">Sel.</td>
+ <td class="list-item">#</td>
+ <td class="list-item">Title / Description</td>
+ <td class="list-item">Handler</td>
+ </tr>
+ </thead>
+
+ <tbody tal:define="step_ids here/listExportSteps;
+ ">
+ <tal:loop tal:repeat="step_id step_ids">
+ <tr valign="top"
+ tal:define="info python: here.getExportStepMetadata( step_id );"
+ tal:attributes="class python:
+ repeat[ 'step_id' ].odd and 'row-normal' or 'row-hilite'" >
+ <td class="list-item" width="16">
+ <input type="checkbox" name="ids:list" value="STEP_ID"
+ tal:attributes="value step_id" />
+ </td>
+ <td align="right" class="list-item"
+ tal:content="repeat/step_id/number">1</td>
+ <td class="list-item">
+ <span tal:content="info/title">STEP TITLE</span><br />
+ <em tal:content="info/description">STEP DESCRIPTION</em>
+ </td>
+ <td class="list-item"
+ tal:content="info/handler">DOTTED.NAME</td>
+ </tr>
+ </tal:loop>
+
+ <tr valign="top" class="list-header">
+ <td colspan="4"> </td>
+ </tr>
+
+ <tr valign="top">
+ <td />
+ <td colspan="3">
+
+ <input class="form-element" type="submit"
+ name="manage_exportSelectedSteps:method"
+ value=" Export selected steps " />
+
+ <input class="form-element" type="submit"
+ name="manage_exportAllSteps:method"
+ value=" Export all steps " />
+
+ </td>
+ </tr>
+ </tbody>
+
+</table>
+</form>
+
+
+<h1 tal:replace="structure here/manage_page_footer"> PAGE FOOTER </h1>
Deleted: GenericSetup/branches/wichert-events/www/sutImportSteps.zpt
===================================================================
--- GenericSetup/trunk/www/sutImportSteps.zpt 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/www/sutImportSteps.zpt 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,135 +0,0 @@
-<tal:block define="base_context_id context/getBaselineContextID;
- context_id request/context_id|base_context_id;
- contexts context/listContextInfos;
- context_title python:[c['title'] for c in contexts if c['id']==context_id][0];
- extension_contexts python:[c for c in contexts if c['type'] in ['extension','snapshot']];
- ">
-<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
-<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
- tal:replace="structure context/manage_tabs">TABS</h2>
-
-<h2> Site Configuration Import Steps </h2>
-
-<p class="form-help">
-This tool allows one to re-run individual steps of the site setup
-procedure, in order to pick up changes since the site was created.
-</p>
-
-<h3>Select Profile or Snapshot</h3>
-
-<form action="." method="POST" id="profileform" tal:attributes="action string:${context/absolute_url}/manage_importSteps">
-<select name="context_id"
- onchange="document.getElementById('profileform').submit();">
- <option value=""
- tal:attributes="value base_context_id;
- selected python:context_id==base_context_id">
- Current base profile</option>
- <option value="context-CONTEXT_ID"
- tal:repeat="context extension_contexts"
- tal:attributes="value context/id; selected python:context_id==context['id']"
- tal:content="context/title">CONTEXT_TITLE</option>
-</select>
- <noscript>
- <input class="form-element" type="submit"
- name="manage_importSteps:method"
- value="Switch profile" />
- </noscript>
-</form>
-
-
-<h3>Available Import Steps for "<span tal:replace="context_title">Base profile</span>"</h3>
-
-<form action="." method="POST" enctype="multipart/form-data"
- tal:attributes="action context/absolute_url" >
-<tal:dummy define="dummy python:context.applyContextById(context_id)"/>
-<input type="hidden" name="ids:default:tokens" value="" />
-
-<table cellspacing="0" cellpadding="4">
-
- <thead>
- <tr class="list-header">
- <td class="list-item">Sel.</td>
- <td class="list-item">#</td>
- <td class="list-item">Title / Description</td>
- <td class="list-item">Handler</td>
- </tr>
- </thead>
-
- <tbody tal:define="registry context/getImportStepRegistry;
- step_ids registry/sortSteps;
- ">
- <tal:loop tal:repeat="step_id step_ids">
- <tr valign="top"
- tal:define="info python: registry.getStepMetadata( step_id );"
- tal:attributes="class python:
- repeat[ 'step_id' ].odd and 'row-normal' or 'row-hilite';
- style python:info['invalid'] and 'background: red' or None" >
- <td class="list-item" width="16">
- <input type="checkbox" name="ids:list" value="STEP_ID"
- tal:attributes="value step_id" />
- </td>
- <td align="right" class="list-item"
- tal:content="repeat/step_id/number">1</td>
- <td class="list-item">
- <span tal:content="info/title">STEP TITLE</span><br />
- <em tal:content="info/description">STEP DESCRIPTION</em>
- </td>
- <td class="list-item"
- tal:content="info/handler">DOTTED.NAME</td>
- </tr>
- </tal:loop>
-
- <tr valign="top" class="list-header">
- <td colspan="4"> </td>
- </tr>
-
- <tr valign="top">
- <td />
- <td colspan="3">
-
- <input type="hidden" name="context_id" value="" tal:attributes="value context_id"/>
- <input type="hidden" name="run_dependencies:int:default" value="0" />
- <input class="form-element" type="checkbox" id="run_dependencies"
- name="run_dependencies:boolean" value="1" checked="checked" />
- <label for="run_dependencies">Include dependencies?</label>
-
-
- <input class="form-element" type="submit"
- name="manage_importSelectedSteps:method"
- value=" Import selected steps " />
-
- <input class="form-element" type="submit"
- name="manage_importAllSteps:method"
- value=" Import all steps " />
-
- <input class="form-element" type="file"
- name="tarball" />
- <input class="form-element" type="submit"
- name="manage_importTarball:method"
- value=" Import uploaded tarball " />
- </td>
- </tr>
- </tbody>
-</table>
-
-<table cellspacing="0" cellpadding="4"
- tal:condition="options/messages | nothing">
-
- <tr class="list-header">
- <td colspan="4">Message Log</td>
- </tr>
-
- <tr valign="top"
- tal:repeat="item options/messages/items">
- <td tal:content="python: item[0]">STEP</td>
- <td colspan="3"
- tal:content="structure python: item[1].replace('\n', '<br />')"
- >MESSAGE</td>
- </tr>
-
-</table>
-</form>
-
-<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
-</tal:block>
-
Copied: GenericSetup/branches/wichert-events/www/sutImportSteps.zpt (from rev 81471, GenericSetup/trunk/www/sutImportSteps.zpt)
===================================================================
--- GenericSetup/branches/wichert-events/www/sutImportSteps.zpt (rev 0)
+++ GenericSetup/branches/wichert-events/www/sutImportSteps.zpt 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,134 @@
+<tal:block define="base_context_id context/getBaselineContextID;
+ context_id request/context_id|base_context_id;
+ contexts context/listContextInfos;
+ context_title python:[c['title'] for c in contexts if c['id']==context_id][0];
+ extension_contexts python:[c for c in contexts if c['type'] in ['extension','snapshot']];
+ ">
+<h1 tal:replace="structure context/manage_page_header">PAGE HEADER</h1>
+<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
+ tal:replace="structure context/manage_tabs">TABS</h2>
+
+<h2> Site Configuration Import Steps </h2>
+
+<p class="form-help">
+This tool allows one to re-run individual steps of the site setup
+procedure, in order to pick up changes since the site was created.
+</p>
+
+<h3>Select Profile or Snapshot</h3>
+
+<form action="." method="POST" id="profileform" tal:attributes="action string:${context/absolute_url}/manage_importSteps">
+<select name="context_id"
+ onchange="document.getElementById('profileform').submit();">
+ <option value=""
+ tal:attributes="value base_context_id;
+ selected python:context_id==base_context_id">
+ Current base profile</option>
+ <option value="context-CONTEXT_ID"
+ tal:repeat="context extension_contexts"
+ tal:attributes="value context/id; selected python:context_id==context['id']"
+ tal:content="context/title">CONTEXT_TITLE</option>
+</select>
+ <noscript>
+ <input class="form-element" type="submit"
+ name="manage_importSteps:method"
+ value="Switch profile" />
+ </noscript>
+</form>
+
+
+<h3>Available Import Steps for "<span tal:replace="context_title">Base profile</span>"</h3>
+
+<form action="." method="POST" enctype="multipart/form-data"
+ tal:attributes="action context/absolute_url" >
+<tal:dummy define="dummy python:context.applyContextById(context_id)"/>
+<input type="hidden" name="ids:default:tokens" value="" />
+
+<table cellspacing="0" cellpadding="4">
+
+ <thead>
+ <tr class="list-header">
+ <td class="list-item">Sel.</td>
+ <td class="list-item">#</td>
+ <td class="list-item">Title / Description</td>
+ <td class="list-item">Handler</td>
+ </tr>
+ </thead>
+
+ <tbody tal:define="step_ids context/getSortedImportSteps;
+ ">
+ <tal:loop tal:repeat="step_id step_ids">
+ <tr valign="top"
+ tal:define="info python: context.getImportStepMetadata( step_id );"
+ tal:attributes="class python:
+ repeat[ 'step_id' ].odd and 'row-normal' or 'row-hilite';
+ style python:info['invalid'] and 'background: red' or None" >
+ <td class="list-item" width="16">
+ <input type="checkbox" name="ids:list" value="STEP_ID"
+ tal:attributes="value step_id" />
+ </td>
+ <td align="right" class="list-item"
+ tal:content="repeat/step_id/number">1</td>
+ <td class="list-item">
+ <span tal:content="info/title">STEP TITLE</span><br />
+ <em tal:content="info/description">STEP DESCRIPTION</em>
+ </td>
+ <td class="list-item"
+ tal:content="info/handler">DOTTED.NAME</td>
+ </tr>
+ </tal:loop>
+
+ <tr valign="top" class="list-header">
+ <td colspan="4"> </td>
+ </tr>
+
+ <tr valign="top">
+ <td />
+ <td colspan="3">
+
+ <input type="hidden" name="context_id" value="" tal:attributes="value context_id"/>
+ <input type="hidden" name="run_dependencies:int:default" value="0" />
+ <input class="form-element" type="checkbox" id="run_dependencies"
+ name="run_dependencies:boolean" value="1" checked="checked" />
+ <label for="run_dependencies">Include dependencies?</label>
+
+
+ <input class="form-element" type="submit"
+ name="manage_importSelectedSteps:method"
+ value=" Import selected steps " />
+
+ <input class="form-element" type="submit"
+ name="manage_importAllSteps:method"
+ value=" Import all steps " />
+
+ <input class="form-element" type="file"
+ name="tarball" />
+ <input class="form-element" type="submit"
+ name="manage_importTarball:method"
+ value=" Import uploaded tarball " />
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<table cellspacing="0" cellpadding="4"
+ tal:condition="options/messages | nothing">
+
+ <tr class="list-header">
+ <td colspan="4">Message Log</td>
+ </tr>
+
+ <tr valign="top"
+ tal:repeat="item options/messages/items">
+ <td tal:content="python: item[0]">STEP</td>
+ <td colspan="3"
+ tal:content="structure python: item[1].replace('\n', '<br />')"
+ >MESSAGE</td>
+ </tr>
+
+</table>
+</form>
+
+<h1 tal:replace="structure context/manage_page_footer">PAGE FOOTER</h1>
+</tal:block>
+
Deleted: GenericSetup/branches/wichert-events/zcml.py
===================================================================
--- GenericSetup/trunk/zcml.py 2007-11-04 07:07:19 UTC (rev 81470)
+++ GenericSetup/branches/wichert-events/zcml.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -1,191 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-"""GenericSetup ZCML directives.
-
-$Id$
-"""
-
-from zope.configuration.fields import GlobalObject
-from zope.configuration.fields import MessageID
-from zope.configuration.fields import Path
-from zope.configuration.fields import PythonIdentifier
-from zope.interface import Interface
-
-from interfaces import BASE
-from registry import _profile_registry
-from upgrade import _upgrade_registry
-
-#### genericsetup:registerProfile
-
-class IRegisterProfileDirective(Interface):
-
- """Register profiles with the global registry.
- """
-
- name = PythonIdentifier(
- title=u'Name',
- description=u'',
- required=True)
-
- title = MessageID(
- title=u'Title',
- description=u'',
- required=True)
-
- description = MessageID(
- title=u'Description',
- description=u'',
- required=True)
-
- directory = Path(
- title=u'Path',
- description=u"If not specified 'profiles/<name>' is used.",
- required=False)
-
- provides = GlobalObject(
- title=u'Type',
- description=u"If not specified 'BASE' is used.",
- default=BASE,
- required=False)
-
- for_ = GlobalObject(
- title=u'Site Interface',
- description=u'If not specified the profile is always available.',
- default=None,
- required=False)
-
-
-_profile_regs = []
-def registerProfile(_context, name, title, description, directory=None,
- provides=BASE, for_=None):
- """ Add a new profile to the registry.
- """
- product = _context.package.__name__
- if directory is None:
- directory = 'profiles/%s' % name
-
- _profile_regs.append('%s:%s' % (product, name))
-
- _context.action(
- discriminator = ('registerProfile', product, name),
- callable = _profile_registry.registerProfile,
- args = (name, title, description, directory, product, provides, for_)
- )
-
-
-#### genericsetup:upgradeStep
-
-import zope.schema
-from upgrade import UpgradeStep
-from upgrade import _registerUpgradeStep
-from upgrade import _registerNestedUpgradeStep
-
-class IUpgradeStepsDirective(Interface):
- """
- Define multiple upgrade steps without repeating all of the parameters
- """
- source = zope.schema.ASCII(
- title=u"Source version",
- required=False)
-
- destination = zope.schema.ASCII(
- title=u"Destination version",
- required=False)
-
- sortkey = zope.schema.Int(
- title=u"Sort key",
- required=False)
-
- profile = zope.schema.TextLine(
- title=u"GenericSetup profile id",
- required=True)
-
-class IUpgradeStepsStepSubDirective(Interface):
- """
- Subdirective to IUpgradeStepsDirective
- """
- title = zope.schema.TextLine(
- title=u"Title",
- required=True)
-
- description = zope.schema.TextLine(
- title=u"Upgrade step description",
- required=True)
-
- handler = GlobalObject(
- title=u"Upgrade handler",
- required=True)
-
- checker = GlobalObject(
- title=u"Upgrade checker",
- required=False)
-
-class IUpgradeStepDirective(IUpgradeStepsDirective, IUpgradeStepsStepSubDirective):
- """
- Define multiple upgrade steps without repeating all of the parameters
- """
-
-
-def upgradeStep(_context, title, profile, handler, description=None, source='*',
- destination='*', sortkey=0, checker=None):
- step = UpgradeStep(title, profile, source, destination, description, handler,
- checker, sortkey)
- _context.action(
- discriminator = ('upgradeStep', source, destination, handler, sortkey),
- callable = _registerUpgradeStep,
- args = (step,),
- )
-
-class upgradeSteps(object):
- """
- Allows nested upgrade steps.
- """
- def __init__(self, _context, profile, source='*', destination='*', sortkey=0):
- self.profile = profile
- self.source = source
- self.dest = destination
- self.sortkey = sortkey
- self.id = None
-
- def upgradeStep(self, _context, title, description, handler, checker=None):
- step = UpgradeStep(title, self.profile, self.source, self.dest, description,
- handler, checker, self.sortkey)
- if self.id is None:
- self.id = str(abs(hash('%s%s%s%s' % (title, self.source, self.dest,
- self.sortkey))))
- _context.action(
- discriminator = ('upgradeStep', self.source, self.dest, handler,
- self.sortkey),
- callable = _registerNestedUpgradeStep,
- args = (step, self.id),
- )
-
- def __call__(self):
- return ()
-
-
-#### cleanup
-
-def cleanUp():
- global _profile_regs
- for profile_id in _profile_regs:
- del _profile_registry._profile_info[profile_id]
- _profile_registry._profile_ids.remove(profile_id)
- _profile_regs = []
-
- _upgrade_registry.clear()
-
-
-from zope.testing.cleanup import addCleanUp
-addCleanUp(cleanUp)
-del addCleanUp
Copied: GenericSetup/branches/wichert-events/zcml.py (from rev 81471, GenericSetup/trunk/zcml.py)
===================================================================
--- GenericSetup/branches/wichert-events/zcml.py (rev 0)
+++ GenericSetup/branches/wichert-events/zcml.py 2007-11-04 14:51:55 UTC (rev 81473)
@@ -0,0 +1,319 @@
+
+#
+# Copyright (c) 2006-2007 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.
+#
+##############################################################################
+"""GenericSetup ZCML directives.
+
+$Id$
+"""
+
+from zope.configuration.fields import GlobalObject
+from zope.configuration.fields import MessageID
+from zope.configuration.fields import Path
+from zope.configuration.fields import PythonIdentifier
+from zope.interface import Interface
+
+from interfaces import BASE
+from registry import _import_step_registry
+from registry import _export_step_registry
+from registry import _profile_registry
+from upgrade import _upgrade_registry
+
+#### genericsetup:registerProfile
+
+class IRegisterProfileDirective(Interface):
+
+ """Register profiles with the global registry.
+ """
+
+ name = PythonIdentifier(
+ title=u'Name',
+ description=u'',
+ required=True)
+
+ title = MessageID(
+ title=u'Title',
+ description=u'',
+ required=True)
+
+ description = MessageID(
+ title=u'Description',
+ description=u'',
+ required=True)
+
+ directory = Path(
+ title=u'Path',
+ description=u"If not specified 'profiles/<name>' is used.",
+ required=False)
+
+ provides = GlobalObject(
+ title=u'Type',
+ description=u"If not specified 'BASE' is used.",
+ default=BASE,
+ required=False)
+
+ for_ = GlobalObject(
+ title=u'Site Interface',
+ description=u'If not specified the profile is always available.',
+ default=None,
+ required=False)
+
+
+_profile_regs = []
+def registerProfile(_context, name, title, description, directory=None,
+ provides=BASE, for_=None):
+ """ Add a new profile to the registry.
+ """
+ product = _context.package.__name__
+ if directory is None:
+ directory = 'profiles/%s' % name
+
+ _profile_regs.append('%s:%s' % (product, name))
+
+ _context.action(
+ discriminator = ('registerProfile', product, name),
+ callable = _profile_registry.registerProfile,
+ args = (name, title, description, directory, product, provides, for_)
+ )
+
+
+#### genericsetup:exportStep
+
+class IExportStepDirective(Interface):
+ name = PythonIdentifier(
+ title=u'Name',
+ description=u'',
+ required=True)
+
+ title = MessageID(
+ title=u'Title',
+ description=u'',
+ required=True)
+
+ description = MessageID(
+ title=u'Description',
+ description=u'',
+ required=True)
+
+ handler = GlobalObject(
+ title=u'Handler',
+ description=u'',
+ required=True)
+
+
+_export_step_regs = []
+
+def exportStep(context, name, handler, title=None, description=None):
+ global _export_step_regs
+ _export_step_regs.append(name)
+
+ context.action(
+ discriminator = ('exportStep', name),
+ callable = _export_step_registry.registerStep,
+ args = (name, handler, title, description),
+ )
+#### genericsetup:importStep
+
+class IImportStepDirective(Interface):
+
+ """Register import steps with the global registry.
+ """
+
+ name = PythonIdentifier(
+ title=u'Name',
+ description=u'',
+ required=True)
+
+ title = MessageID(
+ title=u'Title',
+ description=u'',
+ required=True)
+
+ description = MessageID(
+ title=u'Description',
+ description=u'',
+ required=True)
+
+ handler = GlobalObject(
+ title=u'Handler',
+ description=u'',
+ required=True)
+
+ version = MessageID(
+ title=u'Version',
+ description=u'',
+ required=True)
+
+class IImportStepDependsDirective(Interface):
+ name = PythonIdentifier(
+ title=u'Name',
+ description=u'Name of another import step that has to be run first',
+ required=True)
+
+_import_step_regs = []
+
+class importStep:
+ def __init__(self, context, name, version, title, description, handler):
+ """ Add a new import step to the registry.
+ """
+ self.context=context
+ self.discriminator = ('importStep', name),
+ self.name=name
+ self.version=version
+ self.handler=handler
+ self.title=title
+ self.description=description
+ self.dependencies=()
+
+
+ def depends(self, context, name):
+ self.dependencies+=(name,)
+
+
+ def __call__(self):
+ global _import_step_regs
+ _import_step_regs.append(self.name)
+
+ self.context.action(
+ discriminator = self.discriminator,
+ callable = _import_step_registry.registerStep,
+ args = (self.name, self.version, self.handler, self.dependencies,
+ self.title, self.description),
+ )
+
+#### genericsetup:upgradeStep
+
+import zope.schema
+from upgrade import UpgradeStep
+from upgrade import _registerUpgradeStep
+from upgrade import _registerNestedUpgradeStep
+
+class IUpgradeStepsDirective(Interface):
+ """
+ Define multiple upgrade steps without repeating all of the parameters
+ """
+ source = zope.schema.ASCII(
+ title=u"Source version",
+ required=False)
+
+ destination = zope.schema.ASCII(
+ title=u"Destination version",
+ required=False)
+
+ sortkey = zope.schema.Int(
+ title=u"Sort key",
+ required=False)
+
+ profile = zope.schema.TextLine(
+ title=u"GenericSetup profile id",
+ required=True)
+
+class IUpgradeStepsStepSubDirective(Interface):
+ """
+ Subdirective to IUpgradeStepsDirective
+ """
+ title = zope.schema.TextLine(
+ title=u"Title",
+ required=True)
+
+ description = zope.schema.TextLine(
+ title=u"Upgrade step description",
+ required=True)
+
+ handler = GlobalObject(
+ title=u"Upgrade handler",
+ required=True)
+
+ checker = GlobalObject(
+ title=u"Upgrade checker",
+ required=False)
+
+class IUpgradeStepDirective(IUpgradeStepsDirective, IUpgradeStepsStepSubDirective):
+ """
+ Define multiple upgrade steps without repeating all of the parameters
+ """
+
+
+def upgradeStep(_context, title, profile, handler, description=None, source='*',
+ destination='*', sortkey=0, checker=None):
+ step = UpgradeStep(title, profile, source, destination, description, handler,
+ checker, sortkey)
+ _context.action(
+ discriminator = ('upgradeStep', source, destination, handler, sortkey),
+ callable = _registerUpgradeStep,
+ args = (step,),
+ )
+
+class upgradeSteps(object):
+ """
+ Allows nested upgrade steps.
+ """
+ def __init__(self, _context, profile, source='*', destination='*', sortkey=0):
+ self.profile = profile
+ self.source = source
+ self.dest = destination
+ self.sortkey = sortkey
+ self.id = None
+
+ def upgradeStep(self, _context, title, description, handler, checker=None):
+ step = UpgradeStep(title, self.profile, self.source, self.dest, description,
+ handler, checker, self.sortkey)
+ if self.id is None:
+ self.id = str(abs(hash('%s%s%s%s' % (title, self.source, self.dest,
+ self.sortkey))))
+ _context.action(
+ discriminator = ('upgradeStep', self.source, self.dest, handler,
+ self.sortkey),
+ callable = _registerNestedUpgradeStep,
+ args = (step, self.id),
+ )
+
+ def __call__(self):
+ return ()
+
+
+#### cleanup
+
+def cleanUpProfiles():
+ global _profile_regs
+ for profile_id in _profile_regs:
+ del _profile_registry._profile_info[profile_id]
+ _profile_registry._profile_ids.remove(profile_id)
+ _profile_regs = []
+
+ _upgrade_registry.clear()
+
+
+def cleanUpImportSteps():
+ global _import_step_regs
+ for name in _import_step_regs:
+ try:
+ _import_step_registry.unregisterStep( name )
+ except KeyError:
+ pass
+
+ _import_step_regs=[]
+
+def cleanUpExportSteps():
+ global _export_step_regs
+ for name in _export_step_regs:
+ try:
+ _export_step_registry.unregisterStep( name )
+ except KeyError:
+ pass
+
+ _export_step_regs=[]
+
+from zope.testing.cleanup import addCleanUp
+addCleanUp(cleanUpProfiles)
+addCleanUp(cleanUpImportSteps)
+addCleanUp(cleanUpExportSteps)
+del addCleanUp
More information about the Checkins
mailing list