Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added label/ref functionality to float environments #253

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions pylatex/base_classes/float.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
6 changes: 6 additions & 0 deletions pylatex/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
48 changes: 46 additions & 2 deletions pylatex/labelref.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,52 @@


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


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)."""

Expand Down Expand Up @@ -41,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

Expand Down
16 changes: 3 additions & 13 deletions pylatex/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


from .base_classes import Container, Command
from .labelref import Marker, Label
from .labelref import Label, make_label


class Section(Container):
Expand Down Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions pylatex/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)."""
Expand Down
100 changes: 100 additions & 0 deletions tests/test_labelref.py
Original file line number Diff line number Diff line change
@@ -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()