Merge pull request #42 from 73/master

`doc`, `info` command & prefix completion

+ Prefix completion, see #34 
+ Add `doc` command, closes #41 
+ Add `info` ui command, closes #43
main
Fabien Benureau 9 years ago
commit 402cf62db0

@ -3,6 +3,7 @@ Small code to handle colored text
""" """
import sys import sys
import re import re
import os
def _color_supported(stream): def _color_supported(stream):
"""Returns True is the stream supports colors""" """Returns True is the stream supports colors"""

@ -7,8 +7,7 @@ from . import rename_cmd
from . import remove_cmd from . import remove_cmd
from . import list_cmd from . import list_cmd
# doc # doc
from . import attach_cmd from . import doc_cmd
from . import open_cmd
from . import tag_cmd from . import tag_cmd
from . import note_cmd from . import note_cmd
# bulk # bulk

@ -1,50 +0,0 @@
from .. import repo
from .. import color
from ..uis import get_ui
from .. import content
def parser(subparsers):
parser = subparsers.add_parser('attach',
help='attach a document to an existing paper')
# parser.add_argument('-c', '--copy', action='store_true', default=True,
# help="copy document files into library directory (default)")
parser.add_argument('-L', '--link', action='store_false', dest='copy', default=True,
help="don't copy document files, just create a link.")
parser.add_argument('-M', '--move', action='store_true', dest='move', default=False,
help="move document instead of of copying (ignored if --link).")
parser.add_argument('citekey',
help='citekey of the paper')
parser.add_argument('document',
help='document file')
return parser
def command(conf, args):
"""
:param bibfile: bibtex file (in .bib, .bibml or .yaml format.
:param docfile: path (no url yet) to a pdf or ps file
"""
ui = get_ui()
rp = repo.Repository(conf)
paper = rp.pull_paper(args.citekey)
try:
document = args.document
rp.push_doc(paper.citekey, document, copy=args.copy)
if args.copy:
if args.move:
content.remove_file(document)
# else:
# if ui.input_yn('{} has been copied into pubs; should the original be removed?'.format(color.dye_out(document, 'filepath'))):
# content.remove_file(document)
ui.message('{} attached to {}'.format(color.dye_out(document, 'filepath'), color.dye_out(paper.citekey, 'citekey')))
except ValueError as v:
ui.error(v.message)
ui.exit(1)
except IOError as v:
ui.error(v.message)
ui.exit(1)

