PK!heprefs/__init__.pyPK!W^5heprefs/__main__.pyimport os import sys """ Entry point if executed as 'python heprefs """ if __name__ == '__main__': path = os.path.join(os.path.abspath(os.path.dirname(__file__)), os.pardir) sys.path.insert(0, path) from heprefs.heprefs import heprefs_main sys.exit(heprefs_main()) PK!$wheprefs/arxiv_article.pyfrom __future__ import absolute_import, division, print_function, unicode_literals import re import os import arxiv import feedparser from logging import getLogger logger = getLogger(__name__) class ArxivArticle(object): SERVER = 'https://arxiv.org' API = 'http://export.arxiv.org/api/query' OLD_FORMAT_DEFAULT = 'hep-ph' @classmethod def get_info(cls, arxiv_id): # Code from arxiv.py https://github.com/lukasschwab/arxiv.py query_url = '{}?id_list={}'.format(cls.API, arxiv_id) results = feedparser.parse(query_url) if results.get('status') != 200: raise Exception('Failed to fetch arXiv article information: HTTP Error ' + str(results.get('status', 'no status'))) if len(results['entries']) == 0 \ or 'id' not in results['entries'][0]: # because arXiv api returns one blank entry even if nothing found raise Exception('arXiv:{} not found'.format(arxiv_id)) elif len(results['entries']) > 1: warning_text = 'more than one entries are found, whose titles are' + os.linesep for i in results: title = i.get('title', dict()).get('title') or 'unknown ' + i.get('primary_report_number') warning_text += ' ' + title + os.linesep logger.warning(warning_text) result = results['entries'][0] arxiv.mod_query_result(result) return result @classmethod def shorten_author(cls, author): author = re.sub('collaboration', '', author, flags=re.IGNORECASE).strip() family_name = re.split(r'[ .]', author)[-1] return family_name.replace('-', '') @classmethod def try_to_construct(cls, key, force=False): try: obj = cls(key) except ValueError as e: if force: raise e return False return obj def __init__(self, arxiv_id): self._arxiv_id = None self.arxiv_id = arxiv_id self._info = None @property def arxiv_id(self): return self._arxiv_id @arxiv_id.setter def arxiv_id(self, i): new_style = re.match(r'^(\d{4})\.(\d{4,5})$', i) old_style = re.match(r'^([a-zA-Z.-]+/)?\d{7}$', i) if new_style: (first, second) = (new_style.group(1), new_style.group(2)) if int(first) >= 1500: self._arxiv_id = i if (len(second) == 5) else '{}.0{}'.format(first, second) elif len(second) == 4: self._arxiv_id = i else: raise ValueError('incorrect arXiv id') elif old_style: self._arxiv_id = i if old_style.group(1) else self.OLD_FORMAT_DEFAULT + '/' + i else: raise ValueError('incorrect arXiv id') @property def info(self): if not self._info: self._info = self.get_info(self.arxiv_id) return self._info def abs_url(self): return '{}/abs/{}'.format(self.SERVER, self.arxiv_id) def pdf_url(self): return '{}/pdf/{}'.format(self.SERVER, self.arxiv_id) def source_url(self): return '{}/e-print/{}'.format(self.SERVER, self.arxiv_id) def title(self): return re.sub(r'\s+', ' ', self.info['title']) def authors(self): return ', '.join(self.info['authors']) def first_author(self): return self.info['authors'][0] def authors_short(self): authors = [self.shorten_author(a) for a in self.info['authors']] if len(authors) > 5: authors = authors[0:4] + ['et al.'] return ', '.join(authors) def download_parameters(self): authors = self.authors_short().replace(', ', '-').replace('et al.', 'etal') filename = '{id}-{authors}.pdf'.format(id=self.arxiv_id, authors=authors) return self.info['pdf_url'], filename def debug(self): data = { 'abs_url': self.abs_url(), 'pdf_url': self.pdf_url(), 'title': self.title(), 'authors': self.authors(), 'first_author': self.first_author(), '(download_filename)': self.download_parameters()[1], } for k, v in data.items(): print('{}: {}'.format(k, v)) PK!nnheprefs/cds_article.pyfrom __future__ import (absolute_import, division, print_function, unicode_literals) import json import os import re from logging import getLogger from typing import Tuple # noqa: F401 import heprefs.invenio as invenio try: from urllib import quote_plus # type: ignore # noqa from urllib2 import urlopen, HTTPError # type: ignore # noqa except ImportError: from urllib.parse import quote_plus from urllib.request import urlopen logger = getLogger(__name__) class CDSArticle(object): API = 'https://cds.cern.ch/search' RECORD_PATH = 'http://cds.cern.ch/record/' ARXIV_SERVER = 'https://arxiv.org' DOI_SERVER = 'https://dx.doi.org' DATA_KEY = 'primary_report_number,recid,system_control_number,' + \ 'authors,corporate_name,title,abstract,publication_info,files' LIKELY_PATTERNS = [ r'^[A-Za-z-]+-\d+-\d+$', # "ATLAS-CONF-2018-001" "CMS PAS EXO-16-009" ] @classmethod def get_info(cls, query): query_url = '{}?p={}&of=recjson&ot={}&rg=3'.format(cls.API, quote_plus(query), cls.DATA_KEY) try: f = urlopen(query_url) # "with" does not work on python2 s = f.read() f.close() except HTTPError as e: raise Exception('Failed to fetch CDS information: ' + e.__str__()) try: results = json.loads(s.decode('utf-8')) except Exception as e: raise Exception('parse failed; query {} to CDS, but seems no result.: '.format(query) + e.__str__()) if (not isinstance(results, list)) or len(results) == 0: raise Exception('query {} to CDS gives no result: '.format(query)) if len(results) > 1: warning_text = 'more than one entries are found, whose titles are' + os.linesep for i in results: title = i.get('title', dict()).get('title') or 'unknown ' + i.get('primary_report_number') warning_text += ' ' + title + os.linesep logger.warning(warning_text) result = results[0] return result @classmethod def try_to_construct(cls, query, force=False): if not force: if not any(re.match(r, query) for r in cls.LIKELY_PATTERNS): return False return cls(query) def __init__(self, query): self.query = query self._info = None @property def info(self): if not self._info: self._info = self.get_info(self.query) return self._info def abs_url(self): # type: () -> str if 'doi' in self.info: return '{}/{}'.format(self.DOI_SERVER, self.info['doi']) arxiv_id = invenio.arxiv_id(self.info) if arxiv_id: return '{}/abs/{}'.format(self.ARXIV_SERVER, arxiv_id) if 'recid' in self.info: return self.RECORD_PATH + str(self.info['recid']) return '' def pdf_url(self): # type: () -> str arxiv_id = invenio.arxiv_id(self.info) if arxiv_id: return '{}/pdf/{}'.format(self.ARXIV_SERVER, arxiv_id) pdf_files = [i for i in self.info.get('files', []) if i['superformat'] == '.pdf'] if pdf_files: if len(pdf_files) > 1: print('Note: Fulltext PDF file is guessed by its size.') pdf_files.sort(key=lambda i: int(i.get('size', 0)), reverse=True) return pdf_files[0].get('url', '') return '' def title(self): # type: () -> str return re.sub(r'\s+', ' ', invenio.title(self.info)) def authors(self): # type: () -> str collaborations_list = invenio.collaborations(self.info) if collaborations_list: return ', '.join([c + ' (collaboration)' for c in collaborations_list]) else: return ', '.join(invenio.flatten_authors(self.info)) def authors_short(self): # type: () -> str return invenio.shorten_authors_text(self.info) def first_author(self): # type: () -> str a = self.authors() return a[0] if len(a) > 0 else '' def publication_info(self): # type: () -> str return invenio.publication_info_text(self.info) def download_parameters(self): # type: () -> Tuple[str, str] url = self.pdf_url() if not url: return '', '' arxiv_id = invenio.arxiv_id(self.info) primary_report_number = invenio.primary_report_number(self.info) file_title = \ arxiv_id if arxiv_id else \ primary_report_number if primary_report_number else \ self.info['doi'] if 'doi' in self.info else \ 'unknown' names = invenio.shorten_authors_text(self.info).replace(', ', '-').replace('et al.', 'etal') filename = '{title}-{names}.pdf'.format(title=file_title, names=names) return url, filename def debug(self): data = { 'abs_url': self.abs_url(), 'pdf_url': self.pdf_url(), 'title': self.title(), 'authors': self.authors(), 'first_author': self.first_author(), 'publication_info': self.publication_info(), '(download_filename)': self.download_parameters()[1], '(collaborations)': invenio.collaborations(self.info) } for k, v in data.items(): print('{}: {}'.format(k, v)) PK!\hheprefs/heprefs.py#!/usr/bin/env python from __future__ import absolute_import, division, print_function import click import os import sys import re import tarfile from logging import basicConfig, getLogger, DEBUG from collections import OrderedDict from .arxiv_article import ArxivArticle from .cds_article import CDSArticle from .inspire_article import InspireArticle __author__ = 'Sho Iwamoto / Misho' __version__ = '0.1.4' __license__ = 'MIT' basicConfig(level=DEBUG) logger = getLogger(__name__) types = OrderedDict([ ('arxiv', ArxivArticle), ('cds', CDSArticle), ('ins', InspireArticle), ]) def retrieve_hook(bar): return lambda b, c, t: bar.update(min(int(b * c / t * 100), 100)) def construct_article(key, type=None): if type in types.keys(): classes = [types[type]] force = True elif type is None: classes = list(types.values()) force = False else: raise Exception('invalid type specified') for c in classes: obj = c.try_to_construct(key, force=force) # type: ignore if obj: return obj click.echo('Reference for {} not found.'.format(key), err=True) sys.exit(1) @click.group(help='Handle the references for high-energy physics', context_settings=dict(help_option_names=['-h', '--help'])) @click.version_option(__version__, '-V', '--version') # @click.option('-v', '--verbose', is_flag=True, default=False, help="Show verbose output") def heprefs_main(**args): pass def heprefs_subcommand(help_msg): d1 = heprefs_main.command(short_help=help_msg, help=help_msg) d2 = click.option('-t', '--type', type=click.Choice(types.keys()), help='Specify article type (guessed if unspecified)') d3 = click.argument('key', required=True) def decorator(func): d1(d2(d3(func))) return decorator def with_article(func): def decorator(key, type, **kwargs): article = construct_article(key, type) func(article, **kwargs) decorator.__name__ = func.__name__ return decorator @heprefs_subcommand(help_msg='display title of the article') @with_article def title(article): click.echo(article.title()) @heprefs_subcommand(help_msg='display authors of the article') @with_article def authors(article): click.echo(article.authors()) @heprefs_subcommand(help_msg='display first author of the article') @with_article def first_author(article): click.echo(article.first_author()) @heprefs_subcommand(help_msg='Open abstract page with Browser') @with_article def abs(article): url = article.abs_url() click.echo('Opening {} ...'.format(url), err=True) click.launch(url) @heprefs_subcommand(help_msg='Open PDF with Browser') @with_article def pdf(article): url = article.pdf_url() click.echo('Opening {} ...'.format(url), err=True) click.launch(url) @heprefs_subcommand(help_msg='display short information of the article') @click.option('-s', '--shortauthors', is_flag=True, default=False, help='Shorten authors') @with_article def short_info(article, shortauthors): authors = article.authors_short() if shortauthors else article.authors() click.echo(u"{authors}\n{title}\n{abs_url}".format( authors=authors, title=article.title(), abs_url=article.abs_url(), )) @heprefs_subcommand(help_msg='Download PDF file and display the filename') @click.option('-o', '--open', is_flag=True, default=False, help='Open PDF file by viewer') @with_article def get(article, open): (pdf_url, filename) = article.download_parameters() filename = re.sub(r'[\\/*?:"<>|]', '', filename) click.echo('Downloading {} ...'.format(pdf_url), err=True) with click.progressbar(length=100, label=filename, file=sys.stderr) as bar: try: import urllib urllib.urlretrieve(pdf_url, filename, reporthook=retrieve_hook(bar)) # type: ignore except AttributeError: from urllib import request request.urlretrieve(pdf_url, filename, reporthook=retrieve_hook(bar)) # display the name so that piped to other scripts click.echo(filename) if open: click.launch(filename) @heprefs_subcommand(help_msg='Download arXiv source file and display the filename') @click.option('-u', '--untar', is_flag=True, default=False, help='Untar downloaded file') @with_article def source(article, untar): if isinstance(article, ArxivArticle): url = article.source_url() filename = '{}.tar.gz'.format(article.arxiv_id) dirname = '{}.source'.format(article.arxiv_id) else: click.echo('`source` is available only for arXiv articles.', err=True) sys.exit(1) filename = re.sub(r'[\\/*?:"<>|]', '', filename) click.echo('Downloading {} ...'.format(url), err=True) with click.progressbar(length=100, label=filename, file=sys.stderr) as bar: try: import urllib urllib.urlretrieve(url, filename, reporthook=retrieve_hook(bar)) # type: ignore except AttributeError: from urllib import request request.urlretrieve(url, filename, reporthook=retrieve_hook(bar)) if not os.path.isfile(filename): click.echo('Download failed and file {} is not created.'.format(filename), err=True) sys.exit(1) if untar: if tarfile.is_tarfile(filename): with tarfile.open(filename) as f: f.list() f.extractall(path=dirname) click.echo('\n{filename} successfully extracted to {dirname}.'.format( filename=filename, dirname=dirname), err=True) else: click.echo(""" {filename} has been downloaded but seems not a TAR file. Execute `gunzip {filename}` and inspect the file.""".format(filename=filename), err=True) # display the name so that piped to other scripts click.echo(filename) @heprefs_subcommand(help_msg='display information') @with_article def debug(article): article.debug() PK!nOheprefs/inspire_article.pyfrom __future__ import absolute_import, division, print_function, unicode_literals import re import os from logging import getLogger from typing import Tuple # noqa: F401 import json import heprefs.invenio as invenio try: from urllib import quote_plus # type: ignore # noqa from urllib2 import urlopen, HTTPError # type: ignore # noqa except ImportError: from urllib.parse import quote_plus from urllib.request import urlopen logger = getLogger(__name__) class InspireArticle(object): API = 'https://inspirehep.net/search' RECORD_PATH = 'http://inspirehep.net/record/' ARXIV_SERVER = 'https://arxiv.org' DOI_SERVER = 'https://dx.doi.org' DATA_KEY = 'primary_report_number,recid,system_control_number,' + \ 'authors,corporate_name,title,abstract,publication_info,files' LIKELY_PATTERNS = [ r'^(doi:)?10\.\d{4,}/.*$', # doi r'^find? .+', # old spires style ] @classmethod def get_info(cls, query): query_url = '{}?p={}&of=recjson&ot={}&rg=3'.format(cls.API, quote_plus(query), cls.DATA_KEY) try: f = urlopen(query_url) # "with" does not work on python2 s = f.read() f.close() except HTTPError as e: raise Exception('Failed to fetch inspireHEP information: ' + e.__str__()) try: results = json.loads(s.decode('utf-8')) except Exception as e: raise Exception('parse failed; query {} to inspireHEP gives no result?: '.format(query) + e.__str__()) if (not isinstance(results, list)) or len(results) == 0: raise Exception('query {} to inspireHEP gives no result: '.format(query)) if len(results) > 1: warning_text = 'more than one entries are found, whose titles are' + os.linesep for i in results: title = i.get('title', dict()).get('title') or 'unknown ' + i.get('primary_report_number') warning_text += ' ' + title + os.linesep logger.warning(warning_text) result = results[0] return result @classmethod def try_to_construct(cls, query, force=False): if not force: if not any(re.match(r, query) for r in cls.LIKELY_PATTERNS): return False return cls(query) def __init__(self, query): self.query = query self._info = None @property def info(self): if not self._info: self._info = self.get_info(self.query) return self._info def abs_url(self): # type: () -> str if 'doi' in self.info: return '{}/{}'.format(self.DOI_SERVER, self.info['doi']) arxiv_id = invenio.arxiv_id(self.info) if arxiv_id: return '{}/abs/{}'.format(self.ARXIV_SERVER, arxiv_id) if 'recid' in self.info: return self.RECORD_PATH + str(self.info['recid']) return '' def pdf_url(self): # type: () -> str scoap3_url = [i['url'] for i in self.info.get('files', []) if i['full_name'] == 'scoap3-fulltext.pdf'] if scoap3_url: return scoap3_url[0] arxiv_id = invenio.arxiv_id(self.info) if arxiv_id: return '{}/pdf/{}'.format(self.ARXIV_SERVER, arxiv_id) pdf_files = [i for i in self.info.get('files', []) if i['superformat'] == '.pdf'] if pdf_files: if len(pdf_files) > 1: logger.warning('Fulltext PDF file is guessed by its size.') pdf_files.sort(key=lambda i: int(i.get('size', 0)), reverse=True) return pdf_files[0].get('url', '') return '' def title(self): # type: () -> str return re.sub(r'\s+', ' ', invenio.title(self.info)) def authors(self): # type: () -> str collaborations_list = invenio.collaborations(self.info) if collaborations_list: return ', '.join([c + ' (collaboration)' for c in collaborations_list]) else: return ', '.join(invenio.flatten_authors(self.info)) def authors_short(self): # type: () -> str return invenio.shorten_authors_text(self.info) def first_author(self): # type: () -> str a = self.authors() return a[0] if len(a) > 0 else '' def texkey(self): # type: () -> str scn = self.info.get('system_control_number') if scn: if isinstance(scn, dict): scn = [scn] texkeys = [i['value'] for i in scn if i['institute'] == 'INSPIRETeX'] if len(texkeys) > 1: logger.warning('multiple TeX-keys are found? : ' + ' & '.join(texkeys)) return texkeys[0] if texkeys else '' return '' def publication_info(self): # type: () -> str return invenio.publication_info_text(self.info) def download_parameters(self): # type: () -> Tuple[str, str] url = self.pdf_url() if not url: return '', '' arxiv_id = invenio.arxiv_id(self.info) primary_report_number = invenio.primary_report_number(self.info) file_title = \ arxiv_id if arxiv_id else \ primary_report_number if primary_report_number else \ self.info['doi'] if 'doi' in self.info else \ 'unknown' names = invenio.shorten_authors_text(self.info).replace(', ', '-').replace('et al.', 'etal') filename = '{title}-{names}.pdf'.format(title=file_title, names=names) return url, filename def debug(self): data = { 'abs_url': self.abs_url(), 'pdf_url': self.pdf_url(), 'title': self.title(), 'authors': self.authors(), 'first_author': self.first_author(), 'texkey': self.texkey(), 'publication_info': self.publication_info(), '(download_filename)': self.download_parameters()[1], '(collaborations)': invenio.collaborations(self.info) } for k, v in data.items(): logger.debug('{}: {}'.format(k, v)) PK!l-heprefs/invenio.pyfrom logging import getLogger from typing import List, Mapping # noqa: F401 import re import sys """ Utilities to handle JSON output from INVENIO system (inspireHEP/CDS). """ if sys.version_info[0] < 3: str = basestring # noqa: F821 logger = getLogger(__name__) def normalize_authors(json): # type: (dict) -> list authors = json.get('authors') or list() if not isinstance(authors, list): authors = [authors] authors_normal = list() # type: List[Mapping[str, str]] collaborations_mode = False for i in authors: if i is None or not i.get('full_name'): # 'full_name' might be None. continue if re.search('on behalf', i['full_name'], flags=re.IGNORECASE): break # anything after 'on behalf of' is ignored. if re.search('collaborations ', i['full_name'], flags=re.IGNORECASE): # if no personal name is given, list collaboration names only if len(authors_normal) == 0: collaborations_mode = True if collaborations_mode: authors_normal.append(i) else: if not collaborations_mode: authors_normal.append(i) return authors_normal def flatten_author(a): # type: (dict) -> str if a.get('first_name') and a.get('last_name'): return u'{first_name} {last_name}'.format(**a) elif a.get('full_name'): return a['full_name'] or '' else: logger.warning(u'how to handle the author name?: {}'.format(a.__str__())) return '' def flatten_authors(json): # type: (dict) -> list return [flatten_author(a) for a in normalize_authors(json)] def shorten_author(a): # type: (dict) -> str if a.get('last_name'): return a['last_name'].replace('-', '').replace(' ', '') elif a.get('full_name'): tmp = re.sub('on behalf of.*', '', a['full_name'], flags=re.IGNORECASE) return re.split(r', ', tmp)[0].replace('-', '') else: logger.warning(u'how to handle the author name?: {}'.format(a.__str__())) return '' def shorten_authors(json): # type: (dict) -> list return [shorten_author(a) for a in normalize_authors(json)] def collaborations(json): # type: (dict) -> list corporate_name = json.get('corporate_name') if not corporate_name: return list() collaborations_list = list() for i in corporate_name: for k, v in i.items(): if k == 'collaboration' or k == 'name': v = re.sub('the', '', v, flags=re.IGNORECASE) v = re.sub('collaboration', '', v, flags=re.IGNORECASE) collaborations_list.append(v.strip()) # remove duplicated entries (case insensitive) c_dict = dict() for c in collaborations_list: c_dict[c.lower()] = c return list(c_dict.values()) def shorten_authors_text(json): # type: (dict) -> str collaborations_list = collaborations(json) if collaborations_list: return ', '.join(collaborations_list) authors_short = shorten_authors(json) if len(authors_short) > 5: authors_short.append('et al.') return ', '.join(authors_short) def publication_info_text(json): # type: (dict) -> str publication_info = json.get('publication_info') if publication_info: if isinstance(publication_info, list): publication_info = publication_info[0] logger.warning(u'More than one publication_info is found; first one is used.') if not isinstance(publication_info, dict): raise ValueError('publication_list is not a JSON hash.') items = [publication_info.get(key, '') for key in ['title', 'volume', 'year', 'pagination']] if items[2]: items[2] = '(' + items[2] + ')' items = [i for i in items if i] return ' '.join(items) return '' def title(json): # type: (dict) -> str if 'title' in json and 'title' in json['title']: return json['title']['title'] else: return '' def arxiv_id(json): # type: (dict) -> str if 'primary_report_number' not in json: return '' report_numbers = json['primary_report_number'] or [] if isinstance(report_numbers, str): report_numbers = list(report_numbers) arxiv_ids = list() for i in report_numbers: arxiv_pattern = re.match(r'^arXiv:(.*)$', i or '') if arxiv_pattern: arxiv_ids.append(arxiv_pattern.group(1)) if len(arxiv_ids) > 1: logger.warning('multiple arxiv IDs are found? : ' + ' & '.join(arxiv_ids)) return arxiv_ids[0] if arxiv_ids else '' def primary_report_number(json): # type: (dict) -> str content = '' if json.get('primary_report_number') is None: pass elif isinstance(json['primary_report_number'], str): content = json['primary_report_number'] elif isinstance(json['primary_report_number'], list): if arxiv_id(json): content = arxiv_id(json) else: content = json['primary_report_number'][0] else: raise ValueError('primary_report_number is in unknown format: ' + json['primary_report_number'].__str__()) content = re.sub(r'^arXiv:', '', content, re.IGNORECASE) return content PK!H;O*8(heprefs-0.1.4.dist-info/entry_points.txtN+I/N.,()H-(JM+zP J&fqqPK! 44heprefs-0.1.4.dist-info/LICENSEMIT License Copyright (c) 2017 Sho Iwamoto / Misho 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. PK!HWYheprefs-0.1.4.dist-info/WHEEL A н#Z;/"b&F]xzwC;dhfCSTֻ0*Ri.4œh6-]{H, JPK!HSQ2 heprefs-0.1.4.dist-info/METADATAXms۸_=痈%Y-w瑒;8q&J:\1DB^hj3)? gz5 fxLk.MxrV[NA,[teF"᠎R2h"'</(,Axd9?˘)NU^ =ɇc&֗Z܉' įn97ȬKtʰ{.4Ø)ͳDF+0שѳ{'Ţ\4w" Jۥ-ݤi&<4sM&c8X2C_(bV]Ύ]]9;sYDZ/bJG_Uv{ID"l:_:byL!A'"5׮YZ?N+ڃ3}Koy*2[Ծ*\H5=hh"E9h>uP)Իuȕs8~&' GKs1b%(T6u:g QoO;g{X;rMKqNC1!63`}ʕ6XEsNӧFGqx?ԏ$ )#-Kz9> $[ߧDiE6H%#P*Rdw +$dMGC0LLuuh>*S;E:_(t5#Fi:EJ״Fש Vy%\ib }$|id009>ųo3 Ҕf8S41&楼 ؚro5Y>qDF:c&3p)!hmo^\gg1 ՓEht$^Dxݿ~6FKދ/Fa3bSOoZ^ܿ#8*EśsF-F/Hh$2>"YXEǿh7f%a"cjи+5 ېL( d8gFr*f J"isI^`X*Vt3%঵pCk*տ)lvy::^)'V23V~^Wcf򱷐j U6^ PiɾpRFBQp0Wa`AyYL'ca1XM0ɉi[@mH5\0K41a^ Ul ru9?OxD{Z"{M9oV{l)i pӧoT3d`Nޓ&xkX Y4ӓ=zY$]V<\~?WQL LvcMo|=llAM%ps$'L3aRd\PrgH?`}̘Yw,8n0d̡<}nq'|D.2~^Yp?p~w˳" (oq/7xYP_ډX>`2?T~x^>J"B^upkh# mQ/SysR^qm4^nP1o?ehhPK!H/F(>heprefs-0.1.4.dist-info/RECORDu;@|~ 8 B 4("(IFA v\jnSnAێ'!%+BFX/icԏy됗-D]kYV?|OK=6Z¾2ܴ8HO*nP_h OcV m8=bZ 1?4lgVJnbLm WyZ]Njml`͒눿얙Z_yAGg omک`ps2GDž-^$*"B^(+ʗg$fNGuaHIdyII۔lGm7$L)Ljq|?|^w -99ŽSu%U&$Tl.,V{Ô8L޻ c[UO>lyTqRcw.'jH)X@ӵ@{n %T1{_jy GKJI|_v97PK!heprefs/__init__.pyPK!W^51heprefs/__main__.pyPK!$w~heprefs/arxiv_article.pyPK!nnheprefs/cds_article.pyPK!\h%(heprefs/heprefs.pyPK!nO?heprefs/inspire_article.pyPK!l-HXheprefs/invenio.pyPK!H;O*8(Imheprefs-0.1.4.dist-info/entry_points.txtPK! 44mheprefs-0.1.4.dist-info/LICENSEPK!HWY*rheprefs-0.1.4.dist-info/WHEELPK!HSQ2 rheprefs-0.1.4.dist-info/METADATAPK!H/F(>Kzheprefs-0.1.4.dist-info/RECORDPK \|