diff --git a/book/_config.yml b/book/_config.yml
index 3f4b2da..0f4b175 100644
--- a/book/_config.yml
+++ b/book/_config.yml
@@ -40,6 +40,11 @@ sphinx:
html_show_copyright: false
html_last_updated_fmt: '%B %-d, %Y'
html_static_path: ["images"]
+ bibtex_default_style: myapastyle
+ bibtex_reference_style: author_year_round
+ local_extensions:
+ apastyle: _ext/
+ bracket_citation_style: _ext/
bibtex_bibfiles:
- references.bib
diff --git a/book/_ext/apastyle.py b/book/_ext/apastyle.py
new file mode 100644
index 0000000..16828fe
--- /dev/null
+++ b/book/_ext/apastyle.py
@@ -0,0 +1,17 @@
+
+# from pybtex.style.formatting.unsrt import Style
+from pybtexapastyle.formatting.apa import APAStyle
+from pybtexapastyle.labels.apa import LabelStyle as APALabelStyle
+from pybtex.plugin import register_plugin
+# from pybtex.style.template import names, sentence
+
+class MyAPALabelStyle(APALabelStyle):
+ def format_label(self, entry):
+ return APALabelStyle.format_label(self, entry)
+
+class MyAPAStyle(APAStyle):
+ default_label_style = 'myapa'
+
+def setup(app):
+ register_plugin('pybtex.style.labels', 'myapa', MyAPALabelStyle)
+ register_plugin('pybtex.style.formatting', 'myapastyle', MyAPAStyle)
\ No newline at end of file
diff --git a/book/_ext/bracket_citation_style.py b/book/_ext/bracket_citation_style.py
new file mode 100644
index 0000000..cab10f3
--- /dev/null
+++ b/book/_ext/bracket_citation_style.py
@@ -0,0 +1,27 @@
+from dataclasses import dataclass, field
+import sphinxcontrib.bibtex.plugin
+
+from sphinxcontrib.bibtex.style.referencing import BracketStyle
+from sphinxcontrib.bibtex.style.referencing.author_year import AuthorYearReferenceStyle
+
+
+def bracket_style() -> BracketStyle:
+ return BracketStyle(
+ left='(',
+ right=')',
+ )
+
+
+@dataclass
+class MyReferenceStyle(AuthorYearReferenceStyle):
+ bracket_parenthetical: BracketStyle = field(default_factory=bracket_style)
+ bracket_textual: BracketStyle = field(default_factory=bracket_style)
+ bracket_author: BracketStyle = field(default_factory=bracket_style)
+ bracket_label: BracketStyle = field(default_factory=bracket_style)
+ bracket_year: BracketStyle = field(default_factory=bracket_style)
+
+
+def setup(app):
+ sphinxcontrib.bibtex.plugin.register_plugin(
+ 'sphinxcontrib.bibtex.style.referencing',
+ 'author_year_round', MyReferenceStyle)
\ No newline at end of file
diff --git a/book/_ext/pybtexapastyle/LICENSE b/book/_ext/pybtexapastyle/LICENSE
new file mode 100644
index 0000000..579d367
--- /dev/null
+++ b/book/_ext/pybtexapastyle/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Naeka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/book/_ext/pybtexapastyle/formatting/__init__.py b/book/_ext/pybtexapastyle/formatting/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/book/_ext/pybtexapastyle/formatting/apa.py b/book/_ext/pybtexapastyle/formatting/apa.py
new file mode 100644
index 0000000..dfa5a58
--- /dev/null
+++ b/book/_ext/pybtexapastyle/formatting/apa.py
@@ -0,0 +1,486 @@
+# -*- coding:Utf-8 -*-
+from __future__ import unicode_literals
+
+import re
+import six
+
+from pybtex.plugin import find_plugin
+from pybtex.style.formatting import BaseStyle, toplevel
+from pybtex.style.template import (
+ field, first_of, href, join, optional, optional_field, sentence, tag,
+ together, words, node, FieldIsMissing
+)
+
+from pybtex.richtext import Text, Symbol
+
+
+firstlast = find_plugin('pybtex.style.names', 'lastfirst')()
+
+if six.PY2:
+ def format_pages(text):
+ dash_re = re.compile(r'-+')
+ pages = Text(Symbol('ndash')).join(
+ re.split(dash_re, six.text_type(text)))
+ if re.search('[-‒–—―]', six.text_type(text)):
+ return Text("pp.", Symbol('nbsp'), pages)
+ return Text("p.", Symbol('nbsp'), pages)
+else:
+ def format_pages(text):
+ dash_re = re.compile(r'-+')
+ pages = Text(Symbol('ndash')).join(text.split(dash_re))
+ if re.search('[-‒–—―]', str(text)):
+ return Text("pp.", Symbol('nbsp'), pages)
+ return Text("p.", Symbol('nbsp'), pages)
+
+
+pages = field('pages', apply_func=format_pages)
+date = words[field('year'), optional[", ", field('month')]]
+
+
+@node
+def apa_names(children, context, role, **kwargs):
+ """
+ Returns formatted names as an APA compliant reference list citation.
+ """
+ assert not children
+
+ try:
+ persons = context['entry'].persons[role]
+ except KeyError:
+ raise FieldIsMissing(role, context['entry'])
+
+ style = context['style']
+
+ if len(persons) > 7:
+ persons = persons[:6] + persons[-1:]
+ formatted_names = [style.format_name(
+ person, style.abbreviate_names) for person in persons]
+ return join(sep=', ', last_sep=', … ')[
+ formatted_names].format_data(context)
+ else:
+ formatted_names = [style.format_name(
+ person, style.abbreviate_names) for person in persons]
+ return join(sep=', ', sep2=', & ', last_sep=', & ')[
+ formatted_names].format_data(context)
+
+
+@node
+def editor_names(children, context, with_suffix=True, **kwargs):
+ """
+ Returns formatted editor names for inbook.
+ """
+ assert not children
+
+ try:
+ editors = context['entry'].persons['editor']
+ except KeyError:
+ raise FieldIsMissing('editor', context['entry'])
+
+ formatted_names = [
+ firstlast.format(editor, True) for editor in editors]
+
+ if with_suffix:
+ return words[
+ join(sep=', ', sep2=', & ', last_sep=', & ')[formatted_names],
+ "(Eds.)" if len(editors) > 1 else "(Ed.)"
+ ].format_data(context)
+
+ return join(sep=', ', sep2=', & ', last_sep=', & ')[
+ formatted_names
+ ].format_data(context)
+
+
+class APAStyle(BaseStyle):
+ name = 'apa'
+ default_name_style = 'lastfirst'
+ default_sorting_style = 'author_year_title'
+ default_label_style = 'apa'
+
+ def __init__(self, *args, **kwargs):
+ super(APAStyle, self).__init__(*args, **kwargs)
+ self.abbreviate_names = True
+
+ def format_names(self, role, as_sentence=True):
+ formatted_names = apa_names(role)
+ if as_sentence:
+ return sentence(capfirst=False)[formatted_names]
+ else:
+ return formatted_names
+
+ def format_author_or_editor_and_date(self, e):
+ if 'author' in e.persons and 'editor' in e.persons:
+ return sentence(sep=' ')[
+ self.format_names('author'), join["(", date, ")."],
+ self.format_editor(e, as_sentence=False)]
+ elif 'author' in e.persons:
+ return sentence(sep=' ')[
+ self.format_names('author'), join["(", date, ")"]]
+ else:
+ return sentence(sep=' ')[
+ self.format_editor(e, as_sentence=False),
+ join["(", date, ")"]]
+
+ def format_editor(self, e, as_sentence=True):
+ editors = self.format_names('editor', as_sentence=False)
+ if 'editor' not in e.persons:
+ # when parsing the template, a FieldIsMissing exception
+ # will be thrown anyway; no need to do anything now,
+ # just return the template that will throw the exception
+ return editors
+ if len(e.persons['editor']) > 1:
+ word = '(Eds.)'
+ else:
+ word = '(Ed.)'
+ result = join(sep=' ')[editors, word]
+ if as_sentence:
+ return sentence[result]
+ else:
+ return result
+
+ def format_volume(self, e, for_article=False):
+ prefix = "Vol."
+ if for_article:
+ return join[
+ tag('em')[field('volume')],
+ optional['(', field('number'), ')'],
+ ]
+ else:
+ return optional[together[prefix, field('volume')]]
+
+ def format_title(self, e, which_field, as_sentence=True):
+ formatted_title = field(
+ which_field, apply_func=lambda text: text.capitalize()
+ )
+ if as_sentence:
+ return sentence[formatted_title]
+ else:
+ return formatted_title
+
+ def format_btitle(self, e, which_field, as_sentence=True):
+ formatted_title = tag('em')[field(which_field)]
+ if as_sentence:
+ return sentence[formatted_title]
+ else:
+ return formatted_title
+
+ def format_web_refs(self, e):
+ # Based on urlbst output.web.refs
+ return sentence(add_period=False)[
+ optional[self.format_url(e)],
+ optional[self.format_eprint(e)],
+ optional[self.format_pubmed(e)],
+ optional[self.format_doi(e)],
+ ]
+
+ def format_url(self, e):
+ # Based on urlbst format.url
+ return words[
+ 'URL:',
+ href[
+ field('url', raw=True),
+ field('url', raw=True)
+ ]
+ ]
+
+ def format_pubmed(self, e):
+ # Based on urlbst format.pubmed
+ return href[
+ join[
+ 'https://www.ncbi.nlm.nih.gov/pubmed/',
+ field('pubmed', raw=True)
+ ],
+ join[
+ 'PMID:',
+ field('pubmed', raw=True)
+ ]
+ ]
+
+ def format_doi(self, e):
+ # Based on urlbst format.doi
+ return href[
+ join[
+ 'https://doi.org/',
+ field('doi', raw=True)
+ ],
+ join[
+ 'doi:',
+ field('doi', raw=True)
+ ]
+ ]
+
+ def format_eprint(self, e):
+ # Based on urlbst format.eprint
+ return href[
+ join[
+ 'https://arxiv.org/abs/',
+ field('eprint', raw=True)
+ ],
+ join[
+ 'arXiv:',
+ field('eprint', raw=True)
+ ]
+ ]
+
+ def get_article_template(self, e):
+ # Required fields: author, title, journal, year
+ # Optional fields: volume, number, pages, month, note, key
+ volume_and_pages = first_of[
+ # Volume and pages, with optional issue number
+ optional[
+ join[
+ self.format_volume(e, for_article=True),
+ optional[', ', field('pages')]
+ ],
+ ],
+ # Pages only
+ pages,
+ ]
+ template = toplevel[
+ self.format_names('author'),
+ sentence[
+ join["(", date, ")"]
+ ],
+ self.format_title(e, 'title'),
+ sentence[
+ tag('em')[field('journal')],
+ optional[volume_and_pages],
+ ],
+ sentence[optional_field('note')],
+ self.format_web_refs(e),
+ ]
+ return template
+
+ def get_book_template(self, e):
+ # Required fields: author/editor, title, publisher, year
+ # Optional fields: volume, series, address, edition, month, note, key,
+ # isbn
+ return toplevel[
+ self.format_author_or_editor_and_date(e),
+ sentence(sep=' ')[
+ self.format_btitle(e, 'title'),
+ optional[
+ sentence[
+ optional[field('edition'), ' ed.'],
+ self.format_volume(e),
+ ]
+ ]
+ ],
+ sentence(sep=': ')[
+ optional_field('address'),
+ field('publisher'),
+ ],
+ sentence[optional_field('note')],
+ ]
+
+ def get_booklet_template(self, e):
+ # Required fields: title
+ # Optional fields: author, howpublished, address, month, year, note,
+ # key
+ return toplevel[
+ sentence(sep=' ')[
+ first_of[
+ optional[self.format_names(
+ 'author', as_sentence=False)],
+ "None to claim their bones"
+ ],
+ join["(", first_of[optional[date], "n.d."], ")"]
+ ],
+ self.format_title(e, 'title'),
+ sentence[optional_field('address')],
+ sentence[optional_field('note')],
+ ]
+
+ def get_inbook_template(self, e):
+ # Required fields: author/editor, title, chapter/pages, publisher, year
+ # Optional fields: volume, series, address, edition, month, note, key
+ return toplevel[
+ sentence(sep=' ')[
+ self.format_names('author'),
+ join["(", date, ")"]
+ ],
+ self.format_title(e, 'title'),
+ sentence(sep=' ')[
+ optional[
+ "In ",
+ editor_names(),
+ ","
+ ],
+ self.format_btitle(e, 'booktitle', as_sentence=False),
+ optional[
+ join[
+ "(",
+ sentence(add_period=False)[
+ optional[field('edition'), ' ed.'],
+ self.format_volume(e),
+ pages,
+ ],
+ ")"
+ ]
+ ]
+ ],
+ sentence(sep=': ')[
+ optional_field('address'),
+ field('publisher'),
+ ],
+ sentence[optional_field('note')],
+ ]
+
+ def get_incollection_template(self, e):
+ # Required fields: author, title, booktitle, year
+ # Optional fields: editor, pages, organization, publisher, address,
+ # month, note, key
+ return toplevel[
+ self.format_author_or_editor_and_date(e),
+ self.format_title(e, 'title'),
+ sentence(sep=' ')[
+ self.format_btitle(e, 'booktitle', as_sentence=False),
+ optional["(", pages, ")"]
+ ],
+ sentence(sep=': ')[
+ optional_field('address'),
+ optional_field('publisher'),
+ ],
+ sentence[optional_field('note')],
+ ]
+
+ def get_inproceedings_template(self, e):
+ # Required fields: author, title, booktitle, year
+ # Optional fields: editor, pages, organization, publisher, address,
+ # month, note, key
+ return toplevel[
+ self.format_author_or_editor_and_date(e),
+ self.format_title(e, 'title'),
+ sentence(sep=' ')[
+ self.format_btitle(e, 'booktitle', as_sentence=False),
+ optional["(", pages, ")"]
+ ],
+ sentence(sep=': ')[
+ optional_field('address'),
+ optional_field('publisher'),
+ ],
+ sentence[optional_field('note')],
+ self.format_web_refs(e)
+ ]
+
+ def get_manual_template(self, e):
+ # Required fields: title
+ # Optional fields: author, organization, address, edition, month, year,
+ # note, key
+ return toplevel[
+ sentence(sep=' ')[
+ first_of[
+ optional[self.format_names('author', as_sentence=False)],
+ optional_field('organization'),
+ "None to claim their bones"
+ ],
+ join["(", first_of[optional[date], "n.d."], ")"]
+ ],
+ self.format_btitle(e, 'title'),
+ sentence[optional_field('address')],
+ sentence[optional_field('note')],
+ self.format_web_refs(e)
+ ]
+
+ def get_mastersthesis_template(self, e):
+ # Required fields: author, title, school, year
+ # Optional fields: address, month, note, key
+ return toplevel[
+ sentence(sep=' ')[
+ self.format_names('author'),
+ join["(", date, ")"]
+ ],
+ sentence(sep=' ')[
+ self.format_btitle(e, 'title', as_sentence=False),
+ "(Master's thesis)"
+ ],
+ sentence[
+ field('school'),
+ optional_field('address'),
+ ],
+ sentence[optional_field('note')],
+ ]
+
+ def get_misc_template(self, e):
+ # Required fields: aucun
+ # Optional fields: author, title, howpublished, month, year, note, key,
+ # type
+ template = toplevel[
+ sentence(sep=' ')[
+ first_of[
+ optional[self.format_names('author', as_sentence=False)],
+ optional_field('publisher'),
+ "None to claim their bones"
+ ],
+ join["(", first_of[optional[date], "n.d."], ")"]
+ ],
+ optional[self.format_title(e, 'title')],
+ sentence[optional_field('note')],
+ self.format_web_refs(e)
+ ]
+ return template
+
+ def get_phdthesis_template(self, e):
+ # Required fields: author, title, school, year
+ # Optional fields: address, month, note, key
+ return toplevel[
+ sentence(sep=' ')[
+ self.format_names('author'),
+ join["(", date, ")"]
+ ],
+ sentence(sep=' ')[
+ self.format_btitle(e, 'title', as_sentence=False),
+ "(Doctoral dissertation)"
+ ],
+ sentence[
+ field('school'),
+ optional_field('address'),
+ ],
+ sentence[optional_field('note')],
+ ]
+
+ def get_proceedings_template(self, e):
+ # Required fields: title, year
+ # Optional fields: editor, publisher, organization, address, month,
+ # note, key
+ return toplevel[
+ sentence(sep=' ')[
+ first_of[
+ optional[self.format_editor(e, as_sentence=False)],
+ optional_field('organization'),
+ "None to claim their bones"
+ ],
+ join["(", first_of[optional[date], "n.d."], ")"]
+ ],
+ self.format_btitle(e, 'title'),
+ sentence(sep=': ')[
+ optional_field('address'),
+ optional_field('publisher'),
+ ],
+ sentence[optional_field('note')],
+ ]
+
+ def get_techreport_template(self, e):
+ # Required fields: author, title, institution, year
+ # Optional fields: type, number, address, month, note, key
+ return toplevel[
+ sentence(sep=' ')[
+ self.format_names('author', as_sentence=False),
+ join["(", date, ")"]
+ ],
+ self.format_btitle(e, 'title'),
+ sentence[field('institution')],
+ sentence[optional_field('note')],
+ ]
+
+ def get_unpublished_template(self, e):
+ # Required fields: author, title, note
+ # Optional fields: month, year, key
+ template = toplevel[
+ sentence(sep=' ')[
+ self.format_names('author', as_sentence=False),
+ join["(", first_of[optional[date], "n.d."], ")"]
+ ],
+ self.format_btitle(e, 'title'),
+ sentence[field('note')],
+ ]
+ return template
diff --git a/book/_ext/pybtexapastyle/formatting/pybtex/style/formatting/__init__.py b/book/_ext/pybtexapastyle/formatting/pybtex/style/formatting/__init__.py
new file mode 100644
index 0000000..5ac42b8
--- /dev/null
+++ b/book/_ext/pybtexapastyle/formatting/pybtex/style/formatting/__init__.py
@@ -0,0 +1,69 @@
+from __future__ import unicode_literals
+
+from pybtex.style import FormattedEntry, FormattedBibliography
+from pybtex.style.template import node, join
+from pybtex.richtext import Symbol
+from pybtex.plugin import Plugin, find_plugin
+
+
+@node
+def toplevel(children, data):
+ return join(sep=Symbol('newblock')) [children].format_data(data)
+
+
+class BaseStyle(Plugin):
+ """
+ The base class for pythonic formatting styles.
+ """
+
+ default_name_style = None
+ default_label_style = None
+ default_sorting_style = None
+
+ def __init__(self, label_style=None, name_style=None, sorting_style=None, abbreviate_names=False, min_crossrefs=2, **kwargs):
+ self.name_style = find_plugin('pybtex.style.names', name_style or self.default_name_style)()
+ self.label_style = find_plugin('pybtex.style.labels', label_style or self.default_label_style)()
+ self.sorting_style = find_plugin('pybtex.style.sorting', sorting_style or self.default_sorting_style)()
+ self.format_name = self.name_style.format
+ self.format_labels = self.label_style.format_labels
+ self.sort = self.sorting_style.sort
+ self.abbreviate_names = abbreviate_names
+ self.min_crossrefs = min_crossrefs
+
+ def format_entries(self, entries, bib_data=None):
+ sorted_entries = self.sort(entries)
+ labels = self.format_labels(sorted_entries)
+ for label, entry in zip(labels, sorted_entries):
+ yield self.format_entry(label, entry, bib_data=bib_data)
+
+ def format_entry(self, label, entry, bib_data=None):
+ context = {
+ 'entry': entry,
+ 'style': self,
+ 'bib_data': bib_data,
+ }
+ try:
+ get_template = getattr(self, 'get_{}_template'.format(entry.type))
+ except AttributeError:
+ format_method = getattr(self, "format_" + entry.type)
+ text = format_method(context)
+ else:
+ text = get_template(entry).format_data(context)
+ return FormattedEntry(entry.key, text, label)
+
+ def format_bibliography(self, bib_data, citations=None):
+ """
+ Format bibliography entries with the given keys and return a
+ ``FormattedBibliography`` object.
+
+ :param bib_data: A :py:class:`pybtex.database.BibliographyData` object.
+ :param citations: A list of citation keys.
+ """
+
+ if citations is None:
+ citations = list(bib_data.entries.keys())
+ citations = bib_data.add_extra_citations(citations, self.min_crossrefs)
+ entries = [bib_data.entries[key] for key in citations]
+ formatted_entries = self.format_entries(entries)
+ formatted_bibliography = FormattedBibliography(formatted_entries, style=self, preamble=bib_data.preamble)
+ return formatted_bibliography
diff --git a/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/__init__.py b/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/__init__.py
new file mode 100644
index 0000000..3430087
--- /dev/null
+++ b/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/__init__.py
@@ -0,0 +1,12 @@
+from __future__ import unicode_literals
+
+
+from pybtex.plugin import Plugin
+
+
+class BaseSortingStyle(Plugin):
+ def sorting_key(self, entry):
+ raise NotImplementedError
+
+ def sort(self, entries):
+ return sorted(entries, key=self.sorting_key)
\ No newline at end of file
diff --git a/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/author_year_title.py b/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/author_year_title.py
new file mode 100644
index 0000000..c315c0c
--- /dev/null
+++ b/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/author_year_title.py
@@ -0,0 +1,39 @@
+from __future__ import unicode_literals
+
+from pybtex.style.sorting import BaseSortingStyle
+
+class SortingStyle(BaseSortingStyle):
+
+ def sorting_key(self, entry):
+ if entry.type in ('book', 'inbook'):
+ author_key = self.author_editor_key(entry)
+ elif 'author' in entry.persons:
+ author_key = self.persons_key(entry.persons['author'])
+ else:
+ author_key = ''
+ return (author_key, entry.fields.get('year', ''), entry.fields.get('title', ''))
+
+ def persons_key(self, persons):
+ return ' '.join(self.person_key(person) for person in persons)
+
+ def person_key(self, person):
+ if person.prelast_names[0] != '{' and person.last_names[0] != '{' and person.first_names[0] != '{':
+ return ' '.join((
+ ' '.join(person.prelast_names + person.last_names),
+ ' '.join(person.first_names + person.middle_names),
+ ' '.join(person.lineage_names),
+ )).lower()
+ else:
+ return ' '.join((
+ ' '.join(person.first_names + person.middle_names),
+ ' '.join(person.prelast_names + person.last_names),
+ ' '.join(person.lineage_names),
+ )).lower()
+
+ def author_editor_key(self, entry):
+ if entry.persons.get('author'):
+ return self.persons_key(entry.persons['author'])
+ elif entry.persons.get('editor'):
+ return self.persons_key(entry.persons['editor'])
+ else:
+ return ''
diff --git a/book/_ext/pybtexapastyle/labels/__init__.py b/book/_ext/pybtexapastyle/labels/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/book/_ext/pybtexapastyle/labels/apa.py b/book/_ext/pybtexapastyle/labels/apa.py
new file mode 100644
index 0000000..3632dc6
--- /dev/null
+++ b/book/_ext/pybtexapastyle/labels/apa.py
@@ -0,0 +1,67 @@
+# -*- coding:Utf-8 -*-
+from __future__ import unicode_literals
+
+from collections import Counter
+import re
+import unicodedata
+
+from pybtex.style.labels import BaseLabelStyle
+
+
+_nonalnum_pattern = re.compile('[^A-Za-z0-9 \-]+', re.UNICODE)
+
+
+def _strip_accents(s):
+ return "".join(
+ (c for c in unicodedata.normalize('NFD', s)
+ if not unicodedata.combining(c)))
+
+
+def _strip_nonalnum(parts):
+ """Strip all non-alphanumerical characters from a list of strings.
+
+ >>> print(_strip_nonalnum([u"ÅA. B. Testing 12+}[.@~_", u" 3%"]))
+ AABTesting123
+ """
+ s = "".join(parts)
+ return _nonalnum_pattern.sub("", _strip_accents(s))
+
+
+class LabelStyle(BaseLabelStyle):
+ def format_labels(self, sorted_entries):
+ labels = [self.format_label(entry) for entry in sorted_entries]
+ count = Counter(labels)
+ counted = Counter()
+ for label in labels:
+ if count[label] == 1:
+ yield label
+ else:
+ yield label # + chr(ord('a') + counted[label])
+ # counted.update([label])
+
+ def format_label(self, entry):
+ label = "Anonymous"
+ if 'author' in entry.persons:
+ label = self.format_author_or_editor_names(entry.persons['author'])
+ elif 'editor' in entry.persons:
+ label = self.format_author_or_editor_names(entry.persons['editor'])
+ elif 'organization' in entry.fields:
+ label = entry.fields['organization']
+ if label.startswith("The "):
+ label = label[4:]
+ return ''
+ # if 'year' in entry.fields:
+ # return "{}, {}".format(label, entry.fields['year'])
+ # else:
+ # return "{}, n.d.".format(label)
+
+ def format_author_or_editor_names(self, persons):
+ if len(persons)==1:
+ return _strip_nonalnum(persons[0].last_names)
+ elif len(persons)==2:
+ return "{} & {}".format(
+ _strip_nonalnum(persons[0].last_names),
+ _strip_nonalnum(persons[1].last_names))
+ else:
+ return "{} et al.".format(
+ _strip_nonalnum(persons[0].last_names))
diff --git a/book/_ext/pybtexapastyle/names/__init__.py b/book/_ext/pybtexapastyle/names/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/book/_ext/pybtexapastyle/names/firstlast.py b/book/_ext/pybtexapastyle/names/firstlast.py
new file mode 100644
index 0000000..8f23263
--- /dev/null
+++ b/book/_ext/pybtexapastyle/names/firstlast.py
@@ -0,0 +1,39 @@
+# -*- coding:Utf-8 -*-
+from __future__ import unicode_literals
+
+from pybtex.style.names import BaseNameStyle, name_part
+from pybtex.style.template import join
+
+
+class NameStyle(BaseNameStyle):
+
+ def format(self, person, abbr=False):
+ r"""
+ Format names similarly to {vv~}{ll}{, jj}{, f.} in BibTeX.
+
+ >>> from pybtex.database import Person
+ >>> name = Person(string=r"Charles Louis Xavier Joseph de la Vall{\'e}e Poussin")
+ >>> firstlast = NameStyle().format
+
+ >>> print(firstlast(name).format().render_as('latex'))
+ Charles Louis Xavier~Joseph de~la Vall{é}e~Poussin
+ >>> print(firstlast(name).format().render_as('html'))
+ Charles Louis Xavier Joseph de la Vallée Poussin
+
+ >>> print(firstlast(name, abbr=True).format().render_as('latex'))
+ C.~L. X.~J. de~la Vall{é}e~Poussin
+ >>> print(firstlast(name, abbr=True).format().render_as('html'))
+ C. L. X. J. de la Vallée Poussin
+
+ >>> name = Person(first='First', last='Last', middle='Middle')
+ >>> print(firstlast(name).format().render_as('latex'))
+ First~Middle Last
+ >>> print(firstlast(name, abbr=True).format().render_as('latex'))
+ F.~M. Last
+
+ """
+ return join[
+ name_part(abbr=abbr)[person.rich_first_names + person.rich_middle_names],
+ name_part(before=' ')[person.rich_prelast_names],
+ name_part(before=' ')[person.rich_last_names],
+ ]
diff --git a/book/_ext/pybtexapastyle/setup.py b/book/_ext/pybtexapastyle/setup.py
new file mode 100644
index 0000000..a62d122
--- /dev/null
+++ b/book/_ext/pybtexapastyle/setup.py
@@ -0,0 +1,27 @@
+import setuptools
+
+
+setuptools.setup(
+ name='pybtex-apa-style',
+ version='1.3',
+ author='Naeka',
+ author_email='contact@naeka.fr',
+ description='Pybtex APA-like style',
+ url='https://github.com/naeka/pybtex-apa-style',
+ py_modules=['formatting.apa', 'labels.apa', 'names.firstlast'],
+ entry_points={
+ 'pybtex.style.formatting': 'apa = formatting.apa:APAStyle',
+ 'pybtex.style.labels': 'apa = labels.apa:LabelStyle',
+ 'pybtex.style.names': 'firstlast = names.firstlast:NameStyle',
+ },
+ classifiers=(
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Topic :: Text Processing :: Markup',
+ 'Topic :: Utilities',
+ ),
+)
diff --git a/book/credits.md b/book/credits.md
index a46413e..e545095 100644
--- a/book/credits.md
+++ b/book/credits.md
@@ -31,10 +31,10 @@ Ths doesn't include small contributions from various people from within and outs
Parts of this book are taken from other external resources. If an author is not listed on a particular page, it is by the Authors. The following pages are not edited by the TeachBooks Team:
-- [](external/latex-to-markdown-conversion/README.md) {cite:ts}`timon_latex`, [BSD 3-clause license](https://opensource.org/license/BSD-3-Clause).
+- [](external/latex-to-markdown-conversion/README.md) by {cite:t}`timon_latex`, [BSD 3-clause license](https://opensource.org/license/BSD-3-Clause).
## Acknowledgements
Big thanks to the various colleagues and teaching assistant part of the [TeachBooks](https://teachbooks.io/) for developing tools and providing support to improve this book.
## License
-This manual is [CC BY 4.0 licensed](https://creativecommons.org/licenses/by/4.0/) allowing you to share and adapt the material, as long as the source is named. Additional licenses may be applicable for external resources.
\ No newline at end of file
+This manual is [CC BY 4.0 licensed](https://creativecommons.org/licenses/by/4.0/) allowing you to share and adapt the material, as long as the source is named. Additional licenses may be applicable for external resources.
diff --git a/requirements.txt b/requirements.txt
index 0a63bae..a3917fa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,3 +10,5 @@ sphinx-image-inverter
--extra-index-url https://gitlab.tudelft.nl/api/v4/projects/11239/packages/pypi/simple
sphinx-thebe ~= 0.9.9
+
+docutils==0.17.1
\ No newline at end of file