diff --git a/Readme.md b/Readme.md index b69fb12..efedf12 100644 --- a/Readme.md +++ b/Readme.md @@ -1,13 +1,9 @@ Pelican BibTeX ============== -Organize your scientific publications with BibTeX in Pelican - -Author | Vlad Niculae -----------------|----- -Author Email | vlad@vene.ro -Author Homepage | http://vene.ro -Github Account | https://github.com/vene +Organize your scientific publications with BibTeX in Pelican. Original author is Vlad Niculae (http://vene.ro). +This project is forked from https://github.com/vene and provides support for multiple BibTeX files and slightly +more advanced sorting and displaying options. *Note*: This code is unlicensed. It was not submitted to the `pelican-plugins` official repository because of the license constraint imposed there. @@ -22,31 +18,51 @@ Requirements pip install pybtex ``` -How to Use -========== +The example template (which is optional) requires `Markdown` at runtime. + +```bash +pip install Markdown +``` + +How to Configure the Plugin +=========================== This plugin reads a user-specified BibTeX file and populates the context with a list of publications, ready to be used in your Jinja2 template. -Configuration is simply: +As with all pelican plugins, you need to specify the path in which pelican searches for plugins. +Let's assume you have cloned or downloaded this plugin to the folder `plugins/pelican-bibtex` inside your pelican folder. +You then need to configure pelican like this by inserting the following into in `pelicanconf.py`: + +```python +PLUGIN_PATHS = [ 'plugins' ] +PLUGINS = [ 'other-plugin-a', 'other-plugin-b', 'pelican-bibtex' ] +``` + +The plugin itself is configured as follows (also in `pelicanconf.py`): ```python -PUBLICATIONS_SRC = 'content/pubs.bib' +PUBLICATIONS = { + 'mypub': { 'file': 'content/publications.bib' } +} ``` -If the file is present and readable, you will be able to find the `publications` -variable in all templates. It is a list of tuples with the following fields: +What the Plugin provides +======================== + +You will be able to find a `publications` variable in all templates. If the given +`file` is present and readable, *BibTeX* entries will also be accessible in the template. +As a result, the `publications` variable (`dict`) will contain a field with the identifier `mypub` (also a `dict`) containing entries of the following tuple: + ``` (key, year, text, bibtex, pdf, slides, poster) ``` -1. `key` is the BibTeX key (identifier) of the entry. +1. `key` is the *BibTeX* key (identifier) of the entry. 2. `year` is the year when the entry was published. Useful for grouping by year in templates using Jinja's `groupby` 3. `text` is the HTML formatted entry, generated by `pybtex`. -4. `bibtex` is a string containing BibTeX code for the entry, useful to make it -available to people who want to cite your work. -5. `pdf`, `slides`, `poster`: in your BibTeX file, you can add these special fields, -for example: +4. `bibtex` is a string containing *BibTeX* code for the entry, useful to make it available to people who want to cite your work. +5. `pdf`, `slides`, `poster`: in your *BibTeX* file, you can add these special fields, for example: ``` @article{ foo13 @@ -55,51 +71,228 @@ for example: slides = {/slides/foo13.html} } ``` -This plugin will take all defined fields and make them available in the template. -If a field is not defined, the tuple field will be `None`. Furthermore, the -fields are stripped from the generated BibTeX (found in the `bibtex` field). +If a field is not defined, the tuple field will be `None`. Furthermore, the fields are stripped from the generated *BibTeX* (found in the `bibtex` field). + +Advanced Configuration +====================== + +The configuration allows for multiple files and control over several variables **that are also passed on to the template phase**. +The following optional fields can be specified for each bibliography in the `PUBLICATIONS` variable: + +* `title`: Title for this bibliography (h2), if empty, the bibliographies key is used instead. +* `header`: Bool denoting whether a header (h2) should be produced for this bibliography. +* `split`: Bool denoting whether bibliographies should be split by year (h3). +* `split_link`: Bool denoting whether to generate a "link to top" after each year's section. +* `bottom_link`: Bool denoting whether to generate a "link to top" after this bibliography. +* `highlight`: String, e.g., a name, that will be entailed in a \ tag to highlight. +* `all_bibtex`: Provide a link to the original .bib file + +A `PUBLICATIONS_NAVBAR` variable can be used in `pelicanconf.py` to specify whether or not to produce a line that contains links to each bibliography section. + +The following example contains a default entry `simple-example` plus a secondary bibliography `modified-defaults` in which all variables that can be configured through the plugin are set to non-standard. + +```python +PUBLICATIONS = { + 'simple-example': { 'file': 'all.bib' }, + 'modified-defaults': { + 'file': 'all.bib', + 'title': 'Different Appearance', + 'header': False, + 'split': False, + 'split_link': False, + 'bottom_link': False, + 'highlight': ['Patrick Holthaus'], + 'all_bibtex': True } +} + +PUBLICATIONS_NAVBAR = True +``` Template Example ================ -You probably want to define a 'publications.html' direct template. Don't forget -to add it to the `DIRECT\_TEMPLATES` configuration key. Note that we are escaping -the BibTeX string twice in order to properly display it. This can be achieved -using `forceescape`. +You probably want to define a `publications.html` direct template that makes use of the variables this plugin defines. +Create an appropriate template file in the folder ```templates``` and add it to pelican's search path by editing ```pelicanconf.py```: ```python -{% extends "base.html" %} -{% block title %}Publications{% endblock %} +EXTRA_TEMPLATES_PATHS = [ 'templates' ] +``` + +The template also needs to be included in your page by adding the following to the header section. +The entry must match the template's file name. + +```rst +:template: publications +``` + +--- +**NOTE** + +If you don't define a template, this plugin won't achieve you any visible result. + +--- + +Consider the following template for inclusion. It respects all configuration methods described above. +Additionally, the following header entries can be used per page to restrict what bibliographies are displayed on that page and on which (header) level. + +```rst +:bibliographies: modified-defaults,simple-example +:bibheader: 2 +``` + +By default, all bibliographies are considered and included with an `

` tag. + +
Click to reveal example template + +```jinja2 +{% extends "page.html" %} {% block content %} - +
-

Publications

-
    - {% for key, year, text, bibtex, pdf, slides, poster in publications %} -
  • {{ text }} - [ Bibtex ] - {% for label, target in [('PDF', pdf), ('Slides', slides), ('Poster', poster)] %} - {{ "[ %s ]" % (target, label) if target }} - {% endfor %} -
  • - {% endfor %} -
+ {% if page.title %} +

{{ page.title }}

+ {% endif %} + {% import 'includes/translations.html' as translations with context %} + {{ translations.translations_for(page) }} + {% if PDF_PROCESSOR %} + + get the pdf + + {% endif %} +
+ {{ page.content }} + + +{% if PUBLICATIONS_NAVBAR %} +

+ {% for bib in publications|sort %} + {{ publications[bib]['title'] }} + {% if not loop.last %}·{% endif %} + {% endfor %} +

+{% endif %} + + +{% if page.bibliographies %} + {% set bibliographies = page.bibliographies.split(',') %} +{% else %} + {% set bibliographies = publications.keys() %} +{% endif %} +{% if page.bibheader %} + {% set mainheader = page.bibheader %} + {% set splitheader = mainheader|int() + 1 %} +{% else %} + {% set mainheader = 2 %} + {% set splitheader = 3 %} +{% endif %} + + +{% for bib in publications|sort %} + {% if bib in bibliographies %} +
+ {% if publications[bib]['header'] %} + {{ publications[bib]['title'] }} + {% endif %} + {% if publications[bib]['all_bibtex'] %} + {% set ns = namespace(fbt='') %} + {% for key, year, text, bibtex, pdf, slides, poster in publications[bib]['data'] %} + {% set ns.fbt = ns.fbt + bibtex %} + {% endfor %} + You can download or display all {{ publications[bib]['title']|lower }} in BibTeX format. +
+ {% set fbt = '```tex\n' + ns.fbt + '```' %} + {{ fbt|md }} + Hide BibTeX for all {{ publications[bib]['title']|lower }}. +
+ {% endif %} + {% if publications[bib]['split'] %} + {% set remember = namespace(year="0") %} + {% for key, year, text, bibtex, pdf, slides, poster in publications[bib]['data'] %} + {% if remember.year != year %} + {% if remember.year !="0" %} + + {% if publications[bib]['split_link'] %} + + {% endif %} + {% endif %} + {{ year }} +
    + {% set remember.year=year %} + {% endif %} +
  • + {{ text }} + [ BibTeX ] + {% for label, target in [('PDF', pdf), ('Slides', slides), ('Poster', poster)] %} + {{ "[ %s ]" % (target, label) if target }} + {% endfor %} +
    + {% set bibtex = '```tex\n' + bibtex + '```' %} + {{ bibtex|md }} +
    +
  • + {% endfor %} +
+ {% else %} +
    + {% for key, year, text, bibtex, pdf, slides, poster in publications[bib]['data'] %} +
  • + {{ text }} + [ BibTeX ] + {% for label, target in [('PDF', pdf), ('Slides', slides), ('Poster', poster)] %} + {{ "[ %s ]" % (target, label) if target }} + {% endfor %} +
    + {% set bibtex = '```tex\n' + bibtex + '```' %} + {{ bibtex|md }} +
    +
  • + {% endfor %} +
+ {% endif %} + {% if not loop.last and publications[bib]['bottom_link'] %} + + {% endif %} +
+ {% endif %} +{% endfor %} + + + {% if page.comments == 'enabled' %} + {% include 'includes/comments.html' %} + {% endif %} +
+ {% endblock %} ``` +
+ +--- +**NOTE** + +This template uses Jinja filter to parse the resulting text with Markdown. + +--- + +To make it work, you will have to include `Markdown` in the `pelicanconf.py` with: + +```python +from markdown import Markdown +``` + +And define the function `md` in `pelicanconf.py` as such: + +```python +def md(content, *args): + return markdown.convert(content) +JINJA_FILTERS = { + 'md': md, +} +``` + Extending this plugin ===================== diff --git a/pelican_bibtex.py b/pelican_bibtex.py index ef64bce..3e311f6 100644 --- a/pelican_bibtex.py +++ b/pelican_bibtex.py @@ -12,10 +12,15 @@ # Unlicense (see UNLICENSE for details) import logging +import collections logger = logging.getLogger(__name__) from pelican import signals +import os.path + +import re + __version__ = '0.2.1' @@ -25,17 +30,37 @@ def add_publications(generator): Configuration ------------- - generator.settings['PUBLICATIONS_SRC']: - local path to the BibTeX file to read. + generator.settings['PUBLICATIONS']: + Dictionary that contains bibliographies: + The key denotes the bibliographies name to use in headers + The values describe the BibTeX files to read + Mandatory for this plugin. + generator.settings['PUBLICATIONS_NAVBAR']: + Bool denoting whether a navigation bar containing links to each bibliography should be produced. + Defaults to 'True'. + generator.settings['PUBLICATIONS_HEADER']: + Bool denoting whether a header (h2) should be produced for each bibliography. + Defaults to 'True'. + generator.settings['PUBLICATIONS_SPLIT']: + Bool denoting whether bibliographies should be split by year (h3). + Defaults to 'True'. + generator.settings['PUBLICATIONS_HIGHLIGHTs']: + String, e.g., a name, that will be entailed in a tag to highlight. + Default: empty Output ------ generator.context['publications']: - List of tuples (key, year, text, bibtex, pdf, slides, poster). + Dictionary containing the name of the publication list a a key, bibliography entries as a value. + A bibliography entry contains of a list of tuples (key, year, text, bibtex, pdf, slides, poster). See Readme.md for more details. """ - if 'PUBLICATIONS_SRC' not in generator.settings: + + if 'PUBLICATIONS' not in generator.settings: return + if 'PUBLICATIONS_NAVBAR' not in generator.settings: + generator.context['PUBLICATIONS_NAVBAR'] = True + try: from StringIO import StringIO except ImportError: @@ -50,48 +75,155 @@ def add_publications(generator): logger.warn('`pelican_bibtex` failed to load dependency `pybtex`') return - refs_file = generator.settings['PUBLICATIONS_SRC'] - try: - bibdata_all = Parser().parse_file(refs_file) - except PybtexError as e: - logger.warn('`pelican_bibtex` failed to parse file %s: %s' % ( - refs_file, - str(e))) - return - - publications = [] - - # format entries - plain_style = plain.Style() - html_backend = html.Backend() - formatted_entries = plain_style.format_entries(bibdata_all.entries.values()) - - for formatted_entry in formatted_entries: - key = formatted_entry.key - entry = bibdata_all.entries[key] - year = entry.fields.get('year') - # This shouldn't really stay in the field dict - # but new versions of pybtex don't support pop - pdf = entry.fields.get('pdf', None) - slides = entry.fields.get('slides', None) - poster = entry.fields.get('poster', None) - - #render the bibtex string for the entry - bib_buf = StringIO() - bibdata_this = BibliographyData(entries={key: entry}) - Writer().write_stream(bibdata_this, bib_buf) - text = formatted_entry.text.render(html_backend) - - publications.append((key, - year, - text, - bib_buf.getvalue(), - pdf, - slides, - poster)) - - generator.context['publications'] = publications - + refs = generator.settings['PUBLICATIONS'] + generator.context['publications'] = collections.OrderedDict() + + for rid in refs: + ref = refs[rid] + bibfile = os.path.join(generator.settings['PATH'], ref['file']) + try: + bibdata_all = Parser().parse_file(bibfile) + except PybtexError as e: + logger.warn('`pelican_bibtex` failed to parse file %s: %s' % ( + bibfile, + str(e))) + return + + if 'title' in ref: + title = ref['title'] + else: + title = rid + + if 'header' in ref: + header = ref['header'] + else: + header = True + + if 'split' in ref: + split = ref['split'] + else: + split = True + + if 'split_link' in ref: + split_link = ref['split_link'] + else: + split_link = True + + if 'bottom_link' in ref: + bottom_link = ref['bottom_link'] + else: + bottom_link = True + + if 'all_bibtex' in ref: + all_bibtex = ref['all_bibtex'] + else: + all_bibtex = False + + if 'highlight' in ref: + highlights = ref['highlight'] + else: + highlights = [] + + if 'group_type' in ref: + group_type = ref['group_type'] + else: + group_type = False + + publications = [] + + # format entries + plain_style = plain.Style() + html_backend = html.Backend() + formatted_entries = plain_style.format_entries(bibdata_all.entries.values()) + + for formatted_entry in formatted_entries: + key = formatted_entry.key + entry = bibdata_all.entries[key] + year = entry.fields.get('year') + typee = entry.type + + if entry.fields.get('tags'): + tags = [tag.strip() for tag in entry.fields.get('tags').split(';')] + else: + tags = [] + + if entry.fields.get('supplements'): + supplements = [tag.strip() for tag in entry.fields.get('supplements').split(';')] + else: + supplements = [] + + display_tags = [x for x in tags if x != "doi-open" and x != "url-open"] + + # This shouldn't really stay in the field dict + # but new versions of pybtex don't support pop + pdf = entry.fields.get('pdf', None) + slides = entry.fields.get('slides', None) + poster = entry.fields.get('poster', None) + doi = entry.fields.get('doi', None) + url = entry.fields.get('url', None) + + + #clean fields from appearing in bibtex and on website + entry_tmp = entry + for to_del in ['pdf', 'slides', 'poster', 'tags', 'supplements']: + entry_tmp.fields.pop(to_del, None) + + #render the bibtex string for the entry + bib_buf = StringIO() + bibdata_this = BibliographyData(entries={key: entry_tmp}) + Writer().write_stream(bibdata_this, bib_buf) + + #clean more fields from appearing on website + for to_del in ['doi', 'url']: + entry_tmp.fields.pop(to_del, None) + + entry_clean = next(plain_style.format_entries(bibdata_this.entries.values()), None) + + # apply highlight (strong) + text = entry_clean.text.render(html_backend) + for replace in highlights: + text = text.replace(replace, '' + replace + '') + + publications.append((key, + typee, + year, + text, + tags, + display_tags, + supplements, + bib_buf.getvalue(), + pdf, + slides, + poster, + doi, + url)) + + generator.context['publications'][rid] = {} + generator.context['publications'][rid]['title'] = title + generator.context['publications'][rid]['path'] = os.path.basename(bibfile) + generator.context['publications'][rid]['header'] = header + generator.context['publications'][rid]['split'] = split + generator.context['publications'][rid]['bottom_link'] = bottom_link + generator.context['publications'][rid]['split_link'] = split_link + generator.context['publications'][rid]['all_bibtex'] = all_bibtex + generator.context['publications'][rid]['data'] = collections.OrderedDict() + if group_type: + generator.context['publications'][rid]['data'] = sorted(publications, + key=lambda pub: ( + -int( + pub[2]. + replace("in press", "9900"). + replace("under review", "9901") + ), + pub[1])) + else: + generator.context['publications'][rid]['data'] = sorted(publications, + key=lambda pub: + -int( + pub[2]. + replace("in press", "9900"). + replace("under review", "9901") + )) def register(): signals.generator_init.connect(add_publications) diff --git a/templates/pubs.html b/templates/pubs.html new file mode 100644 index 0000000..2b201e1 --- /dev/null +++ b/templates/pubs.html @@ -0,0 +1,120 @@ +{% extends "page.html" %} +{% block content %} + + +
+ {% if page.title %} +

{{ page.title }}

+ {% endif %} + {% import 'includes/translations.html' as translations with context %} + {{ translations.translations_for(page) }} + {% if PDF_PROCESSOR %} + + get the pdf + + {% endif %} +
+ {{ page.content }} + + +{% if PUBLICATIONS_NAVBAR %} +

+ {% for bib in publications|sort %} + {{ publications[bib]['title'] }} + {% if not loop.last %}·{% endif %} + {% endfor %} +

+{% endif %} + + +{% if page.bibliographies %} + {% set bibliographies = page.bibliographies.split(',') %} +{% else %} + {% set bibliographies = publications.keys() %} +{% endif %} +{% if page.bibheader %} + {% set mainheader = page.bibheader %} + {% set splitheader = mainheader|int() + 1 %} +{% else %} + {% set mainheader = 2 %} + {% set splitheader = 3 %} +{% endif %} + + +{% for bib in publications|sort %} + {% if bib in bibliographies %} +
+ {% if publications[bib]['header'] %} + {{ publications[bib]['title'] }} + {% endif %} + {% if publications[bib]['all_bibtex'] %} + {% set ns = namespace(fbt='') %} + {% for key, year, text, bibtex, pdf, slides, poster in publications[bib]['data'] %} + {% set ns.fbt = ns.fbt + bibtex %} + {% endfor %} + You can download or display all {{ publications[bib]['title']|lower }} in BibTeX format. +
+ {% set fbt = '```tex\n' + ns.fbt + '```' %} + {{ fbt|md }} + Hide BibTeX for all {{ publications[bib]['title']|lower }}. +
+ {% endif %} + {% if publications[bib]['split'] %} + {% set remember = namespace(year="0") %} + {% for key, year, text, bibtex, pdf, slides, poster in publications[bib]['data'] %} + {% if remember.year != year %} + {% if remember.year !="0" %} + + {% if publications[bib]['split_link'] %} + + {% endif %} + {% endif %} + {{ year }} +
    + {% set remember.year=year %} + {% endif %} +
  • + {{ text }} + [ BibTeX ] + {% for label, target in [('PDF', pdf), ('Slides', slides), ('Poster', poster)] %} + {{ "[ %s ]" % (target, label) if target }} + {% endfor %} +
    + {% set bibtex = '```tex\n' + bibtex + '```' %} + {{ bibtex|md }} +
    +
  • + {% endfor %} +
+ {% else %} +
    + {% for key, year, text, bibtex, pdf, slides, poster in publications[bib]['data'] %} +
  • + {{ text }} + [ BibTeX ] + {% for label, target in [('PDF', pdf), ('Slides', slides), ('Poster', poster)] %} + {{ "[ %s ]" % (target, label) if target }} + {% endfor %} +
    + {% set bibtex = '```tex\n' + bibtex + '```' %} + {{ bibtex|md }} +
    +
  • + {% endfor %} +
+ {% endif %} + {% if not loop.last and publications[bib]['bottom_link'] %} + + {% endif %} +
+ {% endif %} +{% endfor %} + + + {% if page.comments == 'enabled' %} + {% include 'includes/comments.html' %} + {% endif %} +
+
+ +{% endblock %}