@ -22,8 +22,8 @@ def command(conf, args):
config.check_conf(new_conf) config.check_conf(new_conf)
ui.message('The configuration file was updated.') ui.message('The configuration file was updated.')
break break
except AssertionError: # TODO better error message except AssertionError as e: # TODO better error message
ui.error('Error reading the modified configuration file.') ui.error('Error reading the modified configuration file [' + e.message + '].')
options = ['edit_again', 'abort'] options = ['edit_again', 'abort']
choice = options[ui.input_choice( choice = options[ui.input_choice(
options, ['e', 'a'], options, ['e', 'a'],

@ -0,0 +1,160 @@
import os
import subprocess
from .. import repo
from .. import color
from ..uis import get_ui
from .. import content
from ..utils import resolve_citekey, resolve_citekey_list
# doc --+- add $file $key [[-L|--link] | [-M|--move]] [-f|--force]
# +- remove $key [$key [...]] [-f|--force]
# +- export $key [$path]
# +- open $key [-w|--with $cmd]
# supplements attach, open
def parser(subparsers):
doc_parser = subparsers.add_parser('doc', help='manage the document relating to a publication')
doc_subparsers = doc_parser.add_subparsers(title='document actions', help='actions to interact with the documents',
dest='action')
add_parser = doc_subparsers.add_parser('add', help='add a document to a publication')
add_parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='force overwriting an already assigned document')
add_parser.add_argument('document', nargs=1, help='document file to assign')
add_parser.add_argument('citekey', nargs=1, help='citekey of the publication')
add_exclusives = add_parser.add_mutually_exclusive_group()
add_exclusives.add_argument('-L', '--link', action='store_false', dest='link', default=False,
help='do not copy document files, just create a link')
add_exclusives.add_argument('-M', '--move', action='store_true', dest='move', default=False,
help='move document instead of of copying (ignored if --link)')
remove_parser = doc_subparsers.add_parser('remove', help='remove assigned documents from publications')
remove_parser.add_argument('citekeys', nargs='+', help='citekeys of the publications')
remove_parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='force removing assigned documents')
# favor key+ path over: key
export_parser = doc_subparsers.add_parser('export', help='export assigned documents to given path')
export_parser.add_argument('citekeys', nargs='+', help='citekeys of the documents to export')
export_parser.add_argument('path', nargs=1, help='directory to export the files to')
open_parser = doc_subparsers.add_parser('open', help='open an assigned document')
open_parser.add_argument('citekey', nargs=1, help='citekey of the document to open')
open_parser.add_argument('-w', '--with', dest='cmd', help='command to open the file with')
return doc_parser
def command(conf, args):
ui = get_ui()
rp = repo.Repository(conf)
# print(args)
# ui.exit()
if args.action == 'add':
citekey = resolve_citekey(rp, args.citekey[0], ui=ui, exit_on_fail=True)
paper = rp.pull_paper(citekey)
if paper.docpath is not None and not args.force:
msg = ("The publication {} has already the document {} assigned." + os.linesep +
"Overwrite? ").format(color.dye_out(paper.citekey, 'citekey'), color.dye_out(paper.docpath, 'filepath'))
if not ui.input_yn(question=msg, default='n'):
ui.exit(0)
else:
try:
rp.remove_doc(paper.citekey)
except (ValueError, IOError) as v:
ui.error(v.message)
ui.exit(1)
try:
document = args.document[0]
if args.link:
rp.push_doc(paper.citekey, document, copy=False)
else:
rp.push_doc(paper.citekey, document, copy=True)
if not args.link and args.move:
content.remove_file(document)
ui.message('{} added to {}'.format(color.dye_out(document, 'filepath'),
color.dye_out(paper.citekey, 'citekey')))
except (ValueError, IOError) as v:
ui.error(v.message)
ui.exit(1)
elif args.action == 'remove':
for key in resolve_citekey_list(rp, args.citekeys, ui=ui, exit_on_fail=True):
paper = rp.pull_paper(key)
# if there is no document (and the user cares) -> inform + continue
if paper.docpath is None and not args.force:
ui.message('Publication {} has no assigned document. Not removed.'.format(
color.dye_out(paper.citekey, 'citekey')))
continue
if not args.force:
msg = 'Do you really want to remove {} from {} ?'.format(color.dye_out(paper.docpath, 'filepath'),
color.dye_out(paper.citekey, 'citekey'))
if not ui.input_yn(question=msg, default='n'):
continue
try:
rp.remove_doc(paper.citekey)
except (ValueError, IOError) as v:
ui.error(v.message)
ui.exit(1)
elif args.action == 'export':
if os.path.isdir(args.path[0]):
path = args.path[0]
if not path.endswith('/'):
path += '/'
else:
ui.error('{} is not a directory. Quit.'.format(
color.dye_err(args.path[0], 'filepath')))
ui.exit(1)
for key in resolve_citekey_list(rp, args.citekeys, ui=ui, exit_on_fail=True):
try:
paper = rp.pull_paper(key)
doc = paper.docpath
if doc is None:
ui.message('Publication {} has no document assigned.'.format(
color.dye_out(paper.citekey, 'citekey')))
else:
real_doc_path = rp.pull_docpath(key)
dest_path = path + os.path.basename(real_doc_path)
content.copy_content(real_doc_path, dest_path)
except (ValueError, IOError) as e:
ui.error(e.message)
elif args.action == 'open':
with_command = args.cmd
citekey = resolve_citekey(rp, args.citekey[0], ui=ui, exit_on_fail=True)
paper = rp.pull_paper(citekey)
if paper.docpath is None:
ui.error('No document associated with the entry {}. Quit.'.format(
color.dye_err(citekey, 'citekey')))
ui.exit()
if with_command is None:
with_command = conf['main']['open_cmd']
if with_command is None: # default in conf have not been changed
pass # TODO platform specific
try:
docpath = content.system_path(rp.databroker.real_docpath(paper.docpath))
cmd = with_command.split()
cmd.append(docpath)
subprocess.Popen(cmd)
ui.message('{} opened.'.format(color.dye_out(docpath, 'filepath')))
except OSError:
ui.error("Command does not exist: %s." % with_command)
ui.exit(127)

@ -3,7 +3,7 @@ from __future__ import print_function
from .. import repo from .. import repo
from ..uis import get_ui from ..uis import get_ui
from .. import endecoder from .. import endecoder
from ..utils import resolve_citekey_list
def parser(subparsers): def parser(subparsers):
parser = subparsers.add_parser('export', help='export bibliography') parser = subparsers.add_parser('export', help='export bibliography')
@ -19,20 +19,20 @@ def command(conf, args):
# :param bib_format (only 'bibtex' now) # :param bib_format (only 'bibtex' now)
ui = get_ui() ui = get_ui()
rp = repo.Repository(conf) rp = repo.Repository(conf)
try: try:
papers = [rp.pull_paper(c) for c in args.citekeys] papers = []
except repo.InvalidReference as v: if len(args.citekeys) < 1:
ui.error(v)
ui.exit(1)
if len(papers) == 0:
papers = rp.all_papers() papers = rp.all_papers()
else:
for key in resolve_citekey_list(repo=rp, citekeys=args.citekeys, ui=ui, exit_on_fail=True):
papers.append(rp.pull_paper(key))
bib = {} bib = {}
for p in papers: for p in papers:
bib[p.citekey] = p.bibdata bib[p.citekey] = p.bibdata
exporter = endecoder.EnDecoder() exporter = endecoder.EnDecoder()
bibdata_raw = exporter.encode_bibdata(bib) bibdata_raw = exporter.encode_bibdata(bib)
ui.message(bibdata_raw) ui.message(bibdata_raw)
except Exception as e:
ui.error(e.message)

@ -77,17 +77,17 @@ def command(conf, args):
try: try:
p = papers[k] p = papers[k]
if isinstance(p, Exception): if isinstance(p, Exception):
ui.error('could not load entry for citekey {}.'.format(k)) ui.error('Could not load entry for citekey {}.'.format(k))
else: else:
rp.push_paper(p) rp.push_paper(p)
ui.message('{} imported'.format(color.dye_out(p.citekey, 'citekey'))) ui.info('{} imported.'.format(color.dye_out(p.citekey, 'citekey')))
docfile = bibstruct.extract_docfile(p.bibdata) docfile = bibstruct.extract_docfile(p.bibdata)
if docfile is None: if docfile is None:
ui.warning("no file for {}.".format(p.citekey)) ui.warning("No file for {}.".format(p.citekey))
else: else:
rp.push_doc(p.citekey, docfile, copy=copy) rp.push_doc(p.citekey, docfile, copy=copy)
#FIXME should move the file if configured to do so. #FIXME should move the file if configured to do so.
except KeyError: except KeyError:
ui.error('no entry found for citekey {}.'.format(k)) ui.error('No entry found for citekey {}.'.format(k))
except IOError as e: except IOError as e:
ui.error(e.message) ui.error(e.message)

@ -33,7 +33,7 @@ def command(conf, args):
pubsdir = system_path(pubsdir) pubsdir = system_path(pubsdir)
if check_directory(pubsdir, fail=False) and len(os.listdir(pubsdir)) > 0: if check_directory(pubsdir, fail=False) and len(os.listdir(pubsdir)) > 0:
ui.error('directory {} is not empty.'.format( ui.error('Directory {} is not empty.'.format(
color.dye_err(pubsdir, 'filepath'))) color.dye_err(pubsdir, 'filepath')))
ui.exit() ui.exit()

@ -1,6 +1,7 @@
from .. import repo from .. import repo
from .. import content from .. import content
from ..uis import get_ui from ..uis import get_ui
from ..utils import resolve_citekey
def parser(subparsers): def parser(subparsers):
@ -16,11 +17,10 @@ def command(conf, args):
""" """
ui = get_ui() ui = get_ui()
rp = repo.Repository(conf) rp = repo.Repository(conf)
if not rp.databroker.exists(args.citekey): citekey = resolve_citekey(rp, args.citekey, ui=ui, exit_on_fail=True)
ui.error("citekey {} not found".format(args.citekey)) try:
ui.exit(1) notepath = rp.databroker.real_notepath(citekey)
notepath = rp.databroker.real_notepath(args.citekey)
content.edit_file(conf['main']['edit_cmd'], notepath, temporary=False) content.edit_file(conf['main']['edit_cmd'], notepath, temporary=False)
except Exception as e:
ui.error(e.message)

@ -1,47 +0,0 @@
import subprocess
from .. import repo
from ..uis import get_ui
from .. import color
from ..content import system_path
from ..utils import resolve_citekey
def parser(subparsers):
parser = subparsers.add_parser('open',
help='open the paper in a pdf viewer')
parser.add_argument('-w', '--with', dest='with_command', default=None,
help='command to use to open the document file')
parser.add_argument('citekey',
help='citekey of the paper')
return parser
def command(conf, args):
ui = get_ui()
with_command = args.with_command
rp = repo.Repository(conf)
citekey = resolve_citekey(rp, args.citekey, ui=ui, exit_on_fail=True)
paper = rp.pull_paper(citekey)
if with_command is None:
with_command = conf['main']['open_cmd']
if with_command is None: # default in conf have not been changed
pass # TODO platform specific
if paper.docpath is None:
ui.error('No document associated with the entry {}.'.format(
color.dye_err(citekey, 'citekey')))
ui.exit()
try:
docpath = system_path(rp.databroker.real_docpath(paper.docpath))
cmd = with_command.split()
cmd.append(docpath)
subprocess.Popen(cmd)
ui.message('{} opened.'.format(color.dye_out(docpath, 'filepath')))
except OSError:
ui.error("Command does not exist: %s." % with_command)
ui.exit(127)

@ -1,13 +1,13 @@
from .. import repo from .. import repo
from .. import color from .. import color
from ..uis import get_ui from ..uis import get_ui
from ..utils import resolve_citekey_list
def parser(subparsers): def parser(subparsers):
parser = subparsers.add_parser('remove', help='removes a paper') parser = subparsers.add_parser('remove', help='removes a publication')
parser.add_argument('-f', '--force', action='store_true', default=None, parser.add_argument('-f', '--force', action='store_true', default=None,
help="does not prompt for confirmation.") help="does not prompt for confirmation.")
parser.add_argument('citekeys', nargs='*', parser.add_argument('citekeys', nargs='+',
help="one or several citekeys") help="one or several citekeys")
return parser return parser
@ -18,15 +18,23 @@ def command(conf, args):
force = args.force force = args.force
rp = repo.Repository(conf) rp = repo.Repository(conf)
keys = resolve_citekey_list(repo=rp, citekeys=args.citekeys, ui=ui, exit_on_fail=True)
if force is None: if force is None:
are_you_sure = (("Are you sure you want to delete paper(s) [{}]" are_you_sure = (("Are you sure you want to delete the publication(s) [{}]"
" (this will also delete associated documents)?") " (this will also delete associated documents)?")
.format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys]))) .format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys])))
sure = ui.input_yn(question=are_you_sure, default='n') sure = ui.input_yn(question=are_you_sure, default='n')
if force or sure: if force or sure:
for c in args.citekeys: for c in keys:
try:
rp.remove_paper(c) rp.remove_paper(c)
ui.message('The paper(s) [{}] were removed'.format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys]))) except Exception as e:
ui.error(e.message)
ui.message('The publication(s) [{}] were removed'.format(
', '.join([color.dye_out(c, 'citekey') for c in keys])))
# FIXME: print should check that removal proceeded well. # FIXME: print should check that removal proceeded well.
else: else:
ui.message('The paper(s) [{}] were *not* removed'.format(', '.join([color.dye_out(c, 'citekey') for c in args.citekeys]))) ui.message('The publication(s) [{}] were {} removed'.format(
', '.join([color.dye_out(c, 'citekey') for c in keys]),
color.dye_out('not','bold')))

