vendor/CMF/1.5.3/CMFSetup

view registry.py @ 0:3ed006215eb6

Vendor import of CMF 1.5.3
author fguillaume
date Tue, 09 Aug 2005 10:47:34 +0000
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE.
11 #
12 ##############################################################################
13 """ Classes: ImportStepRegistry, ExportStepRegistry
15 $Id: registry.py 36896 2005-04-05 15:17:18Z yuppie $
16 """
18 from xml.sax import parseString
20 from AccessControl import ClassSecurityInfo
21 from Acquisition import Implicit
22 from Globals import InitializeClass
23 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
25 from interfaces import BASE
26 from interfaces import IImportStepRegistry
27 from interfaces import IExportStepRegistry
28 from interfaces import IToolsetRegistry
29 from interfaces import IProfileRegistry
30 from permissions import ManagePortal
31 from utils import HandlerBase
32 from utils import _xmldir
33 from utils import _getDottedName
34 from utils import _resolveDottedName
35 from utils import _extractDocstring
38 class ImportStepRegistry( Implicit ):
40 """ Manage knowledge about steps to create / configure site.
42 o Steps are composed together to define a site profile.
43 """
44 __implements__ = ( IImportStepRegistry, )
46 security = ClassSecurityInfo()
48 def __init__( self ):
50 self.clear()
52 security.declareProtected( ManagePortal, 'listSteps' )
53 def listSteps( self ):
55 """ Return a sequence of IDs of registered steps.
57 o Order is not significant.
58 """
59 return self._registered.keys()
61 security.declareProtected( ManagePortal, 'sortSteps' )
62 def sortSteps( self ):
64 """ Return a sequence of registered step IDs
66 o Sequence is sorted topologically by dependency, with the dependent
67 steps *after* the steps they depend on.
68 """
69 return self._computeTopologicalSort()
71 security.declareProtected( ManagePortal, 'checkComplete' )
72 def checkComplete( self ):
74 """ Return a sequence of ( node, edge ) tuples for unsatisifed deps.
75 """
76 result = []
77 seen = {}
79 graph = self._computeTopologicalSort()
81 for node in graph:
83 dependencies = self.getStepMetadata( node )[ 'dependencies' ]
85 for dependency in dependencies:
87 if seen.get( dependency ) is None:
88 result.append( ( node, dependency ) )
90 seen[ node ] = 1
92 return result
94 security.declareProtected( ManagePortal, 'getStepMetadata' )
95 def getStepMetadata( self, key, default=None ):
97 """ Return a mapping of metadata for the step identified by 'key'.
99 o Return 'default' if no such step is registered.
101 o The 'handler' metadata is available via 'getStep'.
102 """
103 result = {}
105 info = self._registered.get( key )
107 if info is None:
108 return default
110 return info.copy()
112 security.declareProtected( ManagePortal, 'listStepMetadata' )
113 def listStepMetadata( self ):
115 """ Return a sequence of mappings describing registered steps.
117 o Mappings will be ordered alphabetically.
118 """
119 step_ids = self.listSteps()
120 step_ids.sort()
121 return [ self.getStepMetadata( x ) for x in step_ids ]
123 security.declareProtected( ManagePortal, 'generateXML' )
124 def generateXML( self ):
126 """ Return a round-trippable XML representation of the registry.
128 o 'handler' values are serialized using their dotted names.
129 """
130 return self._exportTemplate()
132 security.declarePrivate( 'getStep' )
133 def getStep( self, key, default=None ):
135 """ Return the IImportPlugin registered for 'key'.
137 o Return 'default' if no such step is registered.
138 """
139 marker = object()
140 info = self._registered.get( key, marker )
142 if info is marker:
143 return default
145 return _resolveDottedName( info[ 'handler' ] )
147 security.declarePrivate( 'registerStep' )
148 def registerStep( self
149 , id
150 , version
151 , handler
152 , dependencies=()
153 , title=None
154 , description=None
155 ):
156 """ Register a setup step.
158 o 'id' is a unique name for this step,
160 o 'version' is a string for comparing versions, it is preferred to
161 be a yyyy/mm/dd-ii formatted string (date plus two-digit
162 ordinal). when comparing two version strings, the version with
163 the lower sort order is considered the older version.
165 - Newer versions of a step supplant older ones.
167 - Attempting to register an older one after a newer one results
168 in a KeyError.
170 o 'handler' should implement IImportPlugin.
172 o 'dependencies' is a tuple of step ids which have to run before
173 this step in order to be able to run at all. Registration of
174 steps that have unmet dependencies are deferred until the
175 dependencies have been registered.
177 o 'title' is a one-line UI description for this step.
178 If None, the first line of the documentation string of the handler
179 is used, or the id if no docstring can be found.
181 o 'description' is a one-line UI description for this step.
182 If None, the remaining line of the documentation string of
183 the handler is used, or default to ''.
184 """
185 already = self.getStepMetadata( id )
187 if already and already[ 'version' ] > version:
188 raise KeyError( 'Existing registration for step %s, version %s'
189 % ( id, already[ 'version' ] ) )
191 if title is None or description is None:
193 t, d = _extractDocstring( handler, id, '' )
195 title = title or t
196 description = description or d
198 info = { 'id' : id
199 , 'version' : version
200 , 'handler' : _getDottedName( handler )
201 , 'dependencies' : dependencies
202 , 'title' : title
203 , 'description' : description
204 }
206 self._registered[ id ] = info
208 security.declarePrivate( 'parseXML' )
209 def parseXML( self, text, encoding=None ):
211 """ Parse 'text'.
212 """
213 reader = getattr( text, 'read', None )
215 if reader is not None:
216 text = reader()
218 parser = _ImportStepRegistryParser( encoding )
219 parseString( text, parser )
221 return parser._parsed
223 security.declarePrivate( 'clear' )
224 def clear( self ):
226 self._registered = {}
228 #
229 # Helper methods
230 #
231 security.declarePrivate( '_computeTopologicalSort' )
232 def _computeTopologicalSort( self ):
234 result = []
236 graph = [ ( x[ 'id' ], x[ 'dependencies' ] )
237 for x in self._registered.values() ]
239 for node, edges in graph:
241 after = -1
243 for edge in edges:
245 if edge in result:
246 after = max( after, result.index( edge ) )
248 result.insert( after + 1, node )
250 return result
252 security.declarePrivate( '_exportTemplate' )
253 _exportTemplate = PageTemplateFile( 'isrExport.xml', _xmldir )
255 InitializeClass( ImportStepRegistry )
258 class ExportStepRegistry( Implicit ):
260 """ Registry of known site-configuration export steps.
262 o Each step is registered with a unique id.
264 o When called, with the portal object passed in as an argument,
265 the step must return a sequence of three-tuples,
266 ( 'data', 'content_type', 'filename' ), one for each file exported
267 by the step.
269 - 'data' is a string containing the file data;
271 - 'content_type' is the MIME type of the data;
273 - 'filename' is a suggested filename for use when downloading.
275 """
276 __implements__ = ( IExportStepRegistry, )
278 security = ClassSecurityInfo()
280 def __init__( self ):
282 self.clear()
284 security.declareProtected( ManagePortal, 'listSteps' )
285 def listSteps( self ):
287 """ Return a list of registered step IDs.
288 """
289 return self._registered.keys()
291 security.declareProtected( ManagePortal, 'getStepMetadata' )
292 def getStepMetadata( self, key, default=None ):
294 """ Return a mapping of metadata for the step identified by 'key'.
296 o Return 'default' if no such step is registered.
298 o The 'handler' metadata is available via 'getStep'.
299 """
300 info = self._registered.get( key )
302 if info is None:
303 return default
305 return info.copy()
307 security.declareProtected( ManagePortal, 'listStepMetadata' )
308 def listStepMetadata( self ):
310 """ Return a sequence of mappings describing registered steps.
312 o Steps will be alphabetical by ID.
313 """
314 step_ids = self.listSteps()
315 step_ids.sort()
316 return [ self.getStepMetadata( x ) for x in step_ids ]
318 security.declareProtected( ManagePortal, 'generateXML' )
319 def generateXML( self ):
321 """ Return a round-trippable XML representation of the registry.
323 o 'handler' values are serialized using their dotted names.
324 """
325 return self._exportTemplate()
327 security.declarePrivate( 'getStep' )
328 def getStep( self, key, default=None ):
330 """ Return the IExportPlugin registered for 'key'.
332 o Return 'default' if no such step is registered.
333 """
334 marker = object()
335 info = self._registered.get( key, marker )
337 if info is marker:
338 return default
340 return _resolveDottedName( info[ 'handler' ] )
342 security.declarePrivate( 'registerStep' )
343 def registerStep( self, id, handler, title=None, description=None ):
345 """ Register an export step.
347 o 'id' is the unique identifier for this step
349 o 'step' should implement IExportPlugin.
351 o 'title' is a one-line UI description for this step.
352 If None, the first line of the documentation string of the step
353 is used, or the id if no docstring can be found.
355 o 'description' is a one-line UI description for this step.
356 If None, the remaining line of the documentation string of
357 the step is used, or default to ''.
358 """
359 if title is None or description is None:
361 t, d = _extractDocstring( handler, id, '' )
363 title = title or t
364 description = description or d
366 info = { 'id' : id
367 , 'handler' : _getDottedName( handler )
368 , 'title' : title
369 , 'description' : description
370 }
372 self._registered[ id ] = info
374 security.declarePrivate( 'parseXML' )
375 def parseXML( self, text, encoding=None ):
377 """ Parse 'text'.
378 """
379 reader = getattr( text, 'read', None )
381 if reader is not None:
382 text = reader()
384 parser = _ExportStepRegistryParser( encoding )
385 parseString( text, parser )
387 return parser._parsed
389 security.declarePrivate( 'clear' )
390 def clear( self ):
392 self._registered = {}
394 #
395 # Helper methods
396 #
397 security.declarePrivate( '_exportTemplate' )
398 _exportTemplate = PageTemplateFile( 'esrExport.xml', _xmldir )
400 InitializeClass( ExportStepRegistry )
402 class ToolsetRegistry( Implicit ):
404 """ Track required / forbidden tools.
405 """
406 __implements__ = ( IToolsetRegistry, )
408 security = ClassSecurityInfo()
409 security.setDefaultAccess( 'allow' )
411 def __init__( self ):
413 self.clear()
415 #
416 # Toolset API
417 #
418 security.declareProtected( ManagePortal, 'listForbiddenTools' )
419 def listForbiddenTools( self ):
421 """ See IToolsetRegistry.
422 """
423 result = list( self._forbidden )
424 result.sort()
425 return result
427 security.declareProtected( ManagePortal, 'addForbiddenTool' )
428 def addForbiddenTool( self, tool_id ):
430 """ See IToolsetRegistry.
431 """
432 if tool_id in self._forbidden:
433 return
435 if self._required.get( tool_id ) is not None:
436 raise ValueError, 'Tool %s is required!' % tool_id
438 self._forbidden.append( tool_id )
440 security.declareProtected( ManagePortal, 'listRequiredTools' )
441 def listRequiredTools( self ):
443 """ See IToolsetRegistry.
444 """
445 result = list( self._required.keys() )
446 result.sort()
447 return result
449 security.declareProtected( ManagePortal, 'getRequiredToolInfo' )
450 def getRequiredToolInfo( self, tool_id ):
452 """ See IToolsetRegistry.
453 """
454 return self._required[ tool_id ]
456 security.declareProtected( ManagePortal, 'listRequiredToolInfo' )
457 def listRequiredToolInfo( self ):
459 """ See IToolsetRegistry.
460 """
461 return [ self.getRequiredToolInfo( x )
462 for x in self.listRequiredTools() ]
464 security.declareProtected( ManagePortal, 'addRequiredTool' )
465 def addRequiredTool( self, tool_id, dotted_name ):
467 """ See IToolsetRegistry.
468 """
469 if tool_id in self._forbidden:
470 raise ValueError, "Forbidden tool ID: %s" % tool_id
472 self._required[ tool_id ] = { 'id' : tool_id
473 , 'class' : dotted_name
474 }
476 security.declareProtected( ManagePortal, 'generateXML' )
477 def generateXML( self ):
479 """ Pseudo API.
480 """
481 return self._toolsetConfig()
483 security.declareProtected( ManagePortal, 'parseXML' )
484 def parseXML( self, text, encoding=None ):
486 """ Pseudo-API
487 """
488 reader = getattr( text, 'read', None )
490 if reader is not None:
491 text = reader()
493 parser = _ToolsetParser( encoding )
494 parseString( text, parser )
496 for tool_id in parser._forbidden:
497 self.addForbiddenTool( tool_id )
499 for tool_id, dotted_name in parser._required.items():
500 self.addRequiredTool( tool_id, dotted_name )
502 security.declarePrivate( 'clear' )
503 def clear( self ):
505 self._forbidden = []
506 self._required = {}
508 #
509 # Helper methods.
510 #
511 security.declarePrivate( '_toolsetConfig' )
512 _toolsetConfig = PageTemplateFile( 'tscExport.xml'
513 , _xmldir
514 , __name__='toolsetConfig'
515 )
517 InitializeClass( ToolsetRegistry )
519 class ProfileRegistry( Implicit ):
521 """ Track registered profiles.
522 """
523 __implements__ = ( IProfileRegistry, )
525 security = ClassSecurityInfo()
526 security.setDefaultAccess( 'allow' )
528 def __init__( self ):
530 self.clear()
532 security.declareProtected( ManagePortal, '' )
533 def getProfileInfo( self, profile_id ):
535 """ See IProfileRegistry.
536 """
537 result = self._profile_info[ profile_id ]
538 return result.copy()
540 security.declareProtected( ManagePortal, 'listProfiles' )
541 def listProfiles( self ):
543 """ See IProfileRegistry.
544 """
545 return tuple( self._profile_ids )
547 security.declareProtected( ManagePortal, 'listProfileInfo' )
548 def listProfileInfo( self ):
550 """ See IProfileRegistry.
551 """
552 return [ self.getProfileInfo( id ) for id in self.listProfiles() ]
554 security.declareProtected( ManagePortal, 'registerProfile' )
555 def registerProfile( self
556 , name
557 , title
558 , description
559 , path
560 , product=None
561 , profile_type=BASE
562 ):
563 """ See IProfileRegistry.
564 """
565 profile_id = '%s:%s' % (product or 'other', name)
566 if self._profile_info.get( profile_id ) is not None:
567 raise KeyError, 'Duplicate profile ID: %s' % profile_id
569 self._profile_ids.append( profile_id )
571 info = { 'id' : profile_id
572 , 'title' : title
573 , 'description' : description
574 , 'path' : path
575 , 'product' : product
576 , 'type': profile_type
577 }
579 self._profile_info[ profile_id ] = info
581 security.declarePrivate( 'clear' )
582 def clear( self ):
584 self._profile_info = {}
585 self._profile_ids = []
587 InitializeClass( ProfileRegistry )
589 _profile_registry = ProfileRegistry()
591 class _ImportStepRegistryParser( HandlerBase ):
593 security = ClassSecurityInfo()
594 security.declareObjectPrivate()
595 security.setDefaultAccess( 'deny' )
597 def __init__( self, encoding ):
599 self._encoding = encoding
600 self._started = False
601 self._pending = None
602 self._parsed = []
604 def startElement( self, name, attrs ):
606 if name == 'import-steps':
608 if self._started:
609 raise ValueError, 'Duplicated setup-steps element: %s' % name
611 self._started = True
613 elif name == 'import-step':
615 if self._pending is not None:
616 raise ValueError, 'Cannot nest setup-step elements'
618 self._pending = dict( [ ( k, self._extract( attrs, k ) )
619 for k in attrs.keys() ] )
621 self._pending[ 'dependencies' ] = []
623 elif name == 'dependency':
625 if not self._pending:
626 raise ValueError, 'Dependency outside of step'
628 depended = self._extract( attrs, 'step' )
629 self._pending[ 'dependencies' ].append( depended )
631 else:
632 raise ValueError, 'Unknown element %s' % name
634 def characters( self, content ):
636 if self._pending is not None:
637 content = self._encode( content )
638 self._pending.setdefault( 'description', [] ).append( content )
640 def endElement(self, name):
642 if name == 'import-steps':
643 pass
645 elif name == 'import-step':
647 if self._pending is None:
648 raise ValueError, 'No pending step!'
650 deps = tuple( self._pending[ 'dependencies' ] )
651 self._pending[ 'dependencies' ] = deps
653 desc = ''.join( self._pending[ 'description' ] )
654 self._pending[ 'description' ] = desc
656 self._parsed.append( self._pending )
657 self._pending = None
659 InitializeClass( _ImportStepRegistryParser )
661 class _ExportStepRegistryParser( HandlerBase ):
663 security = ClassSecurityInfo()
664 security.declareObjectPrivate()
665 security.setDefaultAccess( 'deny' )
667 def __init__( self, encoding ):
669 self._encoding = encoding
670 self._started = False
671 self._pending = None
672 self._parsed = []
674 def startElement( self, name, attrs ):
676 if name == 'export-steps':
678 if self._started:
679 raise ValueError, 'Duplicated export-steps element: %s' % name
681 self._started = True
683 elif name == 'export-step':
685 if self._pending is not None:
686 raise ValueError, 'Cannot nest export-step elements'
688 self._pending = dict( [ ( k, self._extract( attrs, k ) )
689 for k in attrs.keys() ] )
691 else:
692 raise ValueError, 'Unknown element %s' % name
694 def characters( self, content ):
696 if self._pending is not None:
697 content = self._encode( content )
698 self._pending.setdefault( 'description', [] ).append( content )
700 def endElement(self, name):
702 if name == 'export-steps':
703 pass
705 elif name == 'export-step':
707 if self._pending is None:
708 raise ValueError, 'No pending step!'
710 desc = ''.join( self._pending[ 'description' ] )
711 self._pending[ 'description' ] = desc
713 self._parsed.append( self._pending )
714 self._pending = None
716 InitializeClass( _ExportStepRegistryParser )
719 class _ToolsetParser( HandlerBase ):
721 security = ClassSecurityInfo()
722 security.declareObjectPrivate()
723 security.setDefaultAccess( 'deny' )
725 def __init__( self, encoding ):
727 self._encoding = encoding
728 self._required = {}
729 self._forbidden = []
731 def startElement( self, name, attrs ):
733 if name == 'tool-setup':
734 pass
736 elif name == 'forbidden':
738 tool_id = self._extract( attrs, 'tool_id' )
740 if tool_id not in self._forbidden:
741 self._forbidden.append( tool_id )
743 elif name == 'required':
745 tool_id = self._extract( attrs, 'tool_id' )
746 dotted_name = self._extract( attrs, 'class' )
747 self._required[ tool_id ] = dotted_name
749 else:
750 raise ValueError, 'Unknown element %s' % name
753 InitializeClass( _ToolsetParser )