summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Th?nault <sylvain.thenault@logilab.fr>2014-07-24 18:21:12 +0200
committerSylvain Th?nault <sylvain.thenault@logilab.fr>2014-07-24 18:21:12 +0200
commit109090dfa67f1a7a35db5002346795bb717bf7d0 (patch)
tree9c0537f58ba447b8f3bd1004f3f17c6ceb338b71
parent640161196f431821182e4cfac5dd746a7ae464e4 (diff)
downloadpylint-109090dfa67f1a7a35db5002346795bb717bf7d0.tar.gz
extract a messages store from the MessagesHandlerMixIn class
-rw-r--r--lint.py25
-rw-r--r--reporters/__init__.py2
-rw-r--r--test/test_func.py6
-rw-r--r--test/test_regr.py4
-rw-r--r--test/unittest_lint.py156
-rw-r--r--utils.py188
6 files changed, 202 insertions, 179 deletions
diff --git a/lint.py b/lint.py
index 0b7b44f..9814e1d 100644
--- a/lint.py
+++ b/lint.py
@@ -50,7 +50,7 @@ from astroid.modutils import load_module_from_name, get_module_part
from pylint.utils import (
MSG_TYPES, OPTION_RGX,
PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn,
- EmptyReport, WarningScope,
+ MessagesStore, EmptyReport, WarningScope,
expand_modules, tokenize_module)
from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker
from pylint.checkers import (BaseTokenChecker,
@@ -286,7 +286,8 @@ warning, statement which respectively contain the number of errors / warnings\
pylintrc=None):
# some stuff has to be done before ancestors initialization...
#
- # checkers / reporter / astroid manager
+ # messages store / checkers / reporter / astroid manager
+ self.msgs_store = MessagesStore()
self.reporter = None
self._reporter_name = None
self._reporters = {}
@@ -423,11 +424,11 @@ warning, statement which respectively contain the number of errors / warnings\
self.register_report(r_id, r_title, r_cb, checker)
self.register_options_provider(checker)
if hasattr(checker, 'msgs'):
- self.register_messages(checker)
+ self.msgs_store.register_messages(checker)
checker.load_defaults()
def disable_noerror_messages(self):
- for msgcat, msgids in self._msgs_by_category.iteritems():
+ for msgcat, msgids in self.msgs_store._msgs_by_category.iteritems():
if msgcat == 'E':
for msgid in msgids:
self.enable(msgid)
@@ -529,7 +530,7 @@ warning, statement which respectively contain the number of errors / warnings\
if first <= lineno <= last:
# Set state for all lines for this block, if the
# warning is applied to nodes.
- if self.check_message_id(msgid).scope == WarningScope.NODE:
+ if self.msgs_store.check_message_id(msgid).scope == WarningScope.NODE:
if lineno > firstchildlineno:
state = True
first_, last_ = node.block_range(lineno)
@@ -596,6 +597,12 @@ warning, statement which respectively contain the number of errors / warnings\
"""main checking entry: check a list of files or modules from their
name.
"""
+ # initialize msgs_state now that all messages have been registered into
+ # the store
+ for msg in self.msgs_store.messages:
+ if not msg.may_be_emitted():
+ self._msgs_state[msg.msgid] = False
+
if not isinstance(files_or_modules, (list, tuple)):
files_or_modules = (files_or_modules,)
walker = PyLintASTWalker(self)
@@ -758,12 +765,12 @@ warning, statement which respectively contain the number of errors / warnings\
for line, enable in lines.iteritems():
if not enable and (warning, line) not in self._ignored_msgs:
self.add_message('useless-suppression', line, None,
- (self.get_msg_display_string(warning),))
+ (self.msgs_store.get_msg_display_string(warning),))
# don't use iteritems here, _ignored_msgs may be modified by add_message
for (warning, from_), lines in self._ignored_msgs.items():
for line in lines:
self.add_message('suppressed-message', line, None,
- (self.get_msg_display_string(warning), from_))
+ (self.msgs_store.get_msg_display_string(warning), from_))
def report_evaluation(self, sect, stats, previous_stats):
"""make the global evaluation report"""
@@ -1088,7 +1095,7 @@ are done by default'''}),
def cb_help_message(self, option, optname, value, parser):
"""optik callback for printing some help about a particular message"""
- self.linter.help_message(splitstrip(value))
+ self.linter.msgs_store.help_message(splitstrip(value))
sys.exit(0)
def cb_full_documentation(self, option, optname, value, parser):
@@ -1098,7 +1105,7 @@ are done by default'''}),
def cb_list_messages(self, option, optname, value, parser): # FIXME
"""optik callback for printing available messages"""
- self.linter.list_messages()
+ self.linter.msgs_store.list_messages()
sys.exit(0)
def cb_init_hook(optname, value):
diff --git a/reporters/__init__.py b/reporters/__init__.py
index a767a05..12d193f 100644
--- a/reporters/__init__.py
+++ b/reporters/__init__.py
@@ -51,7 +51,7 @@ class Message(object):
self.msg = msg
self.C = msg_id[0]
self.category = MSG_TYPES[msg_id[0]]
- self.symbol = reporter.linter.check_message_id(msg_id).symbol
+ self.symbol = reporter.linter.msgs_store.check_message_id(msg_id).symbol
def format(self, template):
"""Format the message according to the given template.
diff --git a/test/test_func.py b/test/test_func.py
index 6a64ec2..2d573c2 100644
--- a/test/test_func.py
+++ b/test/test_func.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2008 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
@@ -45,11 +45,11 @@ class LintTestNonExistentModuleTC(LintTestUsingModule):
class TestTests(testlib.TestCase):
"""check that all testable messages have been checked"""
PORTED = set(['I0001', 'I0010', 'W0712', 'E1001', 'W1402', 'E1310'])
-
+
@testlib.tag('coverage')
def test_exhaustivity(self):
# skip fatal messages
- not_tested = set(msg.msgid for msg in linter.messages
+ not_tested = set(msg.msgid for msg in linter.msgs_store.messages
if msg.msgid[0] != 'F' and msg.may_be_emitted())
for msgid in test_reporter.message_ids:
try:
diff --git a/test/test_regr.py b/test/test_regr.py
index 0349481..8bc50c2 100644
--- a/test/test_regr.py
+++ b/test/test_regr.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2005 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2005-2014 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
@@ -60,7 +60,7 @@ class NonRegrTC(TestCase):
got = linter.reporter.finalize().strip()
checked = linter.stats['by_module'].keys()
self.assertEqual(checked, ['package.__init__'],
- '%s: %s' % (variation, checked))
+ '%s: %s' % (variation, checked))
cwd = os.getcwd()
os.chdir(join(REGR_DATA, 'package'))
sys.path.insert(0, '')
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
index a856d73..0e0ed9c 100644
--- a/test/unittest_lint.py
+++ b/test/unittest_lint.py
@@ -27,7 +27,8 @@ from pylint import config
from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \
ArgumentPreprocessingError
from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, \
- PyLintASTWalker, MessageDefinition, build_message_def, tokenize_module
+ MessagesStore, PyLintASTWalker, MessageDefinition, build_message_def, \
+ tokenize_module
from pylint.testutils import TestReporter
from pylint.reporters import text
from pylint import checkers
@@ -60,51 +61,6 @@ class PyLinterTC(TestCase):
checkers.initialize(self.linter)
self.linter.set_reporter(TestReporter())
- def _compare_messages(self, desc, msg, checkerref=False):
- # replace \r\n with \n, because
- # logilab.common.textutils.normalize_text
- # uses os.linesep, which will
- # not properly compare with triple
- # quoted multilines used in these tests
- self.assertMultiLineEqual(desc,
- msg.format_help(checkerref=checkerref)
- .replace('\r\n', '\n'))
-
- def test_check_message_id(self):
- self.assertIsInstance(self.linter.check_message_id('F0001'),
- MessageDefinition)
- self.assertRaises(UnknownMessage,
- self.linter.check_message_id, 'YB12')
-
- def test_message_help(self):
- msg = self.linter.check_message_id('F0001')
- self._compare_messages(
- ''':fatal (F0001):
- Used when an error occurred preventing the analysis of a module (unable to
- find it for instance). This message belongs to the master checker.''',
- msg, checkerref=True)
- self._compare_messages(
- ''':fatal (F0001):
- Used when an error occurred preventing the analysis of a module (unable to
- find it for instance).''',
- msg, checkerref=False)
-
- def test_message_help_minmax(self):
- # build the message manually to be python version independant
- msg = build_message_def(self.linter._checkers['typecheck'][0],
- 'E1122', checkers.typecheck.MSGS['E1122'])
- self._compare_messages(
- ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in %s call*
- Used when a function call passes the same keyword argument multiple times.
- This message belongs to the typecheck checker. It can't be emitted when using
- Python >= 2.6.''',
- msg, checkerref=True)
- self._compare_messages(
- ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in %s call*
- Used when a function call passes the same keyword argument multiple times.
- This message can't be emitted when using Python >= 2.6.''',
- msg, checkerref=False)
-
def test_enable_message(self):
linter = self.linter
linter.open()
@@ -246,16 +202,6 @@ class PyLinterTC(TestCase):
self.assertTrue(linter.is_message_enabled('W0102', 1))
self.assertTrue(linter.is_message_enabled('dangerous-default-value', 1))
- def test_list_messages(self):
- sys.stdout = StringIO()
- try:
- self.linter.list_messages()
- output = sys.stdout.getvalue()
- finally:
- sys.stdout = sys.__stdout__
- # cursory examination of the output: we're mostly testing it completes
- self.assertIn(':empty-docstring (C0112): *Empty %s docstring*', output)
-
def test_lint_ext_module_with_file_output(self):
self.linter.set_reporter(text.TextReporter())
if sys.version_info < (3, 0):
@@ -363,23 +309,6 @@ class PyLinterTC(TestCase):
['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'],
self.linter.reporter.messages)
- def test_add_renamed_message(self):
- self.linter.add_renamed_message('C9999', 'old-bad-name', 'invalid-name')
- self.assertEqual('invalid-name',
- self.linter.check_message_id('C9999').symbol)
- self.assertEqual('invalid-name',
- self.linter.check_message_id('old-bad-name').symbol)
-
- def test_renamed_message_register(self):
- class Checker(object):
- msgs = {'W1234': ('message', 'msg-symbol', 'msg-description',
- {'old_names': [('W0001', 'old-symbol')]})}
- self.linter.register_messages(Checker())
- self.assertEqual('msg-symbol',
- self.linter.check_message_id('W0001').symbol)
- self.assertEqual('msg-symbol',
- self.linter.check_message_id('old-symbol').symbol)
-
def test_init_hooks_called_before_load_plugins(self):
self.assertRaises(RuntimeError,
Run, ['--load-plugins', 'unexistant', '--init-hook', 'raise RuntimeError'])
@@ -541,5 +470,86 @@ class PreprocessOptionsTC(TestCase):
{'bar' : (None, False)})
+class MessagesStoreTC(TestCase):
+ def setUp(self):
+ self.store = MessagesStore()
+ class Checker(object):
+ name = 'achecker'
+ msgs = {
+ 'W1234': ('message', 'msg-symbol', 'msg description.',
+ {'old_names': [('W0001', 'old-symbol')]}),
+ 'E1234': ('Duplicate keyword argument %r in %s call',
+ 'duplicate-keyword-arg',
+ 'Used when a function call passes the same keyword argument multiple times.',
+ {'maxversion': (2, 6)}),
+ }
+ self.store.register_messages(Checker())
+
+ def _compare_messages(self, desc, msg, checkerref=False):
+ # replace \r\n with \n, because
+ # logilab.common.textutils.normalize_text
+ # uses os.linesep, which will
+ # not properly compare with triple
+ # quoted multilines used in these tests
+ self.assertMultiLineEqual(
+ desc,
+ msg.format_help(checkerref=checkerref).replace('\r\n', '\n'))
+
+ def test_check_message_id(self):
+ self.assertIsInstance(self.store.check_message_id('W1234'),
+ MessageDefinition)
+ self.assertRaises(UnknownMessage,
+ self.store.check_message_id, 'YB12')
+
+ def test_message_help(self):
+ msg = self.store.check_message_id('W1234')
+ self._compare_messages(
+ ''':msg-symbol (W1234): *message*
+ msg description. This message belongs to the achecker checker.''',
+ msg, checkerref=True)
+ self._compare_messages(
+ ''':msg-symbol (W1234): *message*
+ msg description.''',
+ msg, checkerref=False)
+
+ def test_message_help_minmax(self):
+ # build the message manually to be python version independant
+ msg = self.store.check_message_id('E1234')
+ self._compare_messages(
+ ''':duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call*
+ Used when a function call passes the same keyword argument multiple times.
+ This message belongs to the achecker checker. It can't be emitted when using
+ Python >= 2.6.''',
+ msg, checkerref=True)
+ self._compare_messages(
+ ''':duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call*
+ Used when a function call passes the same keyword argument multiple times.
+ This message can't be emitted when using Python >= 2.6.''',
+ msg, checkerref=False)
+
+ def test_list_messages(self):
+ sys.stdout = StringIO()
+ try:
+ self.store.list_messages()
+ output = sys.stdout.getvalue()
+ finally:
+ sys.stdout = sys.__stdout__
+ # cursory examination of the output: we're mostly testing it completes
+ self.assertIn(':msg-symbol (W1234): *message*', output)
+
+ def test_add_renamed_message(self):
+ self.store.add_renamed_message('W1234', 'old-bad-name', 'msg-symbol')
+ self.assertEqual('msg-symbol',
+ self.store.check_message_id('W1234').symbol)
+ self.assertEqual('msg-symbol',
+ self.store.check_message_id('old-bad-name').symbol)
+
+ def test_renamed_message_register(self):
+ self.assertEqual('msg-symbol',
+ self.store.check_message_id('W0001').symbol)
+ self.assertEqual('msg-symbol',
+ self.store.check_message_id('old-symbol').symbol)
+
+
if __name__ == '__main__':
unittest_main()
diff --git a/utils.py b/utils.py
index 6fbb480..9f5ffbd 100644
--- a/utils.py
+++ b/utils.py
@@ -190,62 +190,13 @@ class MessagesHandlerMixIn(object):
"""
def __init__(self):
- # Primary registry for all active messages (i.e. all messages
- # that can be emitted by pylint for the underlying Python
- # version). It contains the 1:1 mapping from symbolic names
- # to message definition objects.
- self._messages = {}
- # Maps alternative names (numeric IDs, deprecated names) to
- # message definitions. May contain several names for each definition
- # object.
- self._alternative_names = {}
self._msgs_state = {}
self._module_msgs_state = {} # None
self._raw_module_msgs_state = {}
- self._msgs_by_category = {}
self.msg_status = 0
self._ignored_msgs = {}
self._suppression_mapping = {}
- def add_renamed_message(self, old_id, old_symbol, new_symbol):
- """Register the old ID and symbol for a warning that was renamed.
-
- This allows users to keep using the old ID/symbol in suppressions.
- """
- msg = self.check_message_id(new_symbol)
- msg.old_names.append((old_id, old_symbol))
- self._alternative_names[old_id] = msg
- self._alternative_names[old_symbol] = msg
-
- def register_messages(self, checker):
- """register a dictionary of messages
-
- Keys are message ids, values are a 2-uple with the message type and the
- message itself
-
- message ids should be a string of len 4, where the two first characters
- are the checker id and the two last the message id in this checker
- """
- chkid = None
- for msgid, msg_tuple in checker.msgs.iteritems():
- msg = build_message_def(checker, msgid, msg_tuple)
- assert msg.symbol not in self._messages, \
- 'Message symbol %r is already defined' % msg.symbol
- # avoid duplicate / malformed ids
- assert msg.msgid not in self._alternative_names, \
- 'Message id %r is already defined' % msgid
- assert chkid is None or chkid == msg.msgid[1:3], \
- 'Inconsistent checker part in message id %r' % msgid
- chkid = msg.msgid[1:3]
- if not msg.may_be_emitted():
- self._msgs_state[msg.msgid] = False
- self._messages[msg.symbol] = msg
- self._alternative_names[msg.msgid] = msg
- for old_id, old_symbol in msg.old_names:
- self._alternative_names[old_id] = msg
- self._alternative_names[old_symbol] = msg
- self._msgs_by_category.setdefault(msg.msgid[0], []).append(msg.msgid)
-
def disable(self, msgid, scope='package', line=None, ignore_unknown=False):
"""don't output message of the given id"""
assert scope in ('package', 'module')
@@ -257,14 +208,15 @@ class MessagesHandlerMixIn(object):
# msgid is a category?
catid = category_id(msgid)
if catid is not None:
- for _msgid in self._msgs_by_category.get(catid):
+ for _msgid in self.msgs_store._msgs_by_category.get(catid):
self.disable(_msgid, scope, line)
return
# msgid is a checker name?
if msgid.lower() in self._checkers:
+ msgs_store = self.msgs_store
for checker in self._checkers[msgid.lower()]:
for _msgid in checker.msgs:
- if _msgid in self._alternative_names:
+ if _msgid in msgs_store._alternative_names:
self.disable(_msgid, scope, line)
return
# msgid is report id?
@@ -274,7 +226,7 @@ class MessagesHandlerMixIn(object):
try:
# msgid is a symbolic or numeric msgid.
- msg = self.check_message_id(msgid)
+ msg = self.msgs_store.check_message_id(msgid)
except UnknownMessage:
if ignore_unknown:
return
@@ -303,7 +255,7 @@ class MessagesHandlerMixIn(object):
catid = category_id(msgid)
# msgid is a category?
if catid is not None:
- for msgid in self._msgs_by_category.get(catid):
+ for msgid in self.msgs_store._msgs_by_category.get(catid):
self.enable(msgid, scope, line)
return
# msgid is a checker name?
@@ -319,7 +271,7 @@ class MessagesHandlerMixIn(object):
try:
# msgid is a symbolic or numeric msgid.
- msg = self.check_message_id(msgid)
+ msg = self.msgs_store.check_message_id(msgid)
except UnknownMessage:
if ignore_unknown:
return
@@ -337,30 +289,6 @@ class MessagesHandlerMixIn(object):
msgs[msg.msgid] = True
# sync configuration object
self.config.enable = [mid for mid, val in msgs.iteritems() if val]
-
- def check_message_id(self, msgid):
- """returns the Message object for this message.
-
- msgid may be either a numeric or symbolic id.
-
- Raises UnknownMessage if the message id is not defined.
- """
- if msgid[1:].isdigit():
- msgid = msgid.upper()
- for source in (self._alternative_names, self._messages):
- try:
- return source[msgid]
- except KeyError:
- pass
- raise UnknownMessage('No such message id %s' % msgid)
-
- def get_msg_display_string(self, msgid):
- """Generates a user-consumable representation of a message.
-
- Can be just the message ID or the ID and the symbol.
- """
- return repr(self.check_message_id(msgid).symbol)
-
def get_message_state_scope(self, msgid, line=None):
"""Returns the scope at which a message was enabled/disabled."""
try:
@@ -376,7 +304,7 @@ class MessagesHandlerMixIn(object):
msgid may be either a numeric or symbolic message id.
"""
try:
- msgid = self.check_message_id(msg_descr).msgid
+ msgid = self.msgs_store.check_message_id(msg_descr).msgid
except UnknownMessage:
# The linter checks for messages that are not registered
# due to version mismatch, just treat them as message IDs
@@ -412,7 +340,7 @@ class MessagesHandlerMixIn(object):
provide line if the line number is different), raw and token checkers
must provide the line argument.
"""
- msg_info = self.check_message_id(msg_descr)
+ msg_info = self.msgs_store.check_message_id(msg_descr)
msgid = msg_info.msgid
# backward compatibility, message may not have a symbol
symbol = msg_info.symbol or msgid
@@ -460,17 +388,6 @@ class MessagesHandlerMixIn(object):
# add the message
self.reporter.add_message(msgid, (path, module, obj, line or 1, col_offset or 0), msg)
- def help_message(self, msgids):
- """display help messages for the given message identifiers"""
- for msgid in msgids:
- try:
- print self.check_message_id(msgid).format_help(checkerref=True)
- print
- except UnknownMessage, ex:
- print ex
- print
- continue
-
def print_full_documentation(self):
"""output a full documentation in ReST format"""
by_checker = {}
@@ -528,11 +445,100 @@ class MessagesHandlerMixIn(object):
print
print
+
+class MessagesStore(object):
+ """The messages store knows information about every possible message but has
+ no particular state during analysis.
+ """
+
+ def __init__(self):
+ # Primary registry for all active messages (i.e. all messages
+ # that can be emitted by pylint for the underlying Python
+ # version). It contains the 1:1 mapping from symbolic names
+ # to message definition objects.
+ self._messages = {}
+ # Maps alternative names (numeric IDs, deprecated names) to
+ # message definitions. May contain several names for each definition
+ # object.
+ self._alternative_names = {}
+ self._msgs_by_category = {}
+
@property
def messages(self):
"""The list of all active messages."""
return self._messages.itervalues()
+ def add_renamed_message(self, old_id, old_symbol, new_symbol):
+ """Register the old ID and symbol for a warning that was renamed.
+
+ This allows users to keep using the old ID/symbol in suppressions.
+ """
+ msg = self.check_message_id(new_symbol)
+ msg.old_names.append((old_id, old_symbol))
+ self._alternative_names[old_id] = msg
+ self._alternative_names[old_symbol] = msg
+
+ def register_messages(self, checker):
+ """register a dictionary of messages
+
+ Keys are message ids, values are a 2-uple with the message type and the
+ message itself
+
+ message ids should be a string of len 4, where the two first characters
+ are the checker id and the two last the message id in this checker
+ """
+ chkid = None
+ for msgid, msg_tuple in checker.msgs.iteritems():
+ msg = build_message_def(checker, msgid, msg_tuple)
+ assert msg.symbol not in self._messages, \
+ 'Message symbol %r is already defined' % msg.symbol
+ # avoid duplicate / malformed ids
+ assert msg.msgid not in self._alternative_names, \
+ 'Message id %r is already defined' % msgid
+ assert chkid is None or chkid == msg.msgid[1:3], \
+ 'Inconsistent checker part in message id %r' % msgid
+ chkid = msg.msgid[1:3]
+ self._messages[msg.symbol] = msg
+ self._alternative_names[msg.msgid] = msg
+ for old_id, old_symbol in msg.old_names:
+ self._alternative_names[old_id] = msg
+ self._alternative_names[old_symbol] = msg
+ self._msgs_by_category.setdefault(msg.msgid[0], []).append(msg.msgid)
+
+ def check_message_id(self, msgid):
+ """returns the Message object for this message.
+
+ msgid may be either a numeric or symbolic id.
+
+ Raises UnknownMessage if the message id is not defined.
+ """
+ if msgid[1:].isdigit():
+ msgid = msgid.upper()
+ for source in (self._alternative_names, self._messages):
+ try:
+ return source[msgid]
+ except KeyError:
+ pass
+ raise UnknownMessage('No such message id %s' % msgid)
+
+ def get_msg_display_string(self, msgid):
+ """Generates a user-consumable representation of a message.
+
+ Can be just the message ID or the ID and the symbol.
+ """
+ return repr(self.check_message_id(msgid).symbol)
+
+ def help_message(self, msgids):
+ """display help messages for the given message identifiers"""
+ for msgid in msgids:
+ try:
+ print self.check_message_id(msgid).format_help(checkerref=True)
+ print
+ except UnknownMessage, ex:
+ print ex
+ print
+ continue
+
def list_messages(self):
"""output full messages list documentation in ReST format"""
msgs = sorted(self._messages.itervalues(), key=lambda msg: msg.msgid)