@ -1,8 +1,6 @@
from ..uis import get_ui from ..uis import get_ui
from .. import bibstruct
from .. import content
from .. import repo from .. import repo
from .. import paper from ..utils import resolve_citekey
def parser(subparsers): def parser(subparsers):
parser = subparsers.add_parser('rename', help='rename the citekey of a repository') parser = subparsers.add_parser('rename', help='rename the citekey of a repository')
@ -22,5 +20,10 @@ def command(conf, args):
ui = get_ui() ui = get_ui()
rp = repo.Repository(conf) rp = repo.Repository(conf)
paper = rp.pull_paper(args.citekey) # TODO: here should be a test whether the new citekey is valid
try:
key = resolve_citekey(repo=rp, citekey=args.citekey, ui=ui, exit_on_fail=True)
paper = rp.pull_paper(key)
rp.rename_paper(paper, args.new_citekey) rp.rename_paper(paper, args.new_citekey)
except Exception as e:
ui.error(e.message)

@ -23,6 +23,7 @@ from ..repo import Repository
from ..uis import get_ui from ..uis import get_ui
from .. import pretty from .. import pretty
from .. import color from .. import color
from ..utils import resolve_citekey
def parser(subparsers): def parser(subparsers):
@ -81,7 +82,12 @@ def command(conf, args):
if citekeyOrTag is None: if citekeyOrTag is None:
ui.message(color.dye_out(' '.join(sorted(rp.get_tags())), 'tag')) ui.message(color.dye_out(' '.join(sorted(rp.get_tags())), 'tag'))
else: else:
if rp.databroker.exists(citekeyOrTag): not_citekey = False
try:
citekeyOrTag = resolve_citekey(repo=rp, citekey=citekeyOrTag, ui=ui, exit_on_fail=True)
except SystemExit:
not_citekey = True
if not not_citekey:
p = rp.pull_paper(citekeyOrTag) p = rp.pull_paper(citekeyOrTag)
if tags is None: if tags is None:
ui.message(color.dye_out(' '.join(sorted(p.tags)), 'tag')) ui.message(color.dye_out(' '.join(sorted(p.tags)), 'tag'))
@ -93,9 +99,10 @@ def command(conf, args):
p.remove_tag(tag) p.remove_tag(tag)
rp.push_paper(p, overwrite=True) rp.push_paper(p, overwrite=True)
elif tags is not None: elif tags is not None:
ui.error(ui.error('no entry found for citekey {}.'.format(citekeyOrTag))) ui.error(ui.error('No entry found for citekey {}.'.format(citekeyOrTag)))
ui.exit() ui.exit()
else: else:
ui.info('Assuming {} to be a tag.'.format(color.dye_out(citekeyOrTag)))
# case where we want to find papers with specific tags # case where we want to find papers with specific tags
included, excluded = _tag_groups(_parse_tag_seq(citekeyOrTag)) included, excluded = _tag_groups(_parse_tag_seq(citekeyOrTag))
papers_list = [] papers_list = []

@ -18,8 +18,7 @@ CORE_CMDS = collections.OrderedDict([
('remove', commands.remove_cmd), ('remove', commands.remove_cmd),
('list', commands.list_cmd), ('list', commands.list_cmd),
('attach', commands.attach_cmd), ('doc', commands.doc_cmd),
('open', commands.open_cmd),
('tag', commands.tag_cmd), ('tag', commands.tag_cmd),
('note', commands.note_cmd), ('note', commands.note_cmd),

@ -84,21 +84,39 @@ class Repository(object):
def remove_paper(self, citekey, remove_doc=True, event=True): def remove_paper(self, citekey, remove_doc=True, event=True):
""" Remove a paper. Is silent if nothing needs to be done.""" """ Remove a paper. Is silent if nothing needs to be done."""
if event: if event:
events.RemoveEvent(citekey).send() events.RemoveEvent(citekey).send()
if remove_doc: if remove_doc:
self.remove_doc(citekey, detach_only=True)
try:
self.databroker.remove_note(citekey, silent=True)
except IOError:
pass # FIXME: if IOError is about being unable to
# remove the file, we need to issue an error.I
self.citekeys.remove(citekey)
self.databroker.remove(citekey)
def remove_doc(self, citekey, detach_only=False):
""" Remove a doc. Is silent if nothing needs to be done."""
try: try:
metadata = self.databroker.pull_metadata(citekey) metadata = self.databroker.pull_metadata(citekey)
docpath = metadata.get('docfile') docpath = metadata.get('docfile')
self.databroker.remove_doc(docpath, silent=True) self.databroker.remove_doc(docpath, silent=True)
self.databroker.remove_note(citekey, silent=True) if not detach_only:
p = self.pull_paper(citekey)
p.docpath = None
self.push_paper(p, overwrite=True, event=False)
except IOError: except IOError:
pass # FXME: if IOError is about being unable to pass # FIXME: if IOError is about being unable to
# remove the file, we need to issue an error.I # remove the file, we need to issue an error.I
self.citekeys.remove(citekey) def pull_docpath(self, citekey):
self.databroker.remove(citekey) try:
p = self.pull_paper(citekey)
return self.databroker.real_docpath(p.docpath)
except IOError:
pass # FIXME: if IOError is about being unable to
# remove the file, we need to issue an error.I
def rename_paper(self, paper, new_citekey=None, old_citekey=None): def rename_paper(self, paper, new_citekey=None, old_citekey=None):
if old_citekey is None: if old_citekey is None:

@ -58,6 +58,10 @@ class PrintUI(object):
kwargs['file'] = self._stdout kwargs['file'] = self._stdout
print(*messages, **kwargs) print(*messages, **kwargs)
def info(self, message, **kwargs):
kwargs['file'] = self._stderr
print('{}: {}'.format(color.dye_err('info', 'ok'), message), **kwargs)
def warning(self, message, **kwargs): def warning(self, message, **kwargs):
kwargs['file'] = self._stderr kwargs['file'] = self._stderr
print('{}: {}'.format(color.dye_err('warning', 'warning'), message), **kwargs) print('{}: {}'.format(color.dye_err('warning', 'warning'), message), **kwargs)
@ -87,8 +91,8 @@ class InputUI(PrintUI):
return ustr(data) #.decode('utf-8') return ustr(data) #.decode('utf-8')
def input_choice_ng(self, options, option_chars=None, default=None, question=''): def input_choice_ng(self, options, option_chars=None, default=None, question=''):
"""Ask the user to chose between a set of options. The iser is asked """Ask the user to chose between a set of options. The user is asked
to input a char corresponding to the option he choses. to input a char corresponding to the option he chooses.
:param options: list of strings :param options: list of strings
list of options list of options
@ -108,7 +112,7 @@ class InputUI(PrintUI):
option_str = '/'.join(["{}{}".format(color.dye_out(c, 'bold'), s[1:]) option_str = '/'.join(["{}{}".format(color.dye_out(c, 'bold'), s[1:])
for c, s in zip(displayed_chars, options)]) for c, s in zip(displayed_chars, options)])
self.message('{} {}: '.format(question, option_str), end='') self.message('{}: {} {}: '.format(color.dye_err('prompt', 'warning'), question, option_str), end='')
while True: while True:
answer = self.input() answer = self.input()
if answer is None or answer == '': if answer is None or answer == '':
@ -126,8 +130,8 @@ class InputUI(PrintUI):
def input_choice(self, options, option_chars, default=None, question=''): def input_choice(self, options, option_chars, default=None, question=''):
"""Ask the user to chose between a set of options. The iser is asked """Ask the user to chose between a set of options. The user is asked
to input a char corresponding to the option he choses. to input a char corresponding to the option he chooses.
:param options: list of strings :param options: list of strings
list of options list of options

@ -5,22 +5,23 @@ from . import pretty
def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True): def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True):
"""Check that a citekey exists, or autocompletes it if not ambiguous.""" """Check that a citekey exists, or autocompletes it if not ambiguous."""
""" :returns found citekey """
# FIXME. Make me optionally non ui interactive/exiting # FIXME. Make me optionally non ui interactive/exiting
citekeys = repo.citekeys_from_prefix(citekey) citekeys = repo.citekeys_from_prefix(citekey)
if len(citekeys) == 0: if len(citekeys) == 0:
if ui is not None: if ui is not None:
ui.error("no citekey named or beginning with '{}'".format(color.dye_out(citekey, 'citekey'))) ui.error("No citekey named or beginning with '{}'".format(color.dye_out(citekey, 'citekey')))
if exit_on_fail: if exit_on_fail:
ui.exit() ui.exit()
elif len(citekeys) == 1: elif len(citekeys) == 1:
if citekeys[0] != citekey: if citekeys[0] != citekey:
if ui is not None: if ui is not None:
ui.warning("provided citekey '{}' has been autocompleted into '{}'".format(color.dye_out(citekey, 'citekey'), color.dye_out(citekeys[0], 'citekey'))) ui.info("Provided citekey '{}' has been autocompleted into [{}].".format(color.dye_out(citekey, 'citekey'), color.dye_out(citekeys[0], 'citekey')))
citekey = citekeys[0] citekey = citekeys[0]
elif citekey not in citekeys: elif citekey not in citekeys:
if ui is not None: if ui is not None:
citekeys = sorted(citekeys) citekeys = sorted(citekeys)
ui.error("be more specific; provided citekey '{}' matches multiples citekeys:".format( ui.error("Be more specific; Provided citekey '{}' matches multiples citekeys:".format(
citekey)) citekey))
for c in citekeys: for c in citekeys:
p = repo.pull_paper(c) p = repo.pull_paper(c)
@ -28,3 +29,18 @@ def resolve_citekey(repo, citekey, ui=None, exit_on_fail=True):
if exit_on_fail: if exit_on_fail:
ui.exit() ui.exit()
return citekey return citekey
def resolve_citekey_list(repo, citekeys, ui=None, exit_on_fail=True):
shutdown = False
keys = []
for key in citekeys:
try:
keys.append(resolve_citekey(repo, key, ui, exit_on_fail))
except SystemExit:
shutdown = exit_on_fail
if shutdown and ui is not None:
ui.exit()
else:
return keys

@ -46,7 +46,7 @@ If you use latex, you can automatize references, by creating a bash script with:
bibtex manuscript bibtex manuscript
latex manuscript.tex latex manuscript.tex
This ensure that your reference file is always up-to-date; you can cite a paper in your manuscript a soon as you add it in pubs. This means that if you have, for instance, a doi on a webpage, you only need to do: This ensures that your reference file is always up-to-date; you can cite a paper in your manuscript a soon as you add it in pubs. This means that if you have, for instance, a doi on a webpage, you only need to do:
pubs add -D 10.1007/s00422-012-0514-6 pubs add -D 10.1007/s00422-012-0514-6

@ -352,7 +352,7 @@ class TestUsecase(DataCommandTestCase):
'pubs add data/martius.bib', 'pubs add data/martius.bib',
'pubs add data/10.1371%2Fjournal.pone.0038236.bib', 'pubs add data/10.1371%2Fjournal.pone.0038236.bib',
'pubs list', 'pubs list',
'pubs attach Page99 data/pagerank.pdf' 'pubs doc add data/pagerank.pdf Page99'
] ]
self.execute_cmds(cmds) self.execute_cmds(cmds)
@ -363,7 +363,7 @@ class TestUsecase(DataCommandTestCase):
'pubs add data/martius.bib', 'pubs add data/martius.bib',
'pubs add data/10.1371%2Fjournal.pone.0038236.bib', 'pubs add data/10.1371%2Fjournal.pone.0038236.bib',
'pubs list', 'pubs list',
'pubs attach Page99 data/pagerank.pdf', 'pubs doc add data/pagerank.pdf Page99',
('pubs remove Page99', ['y']), ('pubs remove Page99', ['y']),
'pubs remove -f turing1950computing', 'pubs remove -f turing1950computing',
] ]
@ -461,19 +461,6 @@ class TestUsecase(DataCommandTestCase):
outs = self.execute_cmds(cmds) outs = self.execute_cmds(cmds)
self.assertEqual(1 + 1, len(outs[-1].split('\n'))) self.assertEqual(1 + 1, len(outs[-1].split('\n')))
def test_open(self):
cmds = ['pubs init',
'pubs add data/pagerank.bib',
'pubs open Page99'
]
with self.assertRaises(SystemExit):
self.execute_cmds(cmds)
with self.assertRaises(SystemExit):
cmds[-1] == 'pubs open Page8'
self.execute_cmds(cmds)
def test_update(self): def test_update(self):
cmds = ['pubs init', cmds = ['pubs init',
'pubs add data/pagerank.bib', 'pubs add data/pagerank.bib',
@ -491,10 +478,21 @@ class TestUsecase(DataCommandTestCase):
outs = self.execute_cmds(cmds) outs = self.execute_cmds(cmds)
self.assertEqual(1, len(outs[2].splitlines())) self.assertEqual(1, len(outs[2].splitlines()))
def test_attach(self): def test_doc_open(self):
cmds = ['pubs init', cmds = ['pubs init',
'pubs add data/pagerank.bib', 'pubs add data/pagerank.bib',
'pubs attach Page99 data/pagerank.pdf' 'pubs doc open Page99'
]
with self.assertRaises(SystemExit):
self.execute_cmds(cmds)
with self.assertRaises(SystemExit):
cmds[-1] == 'pubs doc open Page8'
self.execute_cmds(cmds)
def test_doc_add(self):
cmds = ['pubs init',
'pubs add data/pagerank.bib',
'pubs doc add data/pagerank.pdf Page99'
] ]
self.execute_cmds(cmds) self.execute_cmds(cmds)
self.assertTrue(self.fs['os'].path.exists( self.assertTrue(self.fs['os'].path.exists(
@ -504,15 +502,36 @@ class TestUsecase(DataCommandTestCase):
# Also test that do not remove original # Also test that do not remove original
self.assertTrue(self.fs['os'].path.exists('/data/pagerank.pdf')) self.assertTrue(self.fs['os'].path.exists('/data/pagerank.pdf'))
def test_attach_with_move(self): def test_doc_add_with_move(self):
cmds = ['pubs init -p paper_second/', cmds = ['pubs init -p paper_second/',
'pubs add data/pagerank.bib', 'pubs add data/pagerank.bib',
'pubs attach --move Page99 data/pagerank.pdf' 'pubs doc add --move data/pagerank.pdf Page99'
] ]
self.execute_cmds(cmds) self.execute_cmds(cmds)
self.assertTrue(self.fs['os'].path.isfile(self.default_conf_path)) self.assertTrue(self.fs['os'].path.isfile(self.default_conf_path))
self.assertFalse(self.fs['os'].path.exists('/data/pagerank.pdf')) self.assertFalse(self.fs['os'].path.exists('/data/pagerank.pdf'))
def test_doc_remove(self):
cmds = ['pubs init',
'pubs add data/pagerank.bib',
'pubs doc add data/pagerank.pdf Page99',
('pubs doc remove Page99', ['y']),
]
self.execute_cmds(cmds)
docdir = self.fs['os'].path.expanduser('~/.pubs/doc/')
self.assertNotIn('turing-mind-1950.pdf', self.fs['os'].listdir(docdir))
def test_doc_export(self):
cmds = ['pubs init',
'pubs add data/pagerank.bib',
'pubs rename Page99 page100',
'pubs doc add data/pagerank.pdf page100',
'pubs doc export page100 /'
]
self.execute_cmds(cmds)
self.assertIn('page100.pdf', self.fs['os'].listdir('/'))
def test_alternate_config(self): def test_alternate_config(self):
alt_conf = self.fs['os'].path.expanduser('~/.alt_conf') alt_conf = self.fs['os'].path.expanduser('~/.alt_conf')
cmds = ['pubs -c ' + alt_conf + ' init', cmds = ['pubs -c ' + alt_conf + ' init',

Loading…
Cancel
Save