From 9ae5f3e6a56802c771e4b223a6e553ae326f1a84 Mon Sep 17 00:00:00 2001 From: Florian Mounier Date: Tue, 26 Apr 2016 14:40:06 +0200 Subject: [PATCH] Add classes option. Handle ellipsis in list type configs to auto-extend parent. --- docs/changelog.rst | 7 +++++++ pygal/__init__.py | 2 +- pygal/_compat.py | 7 +++++++ pygal/config.py | 13 +++++++++++-- pygal/svg.py | 5 +++-- pygal/test/test_config.py | 24 ++++++++++++++++++++++++ pygal/test/test_util.py | 23 +++++++++++++++++++++-- pygal/util.py | 9 ++++++++- 8 files changed, 82 insertions(+), 8 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0f27b0c..93536c1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,6 +2,13 @@ Changelog ========= +2.2.2 +===== + +* Add classes option. +* Handle ellipsis in list type configs to auto-extend parent. (Viva python3) + + 2.2.0 ===== diff --git a/pygal/__init__.py b/pygal/__init__.py index c8f5441..52c542b 100644 --- a/pygal/__init__.py +++ b/pygal/__init__.py @@ -24,7 +24,7 @@ and the maps extensions namespace module. """ -__version__ = '2.2.1' +__version__ = '2.2.2' import pkg_resources import sys diff --git a/pygal/_compat.py b/pygal/_compat.py index 997335d..4811064 100644 --- a/pygal/_compat.py +++ b/pygal/_compat.py @@ -27,10 +27,17 @@ from datetime import datetime, timedelta, tzinfo if sys.version_info[0] == 3: base = (str, bytes) coerce = str + _ellipsis = eval('...') else: base = basestring coerce = unicode + class EllipsisGetter(object): + def __getitem__(self, key): + return key + + _ellipsis = EllipsisGetter()[...] + def is_list_like(value): """Return whether value is an iterable but not a mapping / string""" diff --git a/pygal/config.py b/pygal/config.py index bc9c3ea..0cac19b 100644 --- a/pygal/config.py +++ b/pygal/config.py @@ -22,6 +22,7 @@ from copy import deepcopy from pygal.interpolate import INTERPOLATIONS from pygal.style import DefaultStyle, Style +from pygal.util import mergextend from pygal import formatters @@ -170,8 +171,11 @@ class BaseConfig(MetaConfig('ConfigBase', (object,), {})): """Update the config with the given dictionary""" dir_self_set = set(dir(self)) self.__dict__.update( - dict([(k, v) for (k, v) in kwargs.items() - if not k.startswith('_') and k in dir_self_set])) + dict([ + (k, mergextend(v, self.__dict__.get(k, ()))) + if getattr(Config, k, Key(*[''] * 4)).type == list + else (k, v) for (k, v) in kwargs.items() + if not k.startswith('_') and k in dir_self_set])) def to_dict(self): """Export a JSON serializable dictionary of the config""" @@ -244,6 +248,11 @@ class Config(CommonConfig): "It can be any uri from file:///tmp/style.css to //domain/style.css", str) + classes = Key( + ('pygal-chart',), + list, "Style", "Classes of the root svg node", + str) + defs = Key( [], list, "Misc", "Extraneous defs to be inserted in svg", diff --git a/pygal/svg.py b/pygal/svg.py index 720bcd6..abefe44 100644 --- a/pygal/svg.py +++ b/pygal/svg.py @@ -68,10 +68,11 @@ class Svg(object): self.root = etree.Element('svg', **attrs) self.root.attrib['id'] = self.id.lstrip('#').rstrip() - self.root.attrib['class'] = 'pygal-chart' + if graph.classes: + self.root.attrib['class'] = ' '.join(graph.classes) self.root.append( etree.Comment(u( - 'Generated with pygal %s (%s) ©Kozea 2011-2015 on %s' % ( + 'Generated with pygal %s (%s) ©Kozea 2011-2016 on %s' % ( __version__, 'lxml' if etree.lxml else 'etree', date.today().isoformat())))) diff --git a/pygal/test/test_config.py b/pygal/test/test_config.py index 81ff2b1..76921c4 100644 --- a/pygal/test/test_config.py +++ b/pygal/test/test_config.py @@ -542,3 +542,27 @@ def test_formatters(Chart): assert set([v.text for v in q(".value")]) == set(( u('4€'), u('5€'), u('6€'), '1_a$', '2_a$', u('3¥')) + ( ('6_a$', u('15€')) if Chart in (Pie, SolidGauge) else ())) + + +def test_classes(Chart): + """Test classes option""" + chart = Chart() + assert chart.render_pyquery().attr('class') == 'pygal-chart' + + chart = Chart(classes=()) + assert not chart.render_pyquery().attr('class') + + chart = Chart(classes=(...,)) + assert chart.render_pyquery().attr('class') == 'pygal-chart' + + chart = Chart(classes=('graph',)) + assert chart.render_pyquery().attr('class') == 'graph' + + chart = Chart(classes=('pygal-chart', 'graph')) + assert chart.render_pyquery().attr('class') == 'pygal-chart graph' + + chart = Chart(classes=(..., 'graph')) + assert chart.render_pyquery().attr('class') == 'pygal-chart graph' + + chart = Chart(classes=('graph', ...)) + assert chart.render_pyquery().attr('class') == 'graph pygal-chart' diff --git a/pygal/test/test_util.py b/pygal/test/test_util.py index e576316..b8be162 100644 --- a/pygal/test/test_util.py +++ b/pygal/test/test_util.py @@ -19,11 +19,12 @@ """Utility functions tests""" -from pygal._compat import u +from pygal._compat import u, _ellipsis from pygal.util import ( round_to_int, round_to_float, _swap_curly, template, - truncate, minify_css, majorize) + truncate, minify_css, majorize, mergextend) from pytest import raises +import sys def test_round_to_int(): @@ -153,3 +154,21 @@ def test_majorize(): assert majorize(range(21, 83, 3)) == [30, 45, 60, 75] # TODO: handle crazy cases # assert majorize(range(20, 83, 3)) == [20, 35, 50, 65, 80] + + +def test_mergextend(): + """Test mergextend function""" + assert mergextend(['a', 'b'], ['c', 'd']) == ['a', 'b'] + assert mergextend([], ['c', 'd']) == [] + assert mergextend(['a', 'b'], []) == ['a', 'b'] + + assert mergextend([_ellipsis], ['c', 'd']) == ['c', 'd'] + assert mergextend([_ellipsis, 'b'], ['c', 'd']) == ['c', 'd', 'b'] + assert mergextend(['a', _ellipsis], ['c', 'd']) == ['a', 'c', 'd'] + assert mergextend(['a', _ellipsis, 'b'], ['c', 'd']) == [ + 'a', 'c', 'd', 'b'] + + if sys.version_info[0] >= 3: + # For @#! sake it's 2016 now + assert eval("mergextend(['a', ..., 'b'], ['c', 'd'])") == [ + 'a', 'c', 'd', 'b'] diff --git a/pygal/util.py b/pygal/util.py index a841627..35a71c6 100644 --- a/pygal/util.py +++ b/pygal/util.py @@ -26,7 +26,7 @@ from decimal import Decimal from math import ceil, floor, log10, pi, cos, sin -from pygal._compat import to_unicode, u +from pygal._compat import to_unicode, u, _ellipsis def float_format(number): @@ -364,3 +364,10 @@ def coord_dual(r): def coord_abs_project(center, rho, theta): return coord_format(coord_diff(center, coord_project(rho, theta))) + + +def mergextend(list1, list2): + if _ellipsis not in list1: + return list1 + index = list1.index(_ellipsis) + return list(list1[:index]) + list(list2) + list(list1[index + 1:])