1# Copyright 2004-2008 Roman Yakovenko.
2# Distributed under the Boost Software License, Version 1.0. (See
3# accompanying file LICENSE_1_0.txt or copy at
4# http://www.boost.org/LICENSE_1_0.txt)
5
6# Initial version by Matthias Baas (baas@ira.uka.de).
7
8"""defines a class that helps to format user messages"""
9
10import os, logging, textwrap
11
12class multi_line_formatter_t(logging.Formatter):
13    """Custom log formatter to split long message into several lines.
14
15    This formatter is used for the default stream handler that outputs
16    its messages to `stdout`.
17    """
18
19    def __init__(self, fmt=None, datefmt=None, width=None):
20        """Constructor.
21
22        See the Python standard library reference for a documentation
23        of fmt and datefmt.
24        width is the maximum width of the generated text blocks.
25        """
26        logging.Formatter.__init__(self, fmt, datefmt)
27        if None == width:
28            try:
29                import curses
30                curses.setupterm()
31                self._width = curses.tigetnum('cols')
32                # Leave a border of two blanks to prevent that full lines
33                # wrap to the next line
34                self._width -= 2
35            except:
36                self._width = 70   # default to 70
37
38    def format(self, record):
39        """This method overwrites the original one.
40
41        The first thing, that is done in the original format() method, is the
42        creation of the record.message attribute:
43
44          record.message = record.getMessage()
45
46        Now this method temporarily replaces the getMessage() method of
47        the record by a version that returns a generated message that
48        spans several lines. Then the original format() method is called
49        which will invoke the 'fake' method.
50        """
51        # Get the original single-line message
52        message = record.getMessage()
53        # Distribute the message among several lines...
54        message = self.formatMessage(message, width=self._width)
55        # ...and temporarily replace the getMessage() method so that the
56        # reformatted message is used
57        mth = record.getMessage
58        record.getMessage = lambda x=message: x
59        # Invoke the inherited format() method
60        res = logging.Formatter.format(self, record)
61        # Restore the original method
62        record.getMessage = mth
63        return res
64
65    @staticmethod
66    def formatMessage(msgline, width=70):
67        """Format a long single line message so that it is easier to read.
68
69        `msgline` is a string containing a single message. It can either be
70        a plain message string which is reformatted using the :mod:`textwrap`
71        module or it can be of the form <declaration>;<message> where <declaration>
72        is the declaration string and <message> an arbitrary message. Lines of this
73        form will be separated so that the declaration and the message appear in
74        individual text blocks, where every line of message will start
75        with '>' character.
76
77        width is the maximum width of any text blocks (without indentation).
78        """
79        if isinstance(msgline, logging.LogRecord):
80            msgline = msgline.getMessage()
81        txts = msgline.split(";")
82        # Ensure that there are no more than two items in txts
83        if len( txts ) != 2:
84            #If message is not in format we expected, just return it
85            return os.linesep.join( textwrap.wrap( msgline, width ) )
86
87        lines = [ txts[0] ] #I don't want to break declaration string to few lines
88
89        # Insert a separator if there are two parts (=decl and msg)
90        # Apply the text wrapper to shorten the maximum line length
91        wrapped_lines = textwrap.wrap( txts[1], width )
92        lines.extend( ["> " + s.strip() for s in wrapped_lines] )
93
94        return os.linesep.join(lines)
95
96