Skip to content

Commit

Permalink
Gracefully handle textual citations when author or year are missing (#…
Browse files Browse the repository at this point in the history
…268)

Citation reference improvements: use "n.d." when there's no year, use editor or title when there's no author.
  • Loading branch information
mcmtroffaes committed Sep 9, 2021
1 parent 660a1f4 commit 79f41a4
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 20 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
2.4.1 (in development)
----------------------

* Gracefully handle textual citations when author or year are missing
(see issue #267, reported by fbkarsdorp).

2.4.0 (8 September 2021)
------------------------

Expand Down
15 changes: 14 additions & 1 deletion src/sphinxcontrib/bibtex/style/referencing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import pybtex.plugin
from pybtex.richtext import Text, Tag
from sphinxcontrib.bibtex.richtext import ReferenceInfo, BaseReferenceText
from sphinxcontrib.bibtex.style.template import names, sentence, join
from sphinxcontrib.bibtex.style.template import (
names, sentence, join, author_or_editor_or_title
)
from typing import (
TYPE_CHECKING, Tuple, List, Union, Iterable, Type, Optional, Dict
)
Expand Down Expand Up @@ -161,6 +163,17 @@ def names(self, role: str, full: bool) -> "Node":
other=None if full else self.other,
)

def author_or_editor_or_title(self, full: bool) -> "Node":
"""Returns a template formatting the author, falling back on editor
or title if author is not specified.
"""
return author_or_editor_or_title(
sep=self.sep,
sep2=self.sep2,
last_sep=self.last_sep,
other=None if full else self.other,
)


@dataclasses.dataclass
class GroupReferenceStyle(BaseReferenceStyle):
Expand Down
12 changes: 5 additions & 7 deletions src/sphinxcontrib/bibtex/style/referencing/basic_author_year.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import dataclasses
from typing import TYPE_CHECKING, List, Iterable, Union
from sphinxcontrib.bibtex.style.template import reference, join
from pybtex.style.template import field
from sphinxcontrib.bibtex.style.template import reference, join, year
from . import BaseReferenceStyle, BracketStyle, PersonStyle

if TYPE_CHECKING:
Expand Down Expand Up @@ -34,9 +33,8 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":
def inner(self, role_name: str) -> "Node":
return reference[
join(sep=self.author_year_sep)[
self.person.names(
'author', full='s' in role_name),
field('year')
self.person.author_or_editor_or_title(full='s' in role_name),
year
]
]

Expand Down Expand Up @@ -66,10 +64,10 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":

def inner(self, role_name: str) -> "Node":
return join(sep=self.text_reference_sep)[
self.person.names('author', full='s' in role_name),
self.person.author_or_editor_or_title(full='s' in role_name),
join[
self.bracket.left,
reference[field('year')],
reference[year],
self.bracket.right
]
]
2 changes: 1 addition & 1 deletion src/sphinxcontrib/bibtex/style/referencing/basic_foot.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":

def inner(self, role_name: str) -> "Node":
return join(sep=self.text_reference_sep)[
self.person.names('author', full='s' in role_name),
self.person.author_or_editor_or_title(full='s' in role_name),
footnote_reference,
]
2 changes: 1 addition & 1 deletion src/sphinxcontrib/bibtex/style/referencing/basic_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":

def inner(self, role_name: str) -> "Node":
return join(sep=self.text_reference_sep)[
self.person.names('author', full='s' in role_name),
self.person.author_or_editor_or_title(full='s' in role_name),
join[
self.bracket.left,
reference[entry_label],
Expand Down
2 changes: 1 addition & 1 deletion src/sphinxcontrib/bibtex/style/referencing/basic_super.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":

def inner(self, role_name: str) -> "Node":
return join(sep=self.text_reference_sep)[
self.person.names('author', full='s' in role_name),
self.person.author_or_editor_or_title(full='s' in role_name),
tag('sup')[
join[
self.bracket.left,
Expand Down
2 changes: 1 addition & 1 deletion src/sphinxcontrib/bibtex/style/referencing/extra_author.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":

def inner(self, role_name: str) -> "Node":
return reference[
self.person.names('author', full='s' in role_name)]
self.person.author_or_editor_or_title(full='s' in role_name)]
5 changes: 2 additions & 3 deletions src/sphinxcontrib/bibtex/style/referencing/extra_year.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import dataclasses

from typing import TYPE_CHECKING, List, Iterable
from pybtex.style.template import field
from sphinxcontrib.bibtex.style.template import reference
from sphinxcontrib.bibtex.style.template import reference, year
from . import BaseReferenceStyle, BracketStyle

if TYPE_CHECKING:
Expand All @@ -28,4 +27,4 @@ def outer(self, role_name: str, children: List["BaseText"]) -> "Node":
)

def inner(self, role_name: str) -> "Node":
return reference[field('year')]
return reference[year]
19 changes: 18 additions & 1 deletion src/sphinxcontrib/bibtex/style/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"""

from pybtex.richtext import Text
from pybtex.style.template import Node, _format_list, FieldIsMissing
from pybtex.style.template import (
Node, _format_list, FieldIsMissing, field, first_of, optional, tag
)
from typing import TYPE_CHECKING, Dict, Any, cast, Type

from sphinxcontrib.bibtex.richtext import BaseReferenceText
Expand Down Expand Up @@ -125,3 +127,18 @@ def footnote_reference(children, data: Dict[str, Any]):
# we need to give the footnote text some fake content
# otherwise pybtex richtext engine will mess things up
return reference_text_class(info, '#')


@node
def year(children, data: Dict[str, Any]) -> "BaseText":
assert not children
return first_of[optional[field('year')], 'n.d.'].format_data(data)


@node
def author_or_editor_or_title(children, data, **kwargs):
assert not children
return first_of[
optional[names('author', **kwargs)],
optional[names('editor', **kwargs)],
tag('em')[field('title')]].format_data(data)
1 change: 1 addition & 0 deletions test/roots/test-citation_no_author_no_key/conf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
extensions = ['sphinxcontrib.bibtex']
exclude_patterns = ['_build']
bibtex_bibfiles = ['test.bib']
bibtex_reference_style = 'author_year'
15 changes: 12 additions & 3 deletions test/roots/test-citation_no_author_no_key/index.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
:cite:`_software_2015`
:cite:`2009:mandel`

:cite:p:`software_2015`
:cite:t:`software_2015`

:cite:p:`2009:mandel`
:cite:t:`2009:mandel`

:cite:p:`onlytitle`
:cite:t:`onlytitle`

:cite:p:`onlyeditor`
:cite:t:`onlyeditor`

.. bibliography::
12 changes: 11 additions & 1 deletion test/roots/test-citation_no_author_no_key/test.bib
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@misc{_software_2015,
@misc{software_2015,
title = {Software projects built on {Mesos}},
url = {http://mesos.apache.org/documentation/latest/mesos-frameworks/},
month = sep,
Expand All @@ -15,3 +15,13 @@ @Misc{2009:mandel
eprint = {0901.3725},
primaryClass = {physics.ao-ph}
}

@misc{onlytitle,
title = {This citation only has a title}
}

@misc{onlyeditor,
editor = {Mr. Whatever},
title = {Important Work},
year = {2021}
}
13 changes: 13 additions & 0 deletions test/test_citation.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,24 @@ def test_citation_any_role(app, warning) -> None:
assert {"App", "Bra"} == cits == citrefs


def find_label(output: str, label: str):
assert html_citation_refs(label=label).search(output) is not None


# see issue 85
@pytest.mark.sphinx('html', testroot='citation_no_author_no_key')
def test_citation_no_author_no_key(app, warning) -> None:
app.build()
assert not warning.getvalue()
output = (app.outdir / "index.html").read_text()
find_label(output, "<em>Software projects built on Mesos</em>, 2015")
find_label(output, "2015")
find_label(output, "Mandel, 2009")
find_label(output, "2009")
find_label(output, "<em>This citation only has a title</em>, n.d.")
find_label(output, "n.d.")
find_label(output, "Whatever, 2021")
find_label(output, "2021")


# test cites spanning multiple lines (issue 205)
Expand Down
13 changes: 13 additions & 0 deletions test/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from sphinxcontrib.bibtex.style.referencing import (
BaseReferenceText, BaseReferenceStyle, format_references
)
from sphinxcontrib.bibtex.style.referencing.basic_author_year import (
BasicAuthorYearTextualReferenceStyle
)
from sphinxcontrib.bibtex.style.template import (
entry_label, reference, join, names
)
Expand All @@ -32,6 +35,16 @@ def test_style_names_last() -> None:
assert last(name2).format().render_as('latex') == "Last"


def test_style_names() -> None:
auth = Person('First Last')
fields = dict(title='The Book', year='2000', publisher='Santa')
entry = Entry(type_='book', fields=fields, persons=dict(author=[auth]))
style = BasicAuthorYearTextualReferenceStyle()
rich_name = style.person.names('author', full=True).format_data(
dict(entry=entry, style=style))
assert rich_name.render_as('text') == "Last"


def test_style_names_no_author() -> None:
entry = Entry(type_='book')
with pytest.raises(FieldIsMissing):
Expand Down

0 comments on commit 79f41a4

Please sign in to comment.