From 39071dba065b8daf243e7133b7d8ed6432fea8f8 Mon Sep 17 00:00:00 2001 From: Johann Jacobsohn <j.jacobsohn@satzmedia.de> Date: Sun, 9 Aug 2020 12:51:27 +0200 Subject: [PATCH] add loading indicator and info popup for papers with data from doi.org --- papersurfer.py | 163 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 130 insertions(+), 33 deletions(-) diff --git a/papersurfer.py b/papersurfer.py index 94493a5..5197284 100644 --- a/papersurfer.py +++ b/papersurfer.py @@ -13,12 +13,11 @@ import subprocess from dataclasses import dataclass import re from functools import partial -import xml.etree.ElementTree as ET +import json import requests from mattermostdriver import Driver import urwid import configargparse -from pybtex.database import BibliographyData, Entry URL = "mattermost.cen.uni-hamburg.de" CHANNEL = "n5myem9yc7fyzb9am7ym5o41ry" @@ -37,9 +36,11 @@ class PostDTO: class PaperDTO: """"Encapsulate Mattermost Posts.""" author: str + authors: str title: str journal: str year: int + abstract: str doi: str @@ -59,21 +60,39 @@ class Doi: def load_doi_data(self, doi): headers = { - 'Accept': 'application/vnd.crossref.unixsd+xml', + 'Accept': 'application/json', } return requests.get(f'http://dx.doi.org/{doi}', headers=headers).content - def parse_doi_xml(self, xml): - root = ET.fromstring(xml) - scheme = ".//{http://www.crossref.org/xschema/1.1}" - author = root.find(f"{scheme}surname").text # fixme - title = root.find(f"{scheme}title").text - journal = root.find(f"{scheme}full_title").text - year = root.find(f"{scheme}year").text - doi = root.find(f"{scheme}doi").text - - return PaperDTO(author, title, journal, year, doi) + def parse_doi_json(self, jsoncontent): + """Tranform doi json to PaperDTO""" + info = json.loads(jsoncontent) + + with open("debug.json", "w") as file: + file.write(json.dumps(info)) + + author = (f"{info['author'][0]['given']} {info['author'][0]['family']}" + if "author" in info + else "Author N/A") + authors = (", ".join([f"{a['given']} {a['family']}" + for a in info['author']]) + if "author" in info + else "Authors N/A") + title = (info['title'] + if "title" in info + and isinstance(info['title'], str) + else "Title N/A") + journal = (info['publisher'] + if "publisher" in info + else "Journal N/A") + year = info['created']['date-parts'][0][0] + doi = info['DOI'] + abstract = (info['abstract'] + if "abstract" in info + else "Abstract N/A") + + return PaperDTO(author, authors, title, journal, year, abstract, doi) def get_bibtex(self, doi): headers = { @@ -82,8 +101,8 @@ class Doi: return requests.get(f'http://dx.doi.org/{doi}', headers=headers).text def get_info(self, doi): - xml = self.load_doi_data(doi) - return self.parse_doi_xml(xml) + jsoncontent = self.load_doi_data(doi) + return self.parse_doi_json(jsoncontent) def extract_doi(self, hay): """Parse doi from string, or None if not found. @@ -147,20 +166,10 @@ class PrettyButton(urwid.Button): button_left = urwid.Text('[') button_right = urwid.Text(']') - def __init__(self, *args, onclick=lambda *x:x, **kwargs): + def __init__(self, *args, onclick=lambda *x: x, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) urwid.connect_signal(self, 'click', onclick) - def button(self, label, onclick): - """Render a pretty button.""" - btn = urwid.Button(label) - urwid.connect_signal(btn, 'click', onclick) - - wrapper = urwid.AttrMap(btn, '', 'highlight') - padding = urwid.Padding(wrapper, left=4, right=4) - - return padding, btn - class Papersurfer: """Provide UI and interface with mattermost class.""" @@ -173,6 +182,10 @@ class Papersurfer: ('I say', 'default,bold', 'default', 'bold'), ('needle', 'default, bold, underline', 'default', 'bold'), ('highlight', 'black', 'dark blue'), + ('banner', 'black', 'light gray'), + ('selectable', 'white', 'black'), + ('focus', 'black', 'light gray'), + ('papertitle', 'default,bold', 'default', 'bold') ] ask = urwid.Edit(('I say', u"Filter?\n")) exitbutton = PrettyButton(u'Exit', onclick=self.on_exit_clicked) @@ -182,20 +195,87 @@ class Papersurfer: self.mtm = Mattermost(username, password) - body = [self.list_item(paper) for paper in self.mtm.retrieve()] + body = [urwid.Text("")] self.listcontent = urwid.SimpleFocusListWalker(body) paperlist = urwid.BoxAdapter(urwid.ListBox(self.listcontent), self.size[1] - 5) pile = urwid.Pile([ask, div, paperlist, div, urwid.Columns([exitbutton, self.exportbutton])]) - top = urwid.Filler(pile, valign='middle') + self.top = urwid.Filler(pile, valign='middle') + self._pile = urwid.Pile( + [ + self.loading_indicator() + ] + ) + self._over = urwid.Overlay( + self._pile, + self.top, + align='center', + valign='middle', + width=20, + height=10 + ) urwid.connect_signal(ask, 'change', self.onchange) - - self.mainloop = urwid.MainLoop(top, palette) + self.main = pile + self.mainloop = urwid.MainLoop(self._over, palette) + self.mainloop.set_alarm_in(.1, self.load_list) self.mainloop.run() + def load_list(self, _loop, _data): + body = [self.list_item(paper) for paper in self.mtm.retrieve()] + self.listcontent.clear() + self.listcontent.extend(body) + self.mainloop.widget = self.top + + def loading_indicator(self): + body_text = urwid.Text("Loading...", align='center') + body_filler = urwid.Filler(body_text, valign='middle') + body_padding = urwid.Padding( + body_filler, + left=1, + right=1 + ) + + return urwid.Frame(body_padding) + + def details_popup(self, paper): + header_text = urwid.Text(('banner', 'Paper details'), align='center') + header = urwid.AttrMap(header_text, 'banner') + + body_pile = urwid.Pile([ + urwid.Text(("papertitle", paper.title)), + urwid.Text(paper.authors), + urwid.Text(paper.journal), + urwid.Text(paper.doi), + urwid.Text(paper.abstract), + urwid.Text(" "), + urwid.Text(Bibtex().entry_from_doi(paper.doi)), + ]) + body_filler = urwid.Filler(body_pile, valign='top') + body_padding = urwid.Padding( + body_filler, + left=1, + right=1 + ) + body = urwid.LineBox(body_padding) + + # Footer + footer = urwid.Button('Okay', self.close_details) + footer = urwid.AttrWrap(footer, 'selectable', 'focus') + footer = urwid.GridFlow([footer], 8, 1, 1, 'center') + + # Layout + layout = urwid.Frame( + body, + header=header, + footer=footer, + focus_part='footer' + ) + + return layout + def list_item(self, paper, needle=""): """Create highlighted text entry.""" text_items = [] @@ -210,11 +290,16 @@ class Papersurfer: title = urwid.Text(text_items) discuss_button = PrettyButton("Open Discussion", - onclick=partial(self.h_open_discussion, - paper)) + onclick=partial(self.h_open_discussion, + paper)) doi_button = PrettyButton("Open DOI", onclick=partial(self.h_open_doi, paper)) - button_bar = urwid.Columns([discuss_button, doi_button]) + details_button = PrettyButton("Show details", + onclick=partial(self.h_show_details, + paper)) + + button_bar = urwid.Columns([ + discuss_button, doi_button, details_button]) pile = urwid.Pile([title, button_bar, urwid.Divider()]) return pile @@ -264,6 +349,10 @@ class Papersurfer: """Handle click/enter on doi button.""" self.open_doi(post) + def h_show_details(self, post, _): + """Handle click/enter on doi button.""" + self.show_details(post) + def open_discussion(self, post): """Open Mattermost post in browser.""" link = f"https://mattermost.cen.uni-hamburg.de/ifg/pl/{post.id}" @@ -273,6 +362,14 @@ class Papersurfer: """Open paper page in browser.""" subprocess.call(["xdg-open", Doi().get_doi_link(post.doi)]) + def show_details(self, post): + """Open paper page in browser.""" + paper = Doi().get_info(post.doi) + self.mainloop.widget = self.details_popup(paper) + + def close_details(self, _): + self.mainloop.widget = self.top + def parse_args(): """Parse command line arguments and config file.""" -- GitLab