Skip to content
Snippets Groups Projects
Commit 5e1c75a6 authored by Johann Jacobsohn's avatar Johann Jacobsohn
Browse files

add experimental export-to-bibtex support

parent cc2e5fda
Branches
No related tags found
No related merge requests found
...@@ -13,9 +13,12 @@ import subprocess ...@@ -13,9 +13,12 @@ import subprocess
from dataclasses import dataclass from dataclasses import dataclass
import re import re
from functools import partial from functools import partial
import requests
from mattermostdriver import Driver from mattermostdriver import Driver
import urwid import urwid
import configargparse import configargparse
from pybtex.database import BibliographyData, Entry
import xml.etree.ElementTree as ET
URL = "mattermost.cen.uni-hamburg.de" URL = "mattermost.cen.uni-hamburg.de"
CHANNEL = "n5myem9yc7fyzb9am7ym5o41ry" CHANNEL = "n5myem9yc7fyzb9am7ym5o41ry"
...@@ -29,11 +32,60 @@ class PostDTO: ...@@ -29,11 +32,60 @@ class PostDTO:
reporter: str reporter: str
doi: str doi: str
@dataclass
class PaperDTO:
""""Encapsulate Mattermost Posts."""
author: str
title: str
journal: str
year: int
doi: str
class Bibtex:
def entry(self, author, title, journal, year):
return BibliographyData({
f'{author}{year}': Entry('article', [
('author', author),
('title', title),
('journal', journal),
('year', year),
])
}).to_string('bibtex')
def entry_from_doi(self, doi):
paper = Doi().get_info(doi)
return self.entry(paper.author, paper.title, paper.journal, paper.year)
def bib_from_dois(self, dois):
return "\n".join([self.entry_from_doi(doi) for doi in dois])
class Doi: class Doi:
"""Interface w/ the doi.org api""" """Interface w/ the doi.org api"""
def get_doi_link(self, doi): def get_doi_link(self, doi):
return f"http://doi.org/$doi" """Assemble doi link."""
return f"http://doi.org/{doi}"
def load_doi_data(self, doi):
headers = {
'Accept': 'application/vnd.crossref.unixsd+xml',
}
return requests.get(f'http://dx.doi.org/{doi}', headers=headers).content
def parse_doi_xml(self, xml):
root = ET.fromstring(xml)
author = root.find(".//{http://www.crossref.org/xschema/1.1}surname").text # fixme
title = root.find(".//{http://www.crossref.org/xschema/1.1}title").text
journal = root.find(".//{http://www.crossref.org/xschema/1.1}full_title").text
year = root.find(".//{http://www.crossref.org/xschema/1.1}year").text
doi = root.find(".//{http://www.crossref.org/xschema/1.1}year").text
return PaperDTO(author, title, journal, year, doi)
def get_info(self, doi):
xml = self.load_doi_data(doi)
return self.parse_doi_xml(xml)
def extract_doi(self, hay): def extract_doi(self, hay):
"""Parse doi from string, or None if not found. """Parse doi from string, or None if not found.
...@@ -41,7 +93,8 @@ class Doi: ...@@ -41,7 +93,8 @@ class Doi:
>>> Doi().extract_doi("https://doi.org/10.1093/petrology/egaa077") >>> Doi().extract_doi("https://doi.org/10.1093/petrology/egaa077")
'10.1093/petrology/egaa077' '10.1093/petrology/egaa077'
""" """
matches = re.compile(r'\b10\.\d{4,9}/[-._;()/:A-Z0-9]+', re.I).search(hay) pattern = r'\b10\.\d{4,9}/[-._;()/:A-Z0-9]+'
matches = re.compile(pattern, re.I).search(hay)
return matches.group() if matches else None return matches.group() if matches else None
...@@ -57,10 +110,11 @@ class Mattermost: ...@@ -57,10 +110,11 @@ class Mattermost:
self.mattermost.login() self.mattermost.login()
self.reporters = {} self.reporters = {}
def get_reporter(self, id): def get_reporter(self, id):
"""Load user from mattermost api and cache."""
if id not in self.reporters: if id not in self.reporters:
self.reporters[id] = self.mattermost.users.get_user(id)["username"] self.reporters[id] = self.mattermost.users.get_user(id)["username"]
return self.reporters[id] return self.reporters[id]
def retrieve_all_messages(self): def retrieve_all_messages(self):
...@@ -96,30 +150,35 @@ class Papersurfer: ...@@ -96,30 +150,35 @@ class Papersurfer:
def __init__(self, username, password): def __init__(self, username, password):
self._screen = urwid.raw_display.Screen() self._screen = urwid.raw_display.Screen()
self.size = self._screen.get_cols_rows() self.size = self._screen.get_cols_rows()
self.filter = ""
palette = [ palette = [
('I say', 'default,bold', 'default', 'bold'), ('I say', 'default,bold', 'default', 'bold'),
('needle', 'default, bold, underline', 'default', 'bold')] ('needle', 'default, bold, underline', 'default', 'bold')]
ask = urwid.Edit(('I say', u"Filter?\n")) ask = urwid.Edit(('I say', u"Filter?\n"))
exitbutton = urwid.Button(u'Exit') exitbutton = urwid.Button(u'Exit')
self.exportbutton = urwid.Button(u'Export filtered list as bibtex')
div = urwid.Divider(u'-') div = urwid.Divider(u'-')
self.mtm = Mattermost(username, password) self.mtm = Mattermost(username, password)
body = [self.listItem(paper) for paper in self.mtm.retrieve()] body = [self.list_item(paper) for paper in self.mtm.retrieve()]
self.listcontent = urwid.SimpleFocusListWalker(body) self.listcontent = urwid.SimpleFocusListWalker(body)
paperlist = urwid.BoxAdapter(urwid.ListBox(self.listcontent), paperlist = urwid.BoxAdapter(urwid.ListBox(self.listcontent),
self.size[1] - 5) self.size[1] - 5)
pile = urwid.Pile([ask, div, paperlist, div, exitbutton]) pile = urwid.Pile([ask, div, paperlist, div,
urwid.Columns([exitbutton, self.exportbutton])])
top = urwid.Filler(pile, valign='middle') top = urwid.Filler(pile, valign='middle')
urwid.connect_signal(ask, 'change', self.onchange) urwid.connect_signal(ask, 'change', self.onchange)
urwid.connect_signal(exitbutton, 'click', self.on_exit_clicked) urwid.connect_signal(exitbutton, 'click', self.on_exit_clicked)
urwid.connect_signal(self.exportbutton, 'click', self.on_export_clicked)
urwid.MainLoop(top, palette).run() self.mainloop = urwid.MainLoop(top, palette)
self.mainloop.run()
def listItem(self, paper, needle=""): def list_item(self, paper, needle=""):
"""Create highlighted text entry.""" """Create highlighted text entry."""
text_items = [] text_items = []
needle = needle or "ßß" needle = needle or "ßß"
...@@ -143,16 +202,43 @@ class Papersurfer: ...@@ -143,16 +202,43 @@ class Papersurfer:
pile = urwid.Pile([title, button_bar, urwid.Divider()]) pile = urwid.Pile([title, button_bar, urwid.Divider()])
return pile return pile
def updscrn(self):
self.mainloop.draw_screen()
def onchange(self, _, needle): def onchange(self, _, needle):
"""Handle filter change.""" """Handle filter change."""
self.filter = needle
self.listcontent.clear() self.listcontent.clear()
self.listcontent.extend([self.listItem(paper, needle) self.listcontent.extend([self.list_item(paper, needle)
for paper in self.mtm.get_filtered(needle)]) for paper in self.mtm.get_filtered(needle)])
def running_export(self, state):
btn = self.exportbutton
label = btn.get_label()
running_indicator = " (running...)"
if state:
btn.set_label(label + running_indicator)
else:
btn.set_label(label.replace(running_indicator, ""))
self.updscrn()
def on_exit_clicked(self, button): def on_exit_clicked(self, button):
"""Handle exitbutton click and exit.""" """Handle exitbutton click and exit."""
raise urwid.ExitMainLoop() raise urwid.ExitMainLoop()
def on_export_clicked(self, _):
"""Handle exitbutton click and exit."""
self.running_export(True)
self.export_to_bibtex()
self.running_export(False)
def export_to_bibtex(self):
papers = self.mtm.get_filtered(self.filter)
dois = [paper.doi for paper in papers]
string = Bibtex().bib_from_dois(dois)
with open("export.bib", 'w') as f:
f.write(string)
def h_open_discussion(self, post, _): def h_open_discussion(self, post, _):
"""Handle click/enter on discussion button.""" """Handle click/enter on discussion button."""
self.open_discussion(post) self.open_discussion(post)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment