diff options
author | Julien Jehannet <julien.jehannet@logilab.fr> | 2009-09-15 15:03:37 +0200 |
---|---|---|
committer | Julien Jehannet <julien.jehannet@logilab.fr> | 2009-09-15 15:03:37 +0200 |
commit | 7036bbe00c6c699eac86a65467c583ee901d754d (patch) | |
tree | 8da3fb9383205301b4d485604f0778c632020a4a | |
parent | 028ddb178ec0276bd03b4b9694c3faf1d715a9dd (diff) | |
parent | 9fa76459138f861baff69ad0a58ff078a4cca2da (diff) | |
download | logilab-common-7036bbe00c6c699eac86a65467c583ee901d754d.tar.gz |
(merge)
-rw-r--r-- | ChangeLog | 7 | ||||
-rw-r--r-- | __pkginfo__.py | 5 | ||||
-rw-r--r-- | adbh.py | 4 | ||||
-rw-r--r-- | configuration.py | 16 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | decorators.py | 1 | ||||
-rw-r--r-- | graph.py | 9 | ||||
-rw-r--r-- | html.py | 125 | ||||
-rw-r--r-- | logging_ext.py | 2 | ||||
-rw-r--r-- | test/unittest_html.py | 59 | ||||
-rw-r--r-- | test/unittest_testlib.py | 16 | ||||
-rw-r--r-- | test/unittest_xmlutils.py | 58 | ||||
-rw-r--r-- | textutils.py | 18 | ||||
-rw-r--r-- | xmlutils.py | 44 |
14 files changed, 354 insertions, 16 deletions
@@ -1,6 +1,13 @@ ChangeLog for logilab.common ============================ +-- + * configuration: fix #8849 Using plugins, options and .pylintrc crashes PyLint + +2009-08-26 -- 0.45.0 + * added function for parsing XML processing instructions + + 2009-08-07 -- 0.44.0 * remove code deprecated for a while now diff --git a/__pkginfo__.py b/__pkginfo__.py index b093a2b..148b3d8 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" distname = 'logilab-common' modname = 'common' -numversion = (0, 44, 0) +numversion = (0, 45, 0) version = '.'.join([str(num) for num in numversion]) license = 'GPL' @@ -27,12 +27,13 @@ modules, * writing interactive command line tools * manipulation files and character strings * interfacing to OmniORB - * generating of SQL queries + * generating SQL queries * running unit tests * manipulating tree structures * accessing RDBMS (currently postgreSQL, mysql and sqlite) * generating text and HTML reports * logging + * parsing XML processing instructions * more... """ @@ -318,7 +318,7 @@ class _PGAdvFuncHelper(_GenericAdvFuncHelper): cmd.append('--username=%s' % dbuser) if not keepownership: cmd.append('--no-owner') - cmd.append('--file=%s' % backupfile) + cmd.append('--file="%s"' % backupfile) cmd.append(dbname) return ' '.join(cmd) @@ -337,7 +337,7 @@ class _PGAdvFuncHelper(_GenericAdvFuncHelper): cmd.append('--dbname %s' % dbname) if not keepownership: cmd.append('--no-owner') - cmd.append(backupfile) + cmd.append('"%s"' % backupfile) cmds.append(' '.join(cmd)) return cmds diff --git a/configuration.py b/configuration.py index f6dec1b..969e36d 100644 --- a/configuration.py +++ b/configuration.py @@ -98,7 +98,8 @@ import sys import re from os.path import exists, expanduser from copy import copy -from ConfigParser import ConfigParser, NoOptionError, NoSectionError +from ConfigParser import ConfigParser, NoOptionError, NoSectionError, \ + DuplicateSectionError from logilab.common.compat import set from logilab.common.textutils import normalize_text, unquote @@ -358,6 +359,7 @@ class OptionsManagerMixIn(object): self._all_options = {} self._short_options = {} self._nocallback_options = {} + self._mygroups = set() # verbosity self.quiet = quiet @@ -400,7 +402,15 @@ class OptionsManagerMixIn(object): """ # add section to the config file if group_name != "DEFAULT": - self._config_parser.add_section(group_name) + try: + self._config_parser.add_section(group_name) + except DuplicateSectionError: + # section may be implicitly added by reading the configuration + # file (see http://www.logilab.org/ticket/8849 for description + # of a case where this may happen) + if group_name in self._mygroups: + raise + self._mygroups.add(group_name) # add option group to the command line parser if options: group = OptionGroup(self._optik_parser, @@ -422,6 +432,8 @@ class OptionsManagerMixIn(object): else: opt_dict['action'] = 'callback' opt_dict['callback'] = self.cb_set_provider_option + if 'choices' in opt_dict: + opt_dict['help'] += ': %s' % '|'.join(opt_dict['choices']) # default is handled here and *must not* be given to optik if you # want the whole machinery to work if 'default' in opt_dict: diff --git a/debian/changelog b/debian/changelog index edee802..8460636 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +logilab-common (0.45.0-1) unstable; urgency=low + + * new upstream release + + -- Olivier Cayrol <olivier.cayrol@logilab.fr> Wed, 26 Aug 2009 17:40:00 +0200 + logilab-common (0.44.0-1) unstable; urgency=low * new upstream release diff --git a/decorators.py b/decorators.py index d7768a0..f5278cc 100644 --- a/decorators.py +++ b/decorators.py @@ -81,7 +81,6 @@ def copy_cache(obj, funcname, cacheobj): except KeyError: pass - class wproperty(object): """Simple descriptor expecting to take a modifier function as first argument and looking for a _<function name> to retrieve the attribute. @@ -12,6 +12,8 @@ __metaclass__ = type import os.path as osp import os +import subprocess +import sys import tempfile def escape(value): @@ -80,12 +82,15 @@ class DotBackend: storedir, basename, target = target_info_from_filename(outputfile) if target != "dot": pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) + os.close(pdot) else: dot_sourcepath = osp.join(storedir, dotfile) else: target = 'png' pdot, dot_sourcepath = tempfile.mkstemp(".dot", name) ppng, outputfile = tempfile.mkstemp(".png", name) + os.close(pdot) + os.close(ppng) pdot = open(dot_sourcepath,'w') if isinstance(self.source, unicode): pdot.write(self.source.encode('UTF8')) @@ -93,8 +98,8 @@ class DotBackend: pdot.write(self.source) pdot.close() if target != 'dot': - os.system('%s -T%s %s -o%s' % (self.renderer, target, - dot_sourcepath, outputfile)) + subprocess.call('%s -T%s %s -o%s' % (self.renderer, target, + dot_sourcepath, outputfile), shell=True) os.unlink(dot_sourcepath) return outputfile @@ -0,0 +1,125 @@ +"""render a tree in HTML. + +:copyright: 2000-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: General Public License version 2 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + + +def render_HTML_tree(tree, selected_node=None, render_node=None, caption=None): + """ + Generate a pure HTML representation of a tree given as an instance + of a logilab.common.tree.Node + + selected_node is the currently selected node (if any) which will + have its surrounding <div> have id="selected" (which default + to a bold border libe with the default CSS). + + render_node is a function that should take a Node content (Node.id) + as parameter and should return a string (what will be displayed + in the cell). + + Warning: proper rendering of the generated html code depends on html_tree.css + """ + tree_depth = tree.depth_down() + if render_node is None: + render_node = str + + # helper function that build a matrix from the tree, like: + # +------+-----------+-----------+ + # | root | child_1_1 | child_2_1 | + # | root | child_1_1 | child_2_2 | + # | root | child_1_2 | | + # | root | child_1_3 | child_2_3 | + # | root | child_1_3 | child_2_4 | + # +------+-----------+-----------+ + # from: + # root -+- child_1_1 -+- child_2_1 + # | | + # | +- child_2_2 + # +- child_1_2 + # | + # +- child1_3 -+- child_2_3 + # | + # +- child_2_2 + def build_matrix(path, matrix): + if path[-1].is_leaf(): + matrix.append(path[:]) + else: + for child in path[-1].children: + build_matrix(path[:] + [child], matrix) + + matrix = [] + build_matrix([tree], matrix) + + # make all lines in the matrix have the same number of columns + for line in matrix: + line.extend([None]*(tree_depth-len(line))) + for i in range(len(matrix)-1, 0, -1): + prev_line, line = matrix[i-1:i+1] + for j in range(len(line)): + if line[j] == prev_line[j]: + line[j] = None + + # We build the matrix of link types (between 2 cells on a line of the matrix) + # link types are : + link_types = {(True, True, True ): 1, # T + (False, False, True ): 2, # | + (False, True, True ): 3, # + (actually, vert. bar with horiz. bar on the right) + (False, True, False): 4, # L + (True, True, False): 5, # - + } + links = [] + for i, line in enumerate(matrix): + links.append([]) + for j in range(tree_depth-1): + cell_11 = line[j] is not None + cell_12 = line[j+1] is not None + cell_21 = line[j+1] is not None and line[j+1].next_sibling() is not None + link_type = link_types.get((cell_11, cell_12, cell_21), 0) + if link_type == 0 and i > 0 and links[i-1][j] in (1, 2, 3): + link_type = 2 + links[-1].append(link_type) + + + # We can now generate the HTML code for the <table> + s = u'<table class="tree">\n' + if caption: + s += '<caption>%s</caption>\n' % caption + + for i, link_line in enumerate(links): + line = matrix[i] + + s += '<tr>' + for j, link_cell in enumerate(link_line): + cell = line[j] + if cell: + if cell.id == selected_node: + s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id)) + else: + s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id)) + else: + s += '<td rowspan="2"> </td>' + s += '<td class="tree_cell_%d_1"> </td>' % link_cell + s += '<td class="tree_cell_%d_2"> </td>' % link_cell + + cell = line[-1] + if cell: + if cell.id == selected_node: + s += '<td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">%s</div></td>' % (render_node(cell.id)) + else: + s += '<td class="tree_cell" rowspan="2"><div class="tree_cell">%s</div></td>' % (render_node(cell.id)) + else: + s += '<td rowspan="2"> </td>' + + s += '</tr>\n' + if link_line: + s += '<tr>' + for j, link_cell in enumerate(link_line): + s += '<td class="tree_cell_%d_3"> </td>' % link_cell + s += '<td class="tree_cell_%d_4"> </td>' % link_cell + s += '</tr>\n' + + s += '</table>' + return s diff --git a/logging_ext.py b/logging_ext.py index 4fce3a7..5a27d05 100644 --- a/logging_ext.py +++ b/logging_ext.py @@ -118,7 +118,7 @@ def init_log(debug=False, syslog=False, logthreshold=None, logfile=None, # setHandler method, so do it this way :$ logger.handlers = [handler] isatty = hasattr(sys.__stdout__, 'isatty') and sys.__stdout__.isatty() - if debug and isatty: + if debug and isatty and sys.platform != 'win32': fmt = ColorFormatter(logformat, logdateformat) def col_fact(record): if 'XXX' in record.message: diff --git a/test/unittest_html.py b/test/unittest_html.py new file mode 100644 index 0000000..927425f --- /dev/null +++ b/test/unittest_html.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +"""unittests for logilab.common.html + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" + +__docformat__ = "restructuredtext en" + +from logilab.common.testlib import TestCase, unittest_main +from logilab.common.tree import Node + +from logilab.common import html + +tree = ('root', ( + ('child_1_1', ( + ('child_2_1', ()), ('child_2_2', ( + ('child_3_1', ()), + ('child_3_2', ()), + ('child_3_3', ()), + )))), + ('child_1_2', (('child_2_3', ()),)))) + +generated_html = """\ +<table class="tree"> +<tr><td class="tree_cell" rowspan="2"><div class="tree_cell">root</div></td><td class="tree_cell_1_1"> </td><td class="tree_cell_1_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_1</div></td><td class="tree_cell_1_1"> </td><td class="tree_cell_1_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_1</div></td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td></tr> +<tr><td class="tree_cell_1_3"> </td><td class="tree_cell_1_4"> </td><td class="tree_cell_1_3"> </td><td class="tree_cell_1_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td></tr> +<tr><td rowspan="2"> </td><td class="tree_cell_2_1"> </td><td class="tree_cell_2_2"> </td><td rowspan="2"> </td><td class="tree_cell_4_1"> </td><td class="tree_cell_4_2"> </td><td class="tree_cell" rowspan="2"><div id="selected" class="tree_cell">child_2_2</div></td><td class="tree_cell_1_1"> </td><td class="tree_cell_1_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_1</div></td></tr> +<tr><td class="tree_cell_2_3"> </td><td class="tree_cell_2_4"> </td><td class="tree_cell_4_3"> </td><td class="tree_cell_4_4"> </td><td class="tree_cell_1_3"> </td><td class="tree_cell_1_4"> </td></tr> +<tr><td rowspan="2"> </td><td class="tree_cell_2_1"> </td><td class="tree_cell_2_2"> </td><td rowspan="2"> </td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td><td class="tree_cell_3_1"> </td><td class="tree_cell_3_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_2</div></td></tr> +<tr><td class="tree_cell_2_3"> </td><td class="tree_cell_2_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td><td class="tree_cell_3_3"> </td><td class="tree_cell_3_4"> </td></tr> +<tr><td rowspan="2"> </td><td class="tree_cell_2_1"> </td><td class="tree_cell_2_2"> </td><td rowspan="2"> </td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td><td class="tree_cell_4_1"> </td><td class="tree_cell_4_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_3_3</div></td></tr> +<tr><td class="tree_cell_2_3"> </td><td class="tree_cell_2_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td><td class="tree_cell_4_3"> </td><td class="tree_cell_4_4"> </td></tr> +<tr><td rowspan="2"> </td><td class="tree_cell_4_1"> </td><td class="tree_cell_4_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_1_2</div></td><td class="tree_cell_5_1"> </td><td class="tree_cell_5_2"> </td><td class="tree_cell" rowspan="2"><div class="tree_cell">child_2_3</div></td><td class="tree_cell_0_1"> </td><td class="tree_cell_0_2"> </td><td rowspan="2"> </td></tr> +<tr><td class="tree_cell_4_3"> </td><td class="tree_cell_4_4"> </td><td class="tree_cell_5_3"> </td><td class="tree_cell_5_4"> </td><td class="tree_cell_0_3"> </td><td class="tree_cell_0_4"> </td></tr> +</table>\ +""" + +def make_tree(tupletree): + n = Node(tupletree[0]) + for child in tupletree[1]: + n.append(make_tree(child)) + return n + +class UIlibHTMLGenerationTC(TestCase): + """ a basic tree node, caracterised by an id""" + def setUp(self): + """ called before each test from this class """ + self.o = make_tree(tree) + + def test_generated_html(self): + s = html.render_HTML_tree(self.o, selected_node="child_2_2") + self.assertTextEqual(s, generated_html) + + +if __name__ == '__main__': + unittest_main() diff --git a/test/unittest_testlib.py b/test/unittest_testlib.py index eacb5b8..1bf5123 100644 --- a/test/unittest_testlib.py +++ b/test/unittest_testlib.py @@ -641,12 +641,14 @@ class DecoratorTC(TestCase): @with_tempdir def createfile(list): - tempfile.mkstemp() - tempfile.mkstemp() + fd1, fn1 = tempfile.mkstemp() + fd2, fn2 = tempfile.mkstemp() dir = tempfile.mkdtemp() - tempfile.mkstemp(dir=dir) + fd3, fn3 = tempfile.mkstemp(dir=dir) tempfile.mkdtemp() list.append(True) + for fd in (fd1, fd2, fd3): + os.close(fd) self.assertFalse(witness) createfile(witness) @@ -672,11 +674,13 @@ class DecoratorTC(TestCase): @with_tempdir def createfile(): - tempfile.mkstemp() - tempfile.mkstemp() + fd1, fn1 = tempfile.mkstemp() + fd2, fn2 = tempfile.mkstemp() dir = tempfile.mkdtemp() - tempfile.mkstemp(dir=dir) + fd3, fn3 = tempfile.mkstemp(dir=dir) tempfile.mkdtemp() + for fd in (fd1, fd2, fd3): + os.close(fd) raise WitnessException() self.assertRaises(WitnessException, createfile) diff --git a/test/unittest_xmlutils.py b/test/unittest_xmlutils.py new file mode 100644 index 0000000..12fafae --- /dev/null +++ b/test/unittest_xmlutils.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +from logilab.common.testlib import TestCase, unittest_main +from logilab.common.xmlutils import parse_pi_data + + +class ProcessingInstructionDataParsingTest(TestCase): + def test_empty_pi(self): + """ + Tests the parsing of the data of an empty processing instruction. + """ + pi_data = u" \t \n " + data = parse_pi_data(pi_data) + self.assertEquals(data, {}) + + def test_simple_pi_with_double_quotes(self): + """ + Tests the parsing of the data of a simple processing instruction using + double quotes for embedding the value. + """ + pi_data = u""" \t att="value"\n """ + data = parse_pi_data(pi_data) + self.assertEquals(data, {u"att": u"value"}) + + def test_simple_pi_with_simple_quotes(self): + """ + Tests the parsing of the data of a simple processing instruction using + simple quotes for embedding the value. + """ + pi_data = u""" \t att='value'\n """ + data = parse_pi_data(pi_data) + self.assertEquals(data, {u"att": u"value"}) + + def test_complex_pi_with_different_quotes(self): + """ + Tests the parsing of the data of a complex processing instruction using + simple quotes or double quotes for embedding the values. + """ + pi_data = u""" \t att='value'\n att2="value2" att3='value3'""" + data = parse_pi_data(pi_data) + self.assertEquals(data, {u"att": u"value", u"att2": u"value2", + u"att3": u"value3"}) + + def test_pi_with_non_attribute_data(self): + """ + Tests the parsing of the data of a complex processing instruction + containing non-attribute data. + """ + pi_data = u""" \t keyword att1="value1" """ + data = parse_pi_data(pi_data) + self.assertEquals(data, {u"keyword": None, u"att1": u"value1"}) + + +# definitions for automatic unit testing + +if __name__ == '__main__': + unittest_main() + diff --git a/textutils.py b/textutils.py index c25d8c8..1c24c70 100644 --- a/textutils.py +++ b/textutils.py @@ -30,6 +30,7 @@ unquote, colorize_ansi """ __docformat__ = "restructuredtext en" +import sys import re from unicodedata import normalize as _uninormalize try: @@ -434,3 +435,20 @@ def colorize_ansi(msg, color=None, style=None): return '%s%s%s' % (escape_code, msg, ANSI_RESET) return msg +DIFF_STYLE = {'separator': 'cyan', 'remove': 'red', 'add': 'green'} + +def diff_colorize_ansi(lines, out=sys.stdout, style=DIFF_STYLE): + for line in lines: + if line[:4] in ('--- ', '+++ '): + out.write(colorize_ansi(line, style['separator'])) + elif line[0] == '-': + out.write(colorize_ansi(line, style['remove'])) + elif line[0] == '+': + out.write(colorize_ansi(line, style['add'])) + elif line[:4] == '--- ': + out.write(colorize_ansi(line, style['separator'])) + elif line[:4] == '+++ ': + out.write(colorize_ansi(line, style['separator'])) + else: + out.write(line) + diff --git a/xmlutils.py b/xmlutils.py new file mode 100644 index 0000000..5a27f0f --- /dev/null +++ b/xmlutils.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +"""XML utilities. + +This module contains useful functions for parsing and using XML data. For the +moment, there is only one function that can parse the data inside a processing +instruction and return a Python dictionnary. + +:copyright: 2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: General Public License version 2 - http://www.gnu.org/licenses +""" +__docformat__ = "restructuredtext en" + +import re + +RE_DOUBLE_QUOTE = re.compile('([\w\-\.]+)="([^"]+)"') +RE_SIMPLE_QUOTE = re.compile("([\w\-\.]+)='([^']+)'") + +def parse_pi_data(pi_data): + """ + Utilitary function that parses the data contained in an XML + processing instruction and returns a dictionnary of keywords and their + associated values (most of the time, the processing instructions contain + data like ``keyword="value"``, if a keyword is not associated to a value, + for example ``keyword``, it will be associated to ``None``). + + :param pi_data: data contained in an XML processing instruction. + :type pi_data: unicode + + :returns: Dictionnary of the keywords (Unicode strings) associated to + their values (Unicode strings) as they were defined in the + data. + :rtype: dict + """ + results = {} + for elt in pi_data.split(): + if RE_DOUBLE_QUOTE.match(elt): + kwd, val = RE_DOUBLE_QUOTE.match(elt).groups() + elif RE_SIMPLE_QUOTE.match(elt): + kwd, val = RE_SIMPLE_QUOTE.match(elt).groups() + else: + kwd, val = elt, None + results[kwd] = val + return results |