Further refactor
This commit is contained in:
parent
691e752081
commit
d6b75cacdd
@ -1,15 +1,48 @@
|
|||||||
|
import libbmc
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
from backend import config
|
||||||
from backend import tools
|
from backend import tools
|
||||||
|
from libbmc import bibtex
|
||||||
from libbmc import fetcher
|
from libbmc import fetcher
|
||||||
|
from libbmc.repositories import arxiv
|
||||||
|
from libbmc.papers import identifiers
|
||||||
|
from libbmc.papers import tearpages
|
||||||
|
|
||||||
|
|
||||||
def download(url, filetype, manual, autoconfirm, tag):
|
def get_entry_from_index(item, file_or_id=None):
|
||||||
|
"""
|
||||||
|
Fetch an entry from the global index.
|
||||||
|
|
||||||
|
:param item: An identifier or filename.
|
||||||
|
:param file_or_id: Whether it is a file or an entry identifier. If \
|
||||||
|
``None``, will try to match both.
|
||||||
|
:returns: TODO.
|
||||||
|
"""
|
||||||
|
entry = None
|
||||||
|
# If explictly an identifier
|
||||||
|
if file_or_id == "id":
|
||||||
|
entry = bibtex.get_entry(config.get("index"), item)
|
||||||
|
# If explicitely a filename
|
||||||
|
elif file_or_id == "file":
|
||||||
|
entry = bibtex.get_entry_by_filter(config.get("index"),
|
||||||
|
lambda x: x.file == item) # TODO
|
||||||
|
# Else, get by id or file
|
||||||
|
else:
|
||||||
|
entry = bibtex.get_entry_by_filter(config.get("index"),
|
||||||
|
lambda x: (
|
||||||
|
x.id == item or
|
||||||
|
x.file == item)) # TODO
|
||||||
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
def download(url, manual, autoconfirm, tag):
|
||||||
"""
|
"""
|
||||||
Download a given URL and add it to the library.
|
Download a given URL and add it to the library.
|
||||||
|
|
||||||
:param url: URL to download.
|
:param url: URL to download.
|
||||||
:param filetype: paper / book / ``None``.
|
|
||||||
:param manual: Whether BibTeX should be fetched automatically.
|
:param manual: Whether BibTeX should be fetched automatically.
|
||||||
:param autoconfirm: Whether import should be made silent or not.
|
:param autoconfirm: Whether import should be made silent or not.
|
||||||
:param tag: A tag for this file.
|
:param tag: A tag for this file.
|
||||||
@ -30,7 +63,7 @@ def download(url, filetype, manual, autoconfirm, tag):
|
|||||||
fh.write(dl)
|
fh.write(dl)
|
||||||
|
|
||||||
# And add it as a normal paper from now on
|
# And add it as a normal paper from now on
|
||||||
new_name = import_file(tmp.name, filetype, manual,
|
new_name = import_file(tmp.name, manual,
|
||||||
autoconfirm, tag)
|
autoconfirm, tag)
|
||||||
if new_name is None:
|
if new_name is None:
|
||||||
return None
|
return None
|
||||||
@ -43,17 +76,200 @@ def download(url, filetype, manual, autoconfirm, tag):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def import_file(src, filetype, manual, autoconfirm, tag, rename=True):
|
def import_file(src, manual=False, autoconfirm=False,
|
||||||
|
tag='', rename=True):
|
||||||
"""
|
"""
|
||||||
Add a file to the library.
|
Add a file to the library.
|
||||||
|
|
||||||
:param src: The path of the file to import.
|
:param src: The path of the file to import.
|
||||||
:param filetype: paper / book / ``None``.
|
:param manual: Whether BibTeX should be fetched automatically. \
|
||||||
:param manual: Whether BibTeX should be fetched automatically.
|
Default to ``False``.
|
||||||
:param autoconfirm: Whether import should be made silent or not.
|
:param autoconfirm: Whether import should be made silent or not. \
|
||||||
:param tag: A tag for this file.
|
Default to ``False``.
|
||||||
:param rename: TODO
|
:param tag: A tag for this file. \
|
||||||
|
Default to no tag.
|
||||||
|
:param rename: Whether or not the file should be renamed according to the \
|
||||||
|
mask in the config.
|
||||||
:returns: The name of the imported file, or ``None`` in case of error.
|
:returns: The name of the imported file, or ``None`` in case of error.
|
||||||
"""
|
"""
|
||||||
|
if not manual:
|
||||||
|
type, identifier = identifiers.find_identifiers(src)
|
||||||
|
|
||||||
|
if type is None:
|
||||||
|
tools.warning("Could not find an identifier for %s. \
|
||||||
|
Switching to manual entry." % (src))
|
||||||
|
# Fetch available identifiers types from libbmc
|
||||||
|
# Append "manual" for manual entry of BibTeX and "skip" to skip the
|
||||||
|
# file.
|
||||||
|
available_types_list = (libbmc.__valid_identifiers__ +
|
||||||
|
["manual", "skip"])
|
||||||
|
available_types = " / ".joint(available_types_list)
|
||||||
|
# Query for the type to use
|
||||||
|
while type not in available_types_list:
|
||||||
|
type = input("%s? " % (available_types)).lower()
|
||||||
|
|
||||||
|
if type == "skip":
|
||||||
|
# If "skip" is chosen, skip this file
|
||||||
|
return None
|
||||||
|
elif type == "manual":
|
||||||
|
identifier = None
|
||||||
|
else:
|
||||||
|
# Query for the identifier if required
|
||||||
|
identifier = input("Value? ")
|
||||||
|
else:
|
||||||
|
print("%s found for %s: %s." % (type, src, identifier))
|
||||||
|
|
||||||
|
# Fetch BibTeX automatically if we have an identifier
|
||||||
|
bibtex = None
|
||||||
|
if identifier is not None:
|
||||||
|
# If an identifier was provided, try to automatically fetch the bibtex
|
||||||
|
bibtex = identifiers.get_bibtex((type, identifier))
|
||||||
|
|
||||||
|
# TODO: Check bibtex
|
||||||
|
|
||||||
|
# Handle tag
|
||||||
|
if not autoconfirm:
|
||||||
|
# If autoconfirm is not enabled, query for a tag
|
||||||
|
user_tag = input("Tag for this paper [%s]? " % tag)
|
||||||
|
if user_tag != "":
|
||||||
|
tag = user_tag
|
||||||
|
bibtex["tag"] = tag
|
||||||
|
|
||||||
|
# TODO: Handle renaming
|
||||||
|
new_name = src
|
||||||
|
if rename:
|
||||||
|
pass
|
||||||
|
|
||||||
|
bibtex['file'] = os.path.abspath(new_name)
|
||||||
|
|
||||||
|
# Tear some pages if needed
|
||||||
|
should_tear_pages = True
|
||||||
|
if not autoconfirm:
|
||||||
|
# Ask for confirmation
|
||||||
|
pages_to_tear = tearpages.tearpage_needed(bibtex)
|
||||||
|
user_tear_pages = input("Found some pages to tear: %s. \
|
||||||
|
Confirm? [Y/n]" % (pages_to_tear)).lower()
|
||||||
|
if user_tear_pages == "n":
|
||||||
|
should_tear_pages = False
|
||||||
|
if should_tear_pages:
|
||||||
|
tearpages.tearpage(new_name, bibtex=bibtex)
|
||||||
|
|
||||||
|
# TODO: Append to global bibtex index
|
||||||
|
|
||||||
|
return new_name
|
||||||
|
|
||||||
|
|
||||||
|
def delete(item, keep=False, file_or_id=None):
|
||||||
|
"""
|
||||||
|
Delete an entry in the main BibTeX file, and the associated documents.
|
||||||
|
|
||||||
|
:param item: An entry or filename to delete from the database.
|
||||||
|
:param keep: Whether or not the document should be kept on the disk. \
|
||||||
|
If True, will simply delete the entry in the main BibTeX index.
|
||||||
|
:param file_or_id: Whether it is a file or an entry identifier. If \
|
||||||
|
``None``, will try to match both.
|
||||||
|
:returns: Nothing.
|
||||||
|
"""
|
||||||
|
entry = get_entry_from_index(item, file_or_id)
|
||||||
|
|
||||||
|
# Delete the entry from the bibtex index
|
||||||
|
bibtex.delete(config.get("index"), entry.id) # TODO
|
||||||
|
|
||||||
|
# If file should not be kept
|
||||||
|
if not keep:
|
||||||
|
# Delete it
|
||||||
|
os.unlink(entry.file) # TODO
|
||||||
|
|
||||||
|
|
||||||
|
def edit(item, file_or_id):
|
||||||
|
"""
|
||||||
|
Edit an entry in the main BibTeX file.
|
||||||
|
|
||||||
|
:param item: An entry or filename to edit in the database.
|
||||||
|
:param file_or_id: Whether it is a file or an entry identifier. If \
|
||||||
|
``None``, will try to match both.
|
||||||
|
:returns: Nothing.
|
||||||
|
"""
|
||||||
# TODO
|
# TODO
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def list_entries():
|
||||||
|
"""
|
||||||
|
List all the available entries and their associated files.
|
||||||
|
|
||||||
|
:returns: A dict with entry identifiers as keys and associated files as \
|
||||||
|
values.
|
||||||
|
"""
|
||||||
|
# Get the list of entries from the BibTeX index
|
||||||
|
entries_list = bibtex.get(config.get("index"))
|
||||||
|
return {entry.id: entry.file for entry in entries_list} # TODO
|
||||||
|
|
||||||
|
|
||||||
|
def open(id):
|
||||||
|
"""
|
||||||
|
Open the file associated with the provided entry identifier.
|
||||||
|
|
||||||
|
:param id: An entry identifier in the main BibTeX file.
|
||||||
|
:returns: ``False`` if an error occured. ``True`` otherwise.
|
||||||
|
"""
|
||||||
|
# Fetch the entry from the BibTeX index
|
||||||
|
entry = bibtex.get_entry(config.get("index"), id)
|
||||||
|
if entry is None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Run xdg-open on the associated file to open it
|
||||||
|
subprocess.Popen(['xdg-open', entry.filename]) # TODO
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def export(item, file_or_id=None):
|
||||||
|
"""
|
||||||
|
Export the BibTeX entries associated to some items.
|
||||||
|
|
||||||
|
:param item: An entry or filename to export as BibTeX.
|
||||||
|
:param file_or_id: Whether it is a file or an entry identifier. If \
|
||||||
|
``None``, will try to match both.
|
||||||
|
:returns: TODO.
|
||||||
|
"""
|
||||||
|
# Fetch the entry from the BibTeX index
|
||||||
|
entry = get_entry_from_index(item, file_or_id)
|
||||||
|
if entry is not None:
|
||||||
|
return bibtex.dict2BibTeX(entry) # TODO
|
||||||
|
|
||||||
|
|
||||||
|
def resync():
|
||||||
|
"""
|
||||||
|
Compute the diff between the main BibTeX index and the files on the disk,
|
||||||
|
and try to resync them.
|
||||||
|
|
||||||
|
:returns: Nothing.
|
||||||
|
"""
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def update(item, file_or_id=None):
|
||||||
|
"""
|
||||||
|
Update an entry, trying to fetch a more recent version (on arXiv for \
|
||||||
|
instance.)
|
||||||
|
|
||||||
|
:param item: An entry or filename to fetch update from.
|
||||||
|
:param file_or_id: Whether it is a file or an entry identifier. If \
|
||||||
|
``None``, will try to match both.
|
||||||
|
:returns: TODO.
|
||||||
|
"""
|
||||||
|
entry = get_entry_from_index(item, file_or_id)
|
||||||
|
# Fetch latest version
|
||||||
|
latest_version = arxiv.get_latest_version(entry.eprint) # TODO
|
||||||
|
if latest_version != entry.eprint: # TODO
|
||||||
|
print("New version found for %s: %s" % (entry, latest_version))
|
||||||
|
confirm = input("Download it? [Y/n] ")
|
||||||
|
if confirm.lower() == 'n':
|
||||||
|
return
|
||||||
|
|
||||||
|
# Download the updated version
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
# Delete previous version if needed
|
||||||
|
# TODO
|
||||||
|
@ -55,6 +55,7 @@ class Config():
|
|||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.set("folder", os.path.expanduser("~/Papers/"))
|
self.set("folder", os.path.expanduser("~/Papers/"))
|
||||||
|
self.set("index", os.path.expanduser("~/Papers/index.bib"))
|
||||||
self.set("proxies", [''])
|
self.set("proxies", [''])
|
||||||
self.set("format_articles", "%f_%l-%j-%Y%v")
|
self.set("format_articles", "%f_%l-%j-%Y%v")
|
||||||
self.set("format_books", "%a-%t")
|
self.set("format_books", "%a-%t")
|
||||||
|
64
bmc.py
64
bmc.py
@ -15,6 +15,14 @@ from backend.config import Config
|
|||||||
EDITOR = os.environ.get("EDITOR")
|
EDITOR = os.environ.get("EDITOR")
|
||||||
|
|
||||||
|
|
||||||
|
def file_or_id_from_args(args):
|
||||||
|
"""
|
||||||
|
Helper function to parse provided args to check if the argument is a \
|
||||||
|
file or an identifier.
|
||||||
|
"""
|
||||||
|
return "id" if args.id else "file" if args.file else None
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
"""
|
"""
|
||||||
Build a parser and parse arguments of command line.
|
Build a parser and parse arguments of command line.
|
||||||
@ -25,13 +33,10 @@ def parse_args():
|
|||||||
description="A bibliography management tool.")
|
description="A bibliography management tool.")
|
||||||
parser.add_argument("-c", "--config", default=None,
|
parser.add_argument("-c", "--config", default=None,
|
||||||
help="path to a custom config dir.")
|
help="path to a custom config dir.")
|
||||||
subparsers = parser.add_subparsers(help="sub-command help", dest='parser')
|
subparsers = parser.add_subparsers(help="sub-command help", dest='command')
|
||||||
subparsers.required = True # Fix for Python 3.3.5
|
subparsers.required = True # Fix for Python 3.3.5
|
||||||
|
|
||||||
parser_download = subparsers.add_parser('download', help="download help")
|
parser_download = subparsers.add_parser('download', help="download help")
|
||||||
parser_download.add_argument('-t', '--type', default=None,
|
|
||||||
choices=['paper', 'book'],
|
|
||||||
help="type of the file to download")
|
|
||||||
parser_download.add_argument('-m', '--manual', default=False,
|
parser_download.add_argument('-m', '--manual', default=False,
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="disable auto-download of bibtex")
|
help="disable auto-download of bibtex")
|
||||||
@ -46,9 +51,6 @@ def parse_args():
|
|||||||
parser_download.set_defaults(func='download')
|
parser_download.set_defaults(func='download')
|
||||||
|
|
||||||
parser_import = subparsers.add_parser('import', help="import help")
|
parser_import = subparsers.add_parser('import', help="import help")
|
||||||
parser_import.add_argument('-t', '--type', default=None,
|
|
||||||
choices=['paper', 'book'],
|
|
||||||
help="type of the file to import")
|
|
||||||
parser_import.add_argument('-m', '--manual', default=False,
|
parser_import.add_argument('-m', '--manual', default=False,
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help="disable auto-download of bibtex")
|
help="disable auto-download of bibtex")
|
||||||
@ -100,16 +102,30 @@ def parse_args():
|
|||||||
parser_open.set_defaults(func='open')
|
parser_open.set_defaults(func='open')
|
||||||
|
|
||||||
parser_export = subparsers.add_parser('export', help="export help")
|
parser_export = subparsers.add_parser('export', help="export help")
|
||||||
parser_export.add_argument('ids', metavar='id', nargs='+',
|
parser_export.add_argument('entries', metavar='entry', nargs='+',
|
||||||
help="an identifier")
|
help="a filename or an identifier")
|
||||||
|
parser_export.add_argument('--skip', nargs='+',
|
||||||
|
help="path to files to skip", default=[])
|
||||||
|
group = parser_export.add_mutually_exclusive_group()
|
||||||
|
group.add_argument('--id', action="store_true", default=False,
|
||||||
|
help="id based deletion")
|
||||||
|
group.add_argument('--file', action="store_true", default=False,
|
||||||
|
help="file based deletion")
|
||||||
parser_export.set_defaults(func='export')
|
parser_export.set_defaults(func='export')
|
||||||
|
|
||||||
parser_resync = subparsers.add_parser('resync', help="resync help")
|
parser_resync = subparsers.add_parser('resync', help="resync help")
|
||||||
parser_resync.set_defaults(func='resync')
|
parser_resync.set_defaults(func='resync')
|
||||||
|
|
||||||
parser_update = subparsers.add_parser('update', help="update help")
|
parser_update = subparsers.add_parser('update', help="update help")
|
||||||
parser_update.add_argument('--entries', metavar='entry', nargs='+',
|
parser_update.add_argument('entries', metavar='entry', nargs='+',
|
||||||
help="a filename or an identifier")
|
help="a filename or an identifier")
|
||||||
|
parser_update.add_argument('--skip', nargs='+',
|
||||||
|
help="path to files to skip", default=[])
|
||||||
|
group = parser_update.add_mutually_exclusive_group()
|
||||||
|
group.add_argument('--id', action="store_true", default=False,
|
||||||
|
help="id based deletion")
|
||||||
|
group.add_argument('--file', action="store_true", default=False,
|
||||||
|
help="file based deletion")
|
||||||
parser_update.set_defaults(func='update')
|
parser_update.set_defaults(func='update')
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
@ -133,7 +149,7 @@ def main():
|
|||||||
skipped = []
|
skipped = []
|
||||||
for url in args.url:
|
for url in args.url:
|
||||||
# Try to download the URL
|
# Try to download the URL
|
||||||
new_name = commands.download(url, args.type, args.manual, args.y,
|
new_name = commands.download(url, args.manual, args.y,
|
||||||
args.tag)
|
args.tag)
|
||||||
if new_name is not None:
|
if new_name is not None:
|
||||||
print("%s successfully imported as %s." % (url, new_name))
|
print("%s successfully imported as %s." % (url, new_name))
|
||||||
@ -154,7 +170,7 @@ def main():
|
|||||||
files_to_process = list(set(args.file) - set(args.skip))
|
files_to_process = list(set(args.file) - set(args.skip))
|
||||||
for filename in files_to_process:
|
for filename in files_to_process:
|
||||||
# Try to import the file
|
# Try to import the file
|
||||||
new_name = commands.import_file(filename, args.type,
|
new_name = commands.import_file(filename,
|
||||||
args.manual, args.y,
|
args.manual, args.y,
|
||||||
args.tag, not args.inplace)
|
args.tag, not args.inplace)
|
||||||
if new_name is not None:
|
if new_name is not None:
|
||||||
@ -184,7 +200,7 @@ def main():
|
|||||||
|
|
||||||
# Try to delete the item
|
# Try to delete the item
|
||||||
if confirm.lower() == 'y':
|
if confirm.lower() == 'y':
|
||||||
file_or_id = "id" if args.id else "file" if args.file else None
|
file_or_id = file_or_id_from_args(args)
|
||||||
commands.delete(item, args.keep, file_or_id)
|
commands.delete(item, args.keep, file_or_id)
|
||||||
print("%s successfully deleted." % (item,))
|
print("%s successfully deleted." % (item,))
|
||||||
else:
|
else:
|
||||||
@ -200,13 +216,13 @@ def main():
|
|||||||
# Handle exclusions
|
# Handle exclusions
|
||||||
items_to_process = list(set(args.entries) - set(args.skip))
|
items_to_process = list(set(args.entries) - set(args.skip))
|
||||||
for item in items_to_process:
|
for item in items_to_process:
|
||||||
file_or_id = "id" if args.id else "file" if args.file else None
|
file_or_id = file_or_id_from_args(args)
|
||||||
commands.edit(item, file_or_id)
|
commands.edit(item, file_or_id)
|
||||||
|
|
||||||
# List command
|
# List command
|
||||||
elif args.func == 'list':
|
elif args.func == 'list':
|
||||||
# List all available items
|
# List all available items
|
||||||
for id, file in commands.list().items():
|
for id, file in commands.list_entries().items():
|
||||||
# And print them as "identifier: file"
|
# And print them as "identifier: file"
|
||||||
print("%s: %s" % (id, file))
|
print("%s: %s" % (id, file))
|
||||||
|
|
||||||
@ -214,14 +230,18 @@ def main():
|
|||||||
elif args.func == 'open':
|
elif args.func == 'open':
|
||||||
# Open each entry
|
# Open each entry
|
||||||
for id in args.ids:
|
for id in args.ids:
|
||||||
if commands.open(id) is None:
|
if not commands.open(id):
|
||||||
# And warn the user about missing files or errors
|
# And warn the user about missing files or errors
|
||||||
tools.warning("Unable to open file associated with ident %s." %
|
tools.warning("Unable to open file associated with ident %s." %
|
||||||
(id,))
|
(id,))
|
||||||
|
|
||||||
# Export command
|
# Export command
|
||||||
elif args.func == 'export':
|
elif args.func == 'export':
|
||||||
print(commands.export(args.ids))
|
# Handle exclusions
|
||||||
|
items_to_process = list(set(args.entries) - set(args.skip))
|
||||||
|
for item in items_to_process:
|
||||||
|
file_or_id = file_or_id_from_args(args)
|
||||||
|
print(commands.export(item, file_or_id))
|
||||||
|
|
||||||
# Resync command
|
# Resync command
|
||||||
elif args.func == 'resync':
|
elif args.func == 'resync':
|
||||||
@ -231,7 +251,15 @@ def main():
|
|||||||
|
|
||||||
# Update command
|
# Update command
|
||||||
elif args.func == 'update':
|
elif args.func == 'update':
|
||||||
commands.update(args.entries)
|
# Handle exclusions
|
||||||
|
items_to_process = list(set(args.entries) - set(args.skip))
|
||||||
|
for item in items_to_process:
|
||||||
|
file_or_id = file_or_id_from_args(args)
|
||||||
|
updates = commands.update(args.entries)
|
||||||
|
# TODO \/
|
||||||
|
print("%d new versions of papers were found:" % (len(updates)))
|
||||||
|
for item in updates:
|
||||||
|
print(item)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
Reference in New Issue
Block a user