Skip to content

Commit

Permalink
Customize tooltips. (#287)
Browse files Browse the repository at this point in the history
Custom tooltip styles, allow disabling tooltips (see issue #286).
  • Loading branch information
mcmtroffaes committed Feb 19, 2022
1 parent 14b05c2 commit aa89f67
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 19 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

* Add support for Python 3.10 and 3.11.

* New ``bibtex_tooltips`` option.
Set to ``False`` to disable tooltip generation.
See issue #286.

* New ``bibtex_tooltips_style`` option to customize tooltip text style.
If empty (the default), the bibliography style is used.
See issue #286.

* Use container node instead of paragraph node for containing bibliographies,
fixing a violation against the docutils spec
(see issue #273, reported by rappdw, with additional input from brechtm).
Expand Down
14 changes: 14 additions & 0 deletions doc/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ the ``sphinxcontrib.bibtex.style.referencing``
`entry point <https://packaging.python.org/guides/creating-and-discovering-plugins/#using-package-metadata>`_ group.
See sphinxcontrib-bibtex's own ``setup.py`` script for examples.

Tooltips
~~~~~~~~

.. versionadded:: 2.4.2

The extension will generate plain text tooltips for citation references,
via the html *title* attribute, to allow a preview of the citation by hovering
over the citation reference.

To disable these tooltips, set ``bibtex_tooltips`` to ``False``.

By default, the bibliography style is used to format the tooltips.
You can set the ``bibtex_tooltips_style`` option to use a different style.

Roles and Directives
--------------------

Expand Down
2 changes: 2 additions & 0 deletions src/sphinxcontrib/bibtex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
* connect events to functions
"""
app.add_config_value("bibtex_default_style", "alpha", "html")
app.add_config_value("bibtex_tooltips", True, "html")
app.add_config_value("bibtex_tooltips_style", "", "html")
app.add_config_value("bibtex_bibfiles", None, "html")
app.add_config_value("bibtex_encoding", "utf-8-sig", "html")
app.add_config_value("bibtex_bibliography_header", "", "html")
Expand Down
45 changes: 31 additions & 14 deletions src/sphinxcontrib/bibtex/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import ast
from typing import TYPE_CHECKING
from typing import List, Dict, NamedTuple, cast, Iterable, Tuple, Set
from typing import List, Dict, NamedTuple, cast, Iterable, Tuple, Set, Optional

import docutils.frontend
import docutils.nodes
Expand Down Expand Up @@ -213,7 +213,8 @@ class Citation(NamedTuple):
bibliography_key: "BibliographyKey" #: Key of its bibliography directive.
key: str #: Key (with prefix).
entry: "Entry" #: Entry from pybtex.
formatted_entry: "FormattedEntry" #: Entry as formatted by pybtex.
formatted_entry: "FormattedEntry" #: Formatted entry for bibliography.
tooltip_entry: Optional["FormattedEntry"] #: Formatted entry for tooltip.


def env_updated(app: "Sphinx", env: "BuildEnvironment") -> Iterable[str]:
Expand Down Expand Up @@ -339,8 +340,11 @@ def env_updated(self) -> Iterable[str]:
used_keys: Set[str] = set()
used_labels: Dict[str, str] = {}
for bibliography_key, bibliography in self.bibliographies.items():
for entry, formatted_entry in self.get_formatted_entries(
bibliography_key, docnames):
for entry, formatted_entry, tooltip_entry in \
self.get_formatted_entries(
bibliography_key, docnames,
self.env.app.config.bibtex_tooltips,
self.env.app.config.bibtex_tooltips_style):
key = bibliography.keyprefix + formatted_entry.key
if bibliography.list_ == 'citation' and key in used_keys:
logger.warning(
Expand All @@ -353,6 +357,7 @@ def env_updated(self) -> Iterable[str]:
key=key,
entry=entry,
formatted_entry=formatted_entry,
tooltip_entry=tooltip_entry,
))
if bibliography.list_ == 'citation':
used_keys.add(key)
Expand Down Expand Up @@ -392,7 +397,11 @@ def resolve_xref(self, env: "BuildEnvironment", fromdocname: str,
fromdocname=fromdocname,
todocname=citation.bibliography_key.docname,
citation_id=citation.citation_id,
title=citation.formatted_entry.text.render(plaintext)))
title=(
citation.tooltip_entry.text.render(plaintext)
if citation.tooltip_entry else None
)
))
for citation in citations.values()]
formatted_references = format_references(
self.reference_style, typ, references)
Expand Down Expand Up @@ -485,32 +494,40 @@ def get_sorted_entries(
yield key, entry

def get_formatted_entries(
self, bibliography_key: "BibliographyKey", docnames: List[str]
) -> Iterable[Tuple["Entry", "FormattedEntry"]]:
self, bibliography_key: "BibliographyKey", docnames: List[str],
tooltips: bool, tooltips_style: str
) -> Iterable[Tuple["Entry", "FormattedEntry",
Optional["FormattedEntry"]]]:
"""Get sorted bibliography entries along with their pybtex labels,
with additional sorting and formatting applied from the pybtex style.
"""
bibliography = self.bibliographies[bibliography_key]
entries = dict(
self.get_sorted_entries(bibliography_key, docnames))
style = cast("BaseStyle", pybtex.plugin.find_plugin(
style: BaseStyle = cast("BaseStyle", pybtex.plugin.find_plugin(
'pybtex.style.formatting', bibliography.style)())
sorted_entries = style.sort(entries.values())
style2: Optional[BaseStyle] = (
cast("BaseStyle", pybtex.plugin.find_plugin(
'pybtex.style.formatting', tooltips_style)())
if tooltips_style else style) if tooltips else None
sorted_entries: Iterable[Entry] = style.sort(entries.values())
labels = style.format_labels(sorted_entries)
for label, entry in zip(labels, sorted_entries):
try:
yield (
entry,
style.format_entry(
bibliography.labelprefix + label, entry),
style2.format_entry(
bibliography.labelprefix + label, entry)
if style2 else None,
)
except FieldIsMissing as exc:
logger.warning(
str(exc),
location=(bibliography_key.docname, bibliography.line),
type="bibtex", subtype="missing_field")
yield(
entry,
FormattedEntry(entry.key, Tag('b', str(exc)),
bibliography.labelprefix + label)
)
formatted_error_entry = FormattedEntry(
entry.key, Tag('b', str(exc)),
bibliography.labelprefix + label)
yield entry, formatted_error_entry, None
15 changes: 10 additions & 5 deletions test/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Some common helper functions for the test suite."""

import re
from typing import Optional

import sphinx

RE_ID = r'[a-z][-?a-z0-9]*'
Expand All @@ -11,13 +13,16 @@
RE_TITLE = r'[^"]*'


def html_citation_refs(refid=RE_ID, label=RE_LABEL, title=RE_TITLE):
def html_citation_refs(
refid=RE_ID, label=RE_LABEL, title: Optional[str] = RE_TITLE):
title_pattern = rf' title="{title}"' if title is not None else ''
return re.compile(
r'<a class="reference internal" '
r'href="(?P<refdoc>[^#]+)?#(?P<refid>{refid})" '
r'title="{title}">'
r'<a class="reference internal"'
r' href="(?P<refdoc>[^#]+)?#(?P<refid>{refid})"'
r'{title_pattern}'
r'>'
r'(?P<label>{label})'
r'</a>'.format(refid=refid, label=label, title=title))
r'</a>'.format(refid=refid, label=label, title_pattern=title_pattern))


def html_docutils_citation_refs(refid=RE_ID, label=RE_LABEL, id_=RE_ID):
Expand Down
46 changes: 46 additions & 0 deletions test/test_citation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import pybtex.plugin
from pybtex.style.formatting.unsrt import Style as UnsrtStyle
from pybtex.style.template import words

from test.common import html_citations, html_citation_refs, \
html_docutils_citation_refs
from dataclasses import dataclass, field
Expand Down Expand Up @@ -378,3 +382,45 @@ def test_citation_toctree(app, warning) -> None:
assert len(html_docutils_citation_refs(
label=r'Test2').findall(output2)) == 1
assert len(html_citations(label='Test2').findall(output2)) == 1


@pytest.mark.sphinx('html', testroot='debug_bibtex_citation')
def test_citation_tooltip(app, warning) -> None:
app.build()
assert not warning.getvalue()
output = (app.outdir / "index.html").read_text()
assert len(html_citations(label='tes').findall(output)) == 1
assert len(html_citation_refs(
label='tes', title=r"The title\.").findall(output)) == 1


@pytest.mark.sphinx('html', testroot='debug_bibtex_citation',
confoverrides={'bibtex_tooltips': False})
def test_citation_tooltip2(app, warning) -> None:
app.build()
assert not warning.getvalue()
output = (app.outdir / "index.html").read_text()
assert len(html_citations(label='tes').findall(output)) == 1
assert len(html_citation_refs(
label='tes', title=None).findall(output)) == 1


class CustomTooltipStyle(UnsrtStyle):
def get_misc_template(self, e):
return words['whoop whoop']


pybtex.plugin.register_plugin(
'pybtex.style.formatting', 'xxx_custom_tooltip_xxx', CustomTooltipStyle)


@pytest.mark.sphinx('html', testroot='debug_bibtex_citation',
confoverrides={
'bibtex_tooltips_style': 'xxx_custom_tooltip_xxx'})
def test_citation_tooltip3(app, warning) -> None:
app.build()
assert not warning.getvalue()
output = (app.outdir / "index.html").read_text()
assert len(html_citations(label='tes').findall(output)) == 1
assert len(html_citation_refs(
label='tes', title='whoop whoop').findall(output)) == 1

0 comments on commit aa89f67

Please sign in to comment.