Further refactor
This commit is contained in:
parent
691e752081
commit
d6b75cacdd
@ -1,15 +1,48 @@
|
||||
import libbmc
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from backend import config
|
||||
from backend import tools
|
||||
from libbmc import bibtex
|
||||
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.
|
||||
|
||||
:param url: URL to download.
|
||||
:param filetype: paper / book / ``None``.
|
||||
:param manual: Whether BibTeX should be fetched automatically.
|
||||
:param autoconfirm: Whether import should be made silent or not.
|
||||
:param tag: A tag for this file.
|
||||
@ -30,7 +63,7 @@ def download(url, filetype, manual, autoconfirm, tag):
|
||||
fh.write(dl)
|
||||
|
||||
# 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)
|
||||
if new_name is None:
|
||||
return None
|
||||
@ -43,17 +76,200 @@ def download(url, filetype, manual, autoconfirm, tag):
|
||||
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.
|
||||
|
||||
:param src: The path of the file to import.
|
||||
:param filetype: paper / book / ``None``.
|
||||
:param manual: Whether BibTeX should be fetched automatically.
|
||||
:param autoconfirm: Whether import should be made silent or not.
|
||||
:param tag: A tag for this file.
|
||||
:param rename: TODO
|
||||
:param manual: Whether BibTeX should be fetched automatically. \
|
||||
Default to ``False``.
|
||||
:param autoconfirm: Whether import should be made silent or not. \
|
||||
Default to ``False``.
|
||||
: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.
|
||||
"""
|
||||
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
|
||||
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):
|
||||
self.set("folder", os.path.expanduser("~/Papers/"))
|
||||
self.set("index", os.path.expanduser("~/Papers/index.bib"))
|
||||
self.set("proxies", [''])
|
||||
self.set("format_articles", "%f_%l-%j-%Y%v")
|
||||
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")
|
||||
|
||||
|
||||
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():
|
||||
"""
|
||||
Build a parser and parse arguments of command line.
|
||||
@ -25,13 +33,10 @@ def parse_args():
|
||||
description="A bibliography management tool.")
|
||||
parser.add_argument("-c", "--config", default=None,
|
||||
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
|
||||
|
||||
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,
|
||||
action='store_true',
|
||||
help="disable auto-download of bibtex")
|
||||
@ -46,9 +51,6 @@ def parse_args():
|
||||
parser_download.set_defaults(func='download')
|
||||
|
||||
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,
|
||||
action='store_true',
|
||||
help="disable auto-download of bibtex")
|
||||
@ -100,16 +102,30 @@ def parse_args():
|
||||
parser_open.set_defaults(func='open')
|
||||
|
||||
parser_export = subparsers.add_parser('export', help="export help")
|
||||
parser_export.add_argument('ids', metavar='id', nargs='+',
|
||||
help="an identifier")
|
||||
parser_export.add_argument('entries', metavar='entry', nargs='+',
|
||||
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_resync = subparsers.add_parser('resync', help="resync help")
|
||||
parser_resync.set_defaults(func='resync')
|
||||
|
||||
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")
|
||||
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')
|
||||
|
||||
return parser.parse_args()
|
||||
@ -133,7 +149,7 @@ def main():
|
||||
skipped = []
|
||||
for url in args.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)
|
||||
if new_name is not None:
|
||||
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))
|
||||
for filename in files_to_process:
|
||||
# Try to import the file
|
||||
new_name = commands.import_file(filename, args.type,
|
||||
new_name = commands.import_file(filename,
|
||||
args.manual, args.y,
|
||||
args.tag, not args.inplace)
|
||||
if new_name is not None:
|
||||
@ -184,7 +200,7 @@ def main():
|
||||
|
||||
# Try to delete the item
|
||||
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)
|
||||
print("%s successfully deleted." % (item,))
|
||||
else:
|
||||
@ -200,13 +216,13 @@ def main():
|
||||
# Handle exclusions
|
||||
items_to_process = list(set(args.entries) - set(args.skip))
|
||||
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)
|
||||
|
||||
# List command
|
||||
elif args.func == 'list':
|
||||
# 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"
|
||||
print("%s: %s" % (id, file))
|
||||
|
||||
@ -214,14 +230,18 @@ def main():
|
||||
elif args.func == 'open':
|
||||
# Open each entry
|
||||
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
|
||||
tools.warning("Unable to open file associated with ident %s." %
|
||||
(id,))
|
||||
|
||||
# Export command
|
||||
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
|
||||
elif args.func == 'resync':
|
||||
@ -231,7 +251,15 @@ def main():
|
||||
|
||||
# Update command
|
||||
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__':
|
||||
|
Loading…
Reference in New Issue
Block a user