source: main/waeup.sirp/trunk/src/waeup/sirp/utils/helpers.py @ 6446

Last change on this file since 6446 was 6372, checked in by uli, 14 years ago

Add method to get principal without request (use only in case of
emergency!).

File size: 12.0 KB
Line 
1"""General helper functions for WAeUP.
2"""
3import os
4import re
5import sys
6import shutil
7import grok
8from cStringIO import StringIO
9from docutils.core import publish_string
10from zope.component.interfaces import IFactory
11from zope.interface import implementedBy
12from zope.schema import getFieldNames
13from zope.schema.fieldproperty import FieldProperty
14from zope.security.interfaces import NoInteraction
15from zope.security.management import getInteraction
16
17
18def removeFileOrDirectory(filepath):
19    """Remove a file or directory.
20
21    Different to :func:`shutil.rmtree` we also accept not existing
22    paths (returning silently) and if a dir turns out to be a regular
23    file, we remove that.
24    """
25    filepath = os.path.abspath(filepath)
26    if not os.path.exists(filepath):
27        return
28    if os.path.isdir(filepath):
29        shutil.rmtree(filepath)
30    else:
31        os.unlink(filepath)
32    return
33
34def copyFileSystemTree(src, dst, overwrite=False, del_old=False):
35    """Copy contents of directory src to directory dst.
36
37    Both directories must exists.
38
39    If `overwrite` is true, any same named objects will be
40    overwritten. Otherwise these files will not be touched.
41
42    If `del_old` is true, copied files and directories will be removed
43    from the src directory.
44
45    This functions returns a list of non-copied files.
46
47    Unix hidden files and directories (starting with '.') are not
48    processed by this function.
49    """
50    if not os.path.exists(src):
51        raise ValueError('source path does not exist: %s' % src)
52    if not os.path.exists(dst):
53        raise ValueError('destination path does not exist: %s' % dst)
54    if not os.path.isdir(src):
55        raise ValueError('source path is not a directory: %s' % src)
56    if not os.path.isdir(dst):
57        raise ValueError('destination path is not a directory: %s' % dst)
58    not_copied = []
59    for item in os.listdir(src):
60        if item.startswith('.'):
61            continue # We do not copy hidden stuff...
62        itemsrc = os.path.join(src, item)
63        itemdst = os.path.join(dst, item)
64
65        if os.path.exists(itemdst):
66            if overwrite is True:
67                removeFileOrDirectory(itemdst)
68            else:
69                not_copied.append(item)
70                continue
71
72        if os.path.isdir(itemsrc):
73            shutil.copytree(itemsrc, itemdst)
74        else:
75            shutil.copy2(itemsrc, itemdst)
76        if del_old:
77            removeFileOrDirectory(itemsrc)
78    return not_copied
79
80
81def getInnerHTMLPart(html_code):
82    """Return the 'inner' part of a complete HTML snippet.
83
84    If there is a form part, get this.
85
86    If there is no form part, try to return the body part contents.
87
88    If there is no body, return as-is.
89
90    Let's see how that works. If we deliver some doc with form, we
91    will get that form only:
92
93       >>> doc = '<html><form>My Form</form>Outside the form</html>'
94       >>> getInnerHTMLPart(doc)
95       '<form>My Form</form>'
96
97    No form? Then seek for a body part and get the contents:
98
99       >>> doc = '<html><body>My Body</body>Trailing Trash</html>'
100       >>> getInnerHTMLPart(doc)
101       'My Body'
102
103    If none of these is included, return what we got:
104
105       >>> doc = '<html>without body nor form</html>'
106       >>> getInnerHTMLPart(doc)
107       '<html>without body nor form</html>'
108
109    """
110
111    try:
112        result = re.match('^.+(<form[^\>]*>.*</form>).+$', html_code,
113                          re.DOTALL).groups()[0]
114        return result
115    except AttributeError:
116        # No <form> part included
117        try:
118            result = re.match('^.+<body[^\>]*>(.*)</body>.*$', html_code,
119                              re.DOTALL).groups()[0]
120            return result
121        except AttributeError:
122            # No <form> and no <body> tag...
123            pass
124    return html_code
125
126class FactoryBase(grok.GlobalUtility):
127    """A factory for things.
128
129    This is a baseclass for easier creation of factories. Factories
130    are utilities that are registered under a certain name and return
131    instances of certain classes when called.
132
133    In :mod:`waeup.sirp` we use factories extensively for
134    batching. While processing a batch some importer looks up a
135    factory to create real-world instances that then get filled with
136    data from imported CSV files.
137
138    To get rid of reimplementing the same stuff over and over again,
139    most notably the methods defined here, we offer this base class
140    (which will *not* be registered as a factory itself).
141
142    Real factories can then be created like this:
143
144       >>> import grok
145       >>> from waeup.sirp.utils.helpers import FactoryBase
146       >>> class MyObject(object):
147       ...   # Some class we want to get instances of.
148       ...   pass
149       >>> class MyObjectFactory(FactoryBase):
150       ...   # This is the factory for MyObject instances
151       ...   grok.name(u'waeup.sirp.factory.MyObject')
152       ...   factory = MyObject
153
154    That's it. It is essential to set the ``factory`` attribute, which
155    will determine the class of which instances should be created when
156    called. The given name must even be unique amongst all utilities
157    registered during runtime. While you can pick any name you like
158    you might want to prepend ``waeup.sirp.factory.`` to the name
159    string to make sure it does not clash with names of other
160    utilities one day.
161
162    Before all this works we have to grok the baseclass once and our
163    freshly defined factory. This executes all the component
164    registration stuff we don't want to do ourselves. In daily use
165    this is done automatically on startup of a :mod:`waeup.sirp`
166    system.
167
168       >>> grok.testing.grok('waeup.sirp.utils.helpers')
169       >>> grok.testing.grok_component(
170       ...    'MyObjectFactory', MyObjectFactory
171       ...  )
172       True
173
174    After grokking we (and importers) can create objects without
175    knowing about the location of the real class definition, just by
176    the factory name:
177
178       >>> from zope.component import createObject
179       >>> obj = createObject('waeup.sirp.factory.MyObject')
180       >>> isinstance(obj, MyObject)
181       True
182
183    We can also use the regular utility lookups to find our new
184    factory:
185
186       >>> from zope.component import getUtility
187       >>> from zope.component.interfaces import IFactory
188       >>> factory = getUtility(
189       ...   IFactory, name='waeup.sirp.factory.MyObject'
190       ...   )
191       >>> isinstance(factory, MyObjectFactory)
192       True
193
194    And this factory generates `MyObject` instances:
195
196       >>> obj = factory()
197       >>> isinstance(obj, MyObject)
198       True
199
200    """
201    grok.baseclass() # Do not grok this class, do not register us.
202    grok.implements(IFactory)
203    # You can override any of the following attributes in derived
204    # classes. The `grok.name` setting *must* even be set to some
205    # unique value.
206    grok.name(u'waeup.Factory')
207    title = u"Create instances of ``factory``.",
208    description = u"This factory instantiates new applicant instances."
209    factory = None
210
211    def __call__(self, *args, **kw):
212        """The main factory function.
213
214        Returns an instance of the requested object.
215        """
216        return self.factory()
217
218    def getInterfaces(self):
219        # Required by IFactory
220        return implementedBy(self.factory)
221
222def ReST2HTML_w_warnings(source_string):
223    """Convert a reStructuredText string to HTML preserving warnings.
224
225    Returns a tuple ``(<HTML_CODE>, <WARNINGS>)``, both being
226    strings. Where ``<HTML_CODE>`` is the HTML code generated from the
227    source string (in unicode), ``<WARNINGS>`` is a string containing
228    any warning messages or ``None``.
229
230    Regular multi-line ReStructuredText strings will be returned as
231    HTML code:
232
233        >>> from waeup.sirp.utils.helpers import ReST2HTML
234        >>> source = '''
235        ... Headline
236        ... ========
237        ...
238        ... - A list item
239        ... - Another item
240        ...
241        ... Thanks for watching!
242        ... '''
243        >>> html, warnings = ReST2HTML_w_warnings(source)
244        >>> print html
245        <div class="document" id="headline">
246        <h1 class="title">Headline</h1>
247        <BLANKLINE>
248        <ul class="simple">
249        <li>A list item</li>
250        <li>Another item</li>
251        </ul>
252        <p>Thanks for watching!</p>
253        </div>
254
255    Here no warnings happened, so the `warnings` are ``None``:
256
257        >>> warnings is None
258        True
259
260    If warnings happen then they can be retrieved in the returned
261    ``warnings``. We try to render an erraneous document:
262
263        >>> source = '''
264        ... Headline
265        ... ======
266        ...
267        ... Thanks for watching!
268        ... '''
269        >>> html, warnings = ReST2HTML_w_warnings(source)
270        >>> print html
271        <div class="document" id="headline">
272        <h1 class="title">Headline</h1>
273        <BLANKLINE>
274        <p>Thanks for watching!</p>
275        </div>
276
277        >>> print warnings
278        <string>:3: (WARNING/2) Title underline too short.
279        <BLANKLINE>
280        Headline
281        ======
282        <BLANKLINE>
283
284    As you can see, the warnings are not displayed inline the document
285    but can be retrieved from the returned warnings, which is a string
286    or ``None``.
287    """
288    warnings = StringIO()
289    fulldoc = publish_string(
290        source_string, writer_name='html4css1',
291        settings_overrides={
292            'report_level': 0,
293            'warning_stream': warnings,
294            })
295    warnings.seek(0)
296    warning_msgs = warnings.read()
297    if warning_msgs:
298        # Render again, this time with no warnings inline...
299        fulldoc =  publish_string(
300        source_string, writer_name='html4css1',
301        settings_overrides={
302            'report_level': 10000,
303            'halt_level': 10000,
304            'warning_stream': warnings,
305            })
306    if warning_msgs == '':
307        warning_msgs = None
308    result = getInnerHTMLPart(fulldoc).strip()
309    if not isinstance(result, unicode):
310        result = result.decode('utf-8')
311    return result, warning_msgs
312
313def ReST2HTML(source_string):
314    """Render a string containing ReStructuredText to HTML.
315
316    Any warnings about too short headings, etc. are silently
317    discarded. Use :func:`ReST2HTML_w_warnings` if you want to get any
318    warnings.
319
320    The returned string will be unicode.
321
322    A regular document will be rendered like this:
323
324        >>> source = '''
325        ... Headline
326        ... ========
327        ...
328        ... Thanks for watching!
329        ... '''
330        >>> html = ReST2HTML(source)
331        >>> print html
332        <div class="document" id="headline">
333        <h1 class="title">Headline</h1>
334        <BLANKLINE>
335        <p>Thanks for watching!</p>
336        </div>
337
338    A document with markup problems (here: the underline is too short)
339    will look similar:
340
341        >>> source = '''
342        ... Headline
343        ... ======
344        ...
345        ... Thanks for watching!
346        ... '''
347        >>> html = ReST2HTML(source)
348        >>> print html
349        <div class="document" id="headline">
350        <h1 class="title">Headline</h1>
351        <BLANKLINE>
352        <p>Thanks for watching!</p>
353        </div>
354
355    """
356    html, warnings = ReST2HTML_w_warnings(source_string)
357    return html
358
359def attrs_to_fields(cls):
360    """Turn the attributes of a class into FieldProperty instances.
361
362    With Python >= 2.6 we can even use this function as a class decorator.
363    """
364    iface = list(implementedBy(cls))[0]
365    for field_name in getFieldNames(iface):
366        setattr(cls, field_name, FieldProperty(iface[field_name]))
367    return cls
368
369def get_current_principal():
370    """Get the 'current' principal.
371
372    This method works without a request. Examining a request is the
373    regular (and recommended) way to get a principal involved
374    'currently'.
375
376    Use this method only if you really have no access to the current
377    request.
378
379    Returns ``None`` when no principal is involved (for instance
380    during tests).
381    """
382    try:
383        principal = getInteraction().participations[0].principal
384    except NoInteraction:
385        return None
386    except IndexError: # No participations present
387        return None
388    return principal
Note: See TracBrowser for help on using the repository browser.