From bb7874c7d49add938fc11865bc4809968621de2b Mon Sep 17 00:00:00 2001 From: ngc92 Date: Wed, 6 Mar 2019 10:38:33 +0100 Subject: [PATCH 1/2] now based on labelref.py --- pylatex/base_classes/float.py | 19 +++++++++++++++-- pylatex/figure.py | 6 ++++++ pylatex/labelref.py | 39 +++++++++++++++++++++++++++++++++++ pylatex/section.py | 16 +++----------- pylatex/table.py | 3 +++ 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/pylatex/base_classes/float.py b/pylatex/base_classes/float.py index 1cae9acb..071f70c5 100644 --- a/pylatex/base_classes/float.py +++ b/pylatex/base_classes/float.py @@ -5,13 +5,16 @@ .. :copyright: (c) 2014 by Jelte Fennema. :license: MIT, see License for more details. """ - +from ..labelref import make_label from . import Environment, Command class Float(Environment): """A class that represents a floating environment.""" + #: Default prefix to use with Marker + marker_prefix = "float" + #: By default floats are positioned inside a separate paragraph. #: Setting this to option to `False` will change that. separate_paragraph = True @@ -35,13 +38,25 @@ def __init__(self, *, position=None, **kwargs): super().__init__(options=position, **kwargs) - def add_caption(self, caption): + def add_caption(self, caption, label=None): """Add a caption to the float. Args ---- caption: str The text of the caption. + label: Label or Marker or str + The label to use for this float. + Returns + ------- + Marker + If a label has been created, its `~.Marker` is returned. """ self.append(Command('caption', caption)) + label = make_label(label, self.marker_prefix) + if label is not None: + self.append(label) + return label.marker + + return None diff --git a/pylatex/figure.py b/pylatex/figure.py index efbdd979..340b87b3 100644 --- a/pylatex/figure.py +++ b/pylatex/figure.py @@ -17,6 +17,9 @@ class Figure(Float): """A class that represents a Figure environment.""" + #: Default prefix to use with Marker + marker_prefix = "fig" + def add_image(self, filename, *, width=NoEscape(r'0.8\textwidth'), placement=NoEscape(r'\centering')): """Add an image to the figure. @@ -94,6 +97,9 @@ def add_plot(self, *args, extension='pdf', **kwargs): class SubFigure(Figure): """A class that represents a subfigure from the subcaption package.""" + #: Default prefix to use with Marker + marker_prefix = "subfig" + packages = [Package('subcaption')] #: By default a subfigure is not on its own paragraph since that looks diff --git a/pylatex/labelref.py b/pylatex/labelref.py index d42f8c7c..df0abe36 100644 --- a/pylatex/labelref.py +++ b/pylatex/labelref.py @@ -14,6 +14,45 @@ def _remove_invalid_char(s): return s +def make_label(label, prefix="", default_name=None): + """ + Helper function for generating a `Label` object from arguments that are passed to a LatexObjects + __init__ function. Will create a corresponding `Marker` if necessary. + Args + ---- + label: Label or bool or str or Marker + The label parameter. Can be a string, in which case a `Marker` will be built. If the string contains a colon, + the it is split into prefix and name. If `label` is a boolean, and `default_name` is set, this name will be used + as the label's name. + prefix: str + The prefix to use if `label` is a string that does not contain a colon. + default_name: str + The label name to use if `label` is `True`. + Returns + ------- + Label + + """ + if isinstance(label, Label): + return label + elif isinstance(label, Marker): + return Label(label) + elif isinstance(label, str): + if ':' in label: + label = label.split(':', 1) + return Label(Marker(label[1], label[0])) + else: + return Label(Marker(label, prefix)) + elif label is True: + if default_name is None: + raise ValueError("No label name given") + return Label(Marker(default_name, prefix)) + elif label is False or label is None: + return None + else: + raise TypeError("Unexpected type %s for label" % type(label)) + + class Marker(LatexObject): """A class that represents a marker (label/ref parameter).""" diff --git a/pylatex/section.py b/pylatex/section.py index 45745f55..77f4e705 100644 --- a/pylatex/section.py +++ b/pylatex/section.py @@ -8,7 +8,7 @@ from .base_classes import Container, Command -from .labelref import Marker, Label +from .labelref import Label, make_label class Section(Container): @@ -42,18 +42,8 @@ def __init__(self, title, numbering=None, *, label=True, **kwargs): if numbering is not None: self.numbering = numbering - if isinstance(label, Label): - self.label = label - elif isinstance(label, str): - if ':' in label: - label = label.split(':', 1) - self.label = Label(Marker(label[1], label[0])) - else: - self.label = Label(Marker(label, self.marker_prefix)) - elif label: - self.label = Label(Marker(title, self.marker_prefix)) - else: - self.label = None + + self.label = make_label(label, self.marker_prefix, default_name=title) super().__init__(**kwargs) diff --git a/pylatex/table.py b/pylatex/table.py index 0aada0ee..393044e6 100644 --- a/pylatex/table.py +++ b/pylatex/table.py @@ -371,6 +371,9 @@ def dumps(self): class Table(Float): """A class that represents a table float.""" + #: Default prefix to use with Marker + marker_prefix = "tab" + class Tabu(Tabular): """A class that represents a tabu (more flexible table).""" From 04849c57603b53c307cc20e0c117bbec48642d2c Mon Sep 17 00:00:00 2001 From: ngc92 Date: Wed, 6 Mar 2019 10:39:13 +0100 Subject: [PATCH 2/2] added some tests --- pylatex/labelref.py | 9 +++- tests/test_labelref.py | 100 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 tests/test_labelref.py diff --git a/pylatex/labelref.py b/pylatex/labelref.py index df0abe36..d588c105 100644 --- a/pylatex/labelref.py +++ b/pylatex/labelref.py @@ -7,9 +7,9 @@ def _remove_invalid_char(s): - """Remove invalid and dangerous characters from a string.""" + """Remove invalid and dangerous characters from a label string.""" - s = ''.join([i if ord(i) >= 32 and ord(i) < 127 else '' for i in s]) + s = ''.join([i if 32 <= ord(i) < 127 else '' for i in s]) s = s.translate(dict.fromkeys(map(ord, "_%~#\\{}\":"))) return s @@ -80,6 +80,11 @@ def __init__(self, name, prefix="", del_invalid_char=True): self.prefix = prefix self.name = name + if name == "": + raise ValueError("Cannot create Marker with empty name") + + super().__init__() + def __str__(self): return ((self.prefix + ":") if self.prefix != "" else "") + self.name diff --git a/tests/test_labelref.py b/tests/test_labelref.py new file mode 100644 index 00000000..0377e9c0 --- /dev/null +++ b/tests/test_labelref.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +from nose.tools import raises + +from pylatex.base_classes import Float +from pylatex.labelref import Marker, Label, make_label + + +################### +# Marker tests +################### + + +def test_marker_no_prefix(): + m = Marker("marker") + assert m.dumps() == "marker", m.dumps() + + +def test_marker_with_prefix(): + m = Marker("marker", "prefix") + assert m.dumps() == "prefix:marker", m.dumps() + + +@raises(ValueError) +def test_marker_empty(): + m = Marker("") + + +def test_with_dash(): + m = Marker("marker-name", "prefix") + assert m.dumps() == "prefix:marker-name", m.dumps() + + +def test_marker_cleanup(): + m = Marker("%marker\n", "\\prefix#") + assert m.dumps() == "prefix:marker", m.dumps() + + +################### +# Label tests +################### + + +def test_label(): + l = Label(Marker("marker", "prefix")) + assert l.dumps() == r"\label{prefix:marker}", l.dumps() + + l = Label("%invalid-marker") + # TODO what is the expected behaviour in this case? + assert l.dumps() == r"\label{invalid-marker}", l.dumps() + + +def test_make_label_pass_through(): + l = Label(Marker("marker", "prefix")) + assert make_label(l) is l + + +def test_make_label_from_string(): + assert make_label("label").dumps() == r"\label{label}" + assert make_label("prefix:label").dumps() == r"\label{prefix:label}" + assert make_label("label", "prefix").dumps() == r"\label{prefix:label}" + + +def test_make_label_from_bool(): + assert make_label(True, default_name="label").dumps() == r"\label{label}" + assert make_label(True, prefix="pre", default_name="label").dumps() == r"\label{pre:label}" + + assert make_label(False, prefix="pre", default_name="label") is None + assert make_label(None, prefix="pre", default_name="label") is None + + +@raises(ValueError) +def test_make_label_from_no_name(): + make_label(True).dumps() + + +@raises(TypeError) +def test_make_label_from_number(): + make_label(5).dumps() + + +################### +# float tests +################### + +def test_float_without_label(): + f = Float() + f.add_caption("cap") + assert f.dumps() == "\\begin{float}%\n\\caption{cap}%\n\\end{float}", f.dumps() + + +def test_float_with_label(): + f = Float() + f.add_caption("cap", Label(Marker("lbl"))) + assert f.dumps() == "\\begin{float}%\n\\caption{cap}%\n\\label{lbl}%\n\\end{float}", f.dumps() + + +def test_float_default_prefix(): + f = Float() + f.add_caption("cap", "lbl") + assert f.dumps() == "\\begin{float}%\n\\caption{cap}%\n\\label{float:lbl}%\n\\end{float}", f.dumps()