From af73d4ed64866a53b03bba4f72cf370bbd1fc9be Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 13:37:24 -0400 Subject: [PATCH 01/11] Basic autocompletion with argcomplete. Only support pubs command completion. --- pubs/pubs | 1 + pubs/pubs_cmd.py | 6 ++++++ readme.md | 9 ++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pubs/pubs b/pubs/pubs index 7699f18..68fd3d7 100755 --- a/pubs/pubs +++ b/pubs/pubs @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- +# PYTHON_ARGCOMPLETE_OK from pubs import pubs_cmd diff --git a/pubs/pubs_cmd.py b/pubs/pubs_cmd.py index 5b93893..62a58b8 100644 --- a/pubs/pubs_cmd.py +++ b/pubs/pubs_cmd.py @@ -7,6 +7,10 @@ from . import commands from . import update from . import plugins from .__init__ import __version__ +try: + import argcomplete +except ModuleNotFoundError: + argcomplete = None CORE_CMDS = collections.OrderedDict([ @@ -78,6 +82,8 @@ def execute(raw_args=sys.argv): for p in plugins.get_plugins().values(): p.update_parser(subparsers) + if argcomplete is not None: + argcomplete.autocomplete(parser) # Parse and run appropriate command args = parser.parse_args(remaining_args) args.prog = "pubs" # FIXME? diff --git a/readme.md b/readme.md index dfe19b4..960b6f3 100644 --- a/readme.md +++ b/readme.md @@ -88,6 +88,13 @@ The first command defines a new subcommand: `pubs open -w evince` will be execut The second starts with a bang: `!`, and is treated as a shell command. +## Autocompletion + +For autocompletion to work, you need the [argcomplete](https://argcomplete.readthedocs.io) python package. + +For bash completion, just activate it globally with the command `activate-global-python-argcomplete`, or `activate-global-python-argcomplete --user`, that will copy `python-argcomplete.sh` to `/etc/bash_completion.d/` or `~/.bash_completion.d/`. You need to make sure that the file is evaluated on bash start. For more information or other shells please report to [argcomplete's documentation](https://argcomplete.readthedocs.io). + + ## Need more help ? You can access the self-documented configuration by using `pubs conf`, and all the commands's help is available with the `--help` option. Did not find an answer to your question? Drop us an issue. We may not answer right away (science comes first!) but we'll eventually look into it. @@ -96,7 +103,7 @@ You can access the self-documented configuration by using `pubs conf`, and all t ## Requirements - python >= 2.7 or >= 3.3 - +- [argcomplete](https://argcomplete.readthedocs.io) (optional, for autocompletion) ## Authors From f6e04123060efaca9dbf7aeb83cdb6943698559e Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 16:00:51 -0400 Subject: [PATCH 02/11] Missing import in update. --- pubs/update.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubs/update.py b/pubs/update.py index aab8879..1fb983f 100644 --- a/pubs/update.py +++ b/pubs/update.py @@ -1,6 +1,7 @@ import shutil import io +import sys from . import config from . import uis from . import color @@ -33,6 +34,7 @@ def update_check(conf, path=None): return False + def update(conf, code_version, repo_version, path=None): """Runs an update if necessary, and return True in that case.""" if path is None: From a5466c940eccc9bedda9ca9da5022c8bf812f27f Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 18:10:47 -0400 Subject: [PATCH 03/11] Prepare configuration loading for autocomplete. In particular, tries to load configuration when no argument is given. Also removes unused check option from load_conf. --- pubs/commands/conf_cmd.py | 2 +- pubs/config/__init__.py | 3 ++- pubs/config/conf.py | 18 +++++++++++++----- pubs/pubs_cmd.py | 15 +++++++++------ tests/test_usecase.py | 9 ++++++++- 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/pubs/commands/conf_cmd.py b/pubs/commands/conf_cmd.py index 1f5ba44..75eed5a 100644 --- a/pubs/commands/conf_cmd.py +++ b/pubs/commands/conf_cmd.py @@ -17,7 +17,7 @@ def command(conf, args): # get modif from user ui.edit_file(config.get_confpath()) - new_conf = config.load_conf(check=False) + new_conf = config.load_conf() try: config.check_conf(new_conf) ui.message('The configuration file was updated.') diff --git a/pubs/config/__init__.py b/pubs/config/__init__.py index 0f5a133..3e8ec08 100644 --- a/pubs/config/__init__.py +++ b/pubs/config/__init__.py @@ -1,2 +1,3 @@ -from .conf import get_confpath, load_default_conf, load_conf, save_conf, check_conf +from .conf import (get_confpath, load_default_conf, load_conf, save_conf, + check_conf, ConfigurationNotFound) from .conf import default_open_cmd, post_process_conf diff --git a/pubs/config/conf.py b/pubs/config/conf.py index af2f3ce..2086601 100644 --- a/pubs/config/conf.py +++ b/pubs/config/conf.py @@ -1,7 +1,5 @@ import os import platform -import shutil - import configobj import validate @@ -11,6 +9,16 @@ from .spec import configspec DFT_CONFIG_PATH = os.path.expanduser('~/.pubsrc') + +class ConfigurationNotFound(IOError): + + def __init__(self, path): + super(ConfigurationNotFound, self).__init__( + "No configuration found at path {}. Maybe you need to initialize " + "your repository with `pubs init` or specify a --config argument." + "".format(path)) + + def post_process_conf(conf): """Do some post processing on the configuration""" if conf['main']['docsdir'] == 'docsdir://': @@ -50,14 +58,14 @@ def check_conf(conf): assert results == True, '{}'.format(results) # TODO: precise error dialog when parsing error -def load_conf(check=True, path=None): +def load_conf(path=None): """Load the configuration""" if path is None: path = get_confpath(verify=True) + if not os.path.exists(path): + raise ConfigurationNotFound(path) with open(path, 'rb') as f: conf = configobj.ConfigObj(f.readlines(), configspec=configspec) - if check: - check_conf(conf) conf.filename = path conf = post_process_conf(conf) return conf diff --git a/pubs/pubs_cmd.py b/pubs/pubs_cmd.py index 62a58b8..976be3e 100644 --- a/pubs/pubs_cmd.py +++ b/pubs/pubs_cmd.py @@ -53,15 +53,18 @@ def execute(raw_args=sys.argv): conf_path = config.get_confpath(verify=False) # will be checked on load # Loading config - if len(remaining_args) > 0 and remaining_args[0] != 'init': - conf = config.load_conf(path=conf_path, check=False) + try: + conf = config.load_conf(path=conf_path) if update.update_check(conf, path=conf.filename): # an update happened, reload conf. - conf = config.load_conf(path=conf_path, check=False) + conf = config.load_conf(path=conf_path) config.check_conf(conf) - else: - conf = config.load_default_conf() - conf.filename = conf_path + except config.ConfigurationNotFound: + if len(remaining_args) == 0 or remaining_args[0] == 'init': + conf = config.load_default_conf() + conf.filename = conf_path + else: + raise uis.init_ui(conf, force_colors=top_args.force_colors) ui = uis.get_ui() diff --git a/tests/test_usecase.py b/tests/test_usecase.py index 1120141..4c275d5 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -33,7 +33,7 @@ class FakeSystemExit(Exception): SystemExit exceptions are replaced by FakeSystemExit in the execute_cmds() function, so they can be catched by ExpectedFailure tests in Python 2.x. - If a code is accepted to raise SystemExit, catch FakeSystemExit instead. + If a code is expected to raise SystemExit, catch FakeSystemExit instead. """ pass @@ -151,6 +151,13 @@ class DataCommandTestCase(CommandTestCase): # Actual tests +class TestAlone(CommandTestCase): + + def test_alone_misses_command(self): + with self.assertRaises(FakeSystemExit): + self.execute_cmds(['pubs']) + + class TestInit(CommandTestCase): def test_init(self): From 02c11aaaeabab2f00dfbf5136ef003df63d3f1c7 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 18:33:33 -0400 Subject: [PATCH 04/11] Adds completion for citekeys. --- pubs/commands/add_cmd.py | 2 +- pubs/commands/conf_cmd.py | 2 +- pubs/commands/doc_cmd.py | 43 ++++++++++++++++++++++------------ pubs/commands/edit_cmd.py | 18 ++++++++------ pubs/commands/export_cmd.py | 7 ++++-- pubs/commands/import_cmd.py | 2 +- pubs/commands/init_cmd.py | 3 ++- pubs/commands/list_cmd.py | 2 +- pubs/commands/note_cmd.py | 10 ++++---- pubs/commands/remove_cmd.py | 7 ++++-- pubs/commands/rename_cmd.py | 18 +++++++------- pubs/commands/tag_cmd.py | 5 ++-- pubs/commands/websearch_cmd.py | 3 ++- pubs/completion.py | 38 ++++++++++++++++++++++++++++++ pubs/pubs_cmd.py | 11 ++++----- readme.md | 2 +- 16 files changed, 118 insertions(+), 55 deletions(-) create mode 100644 pubs/completion.py diff --git a/pubs/commands/add_cmd.py b/pubs/commands/add_cmd.py index 95a07a2..8c5db77 100644 --- a/pubs/commands/add_cmd.py +++ b/pubs/commands/add_cmd.py @@ -9,7 +9,7 @@ from .. import color from .. import pretty -def parser(subparsers): +def parser(subparsers, conf): parser = subparsers.add_parser('add', help='add a paper to the repository') parser.add_argument('bibfile', nargs='?', default=None, help='bibtex file') diff --git a/pubs/commands/conf_cmd.py b/pubs/commands/conf_cmd.py index 75eed5a..eceaf8d 100644 --- a/pubs/commands/conf_cmd.py +++ b/pubs/commands/conf_cmd.py @@ -3,7 +3,7 @@ from .. import config from .. import content -def parser(subparsers): +def parser(subparsers, conf): parser = subparsers.add_parser('conf', help='open the configuration in an editor') return parser diff --git a/pubs/commands/doc_cmd.py b/pubs/commands/doc_cmd.py index 96bcf0f..c8f34eb 100644 --- a/pubs/commands/doc_cmd.py +++ b/pubs/commands/doc_cmd.py @@ -6,6 +6,8 @@ from .. import color from ..uis import get_ui from .. import content from ..utils import resolve_citekey, resolve_citekey_list +from ..completion import CiteKeyCompletion + # doc --+- add $file $key [[-L|--link] | [-M|--move]] [-f|--force] # +- remove $key [$key [...]] [-f|--force] @@ -13,35 +15,46 @@ from ..utils import resolve_citekey, resolve_citekey_list # +- 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') +def parser(subparsers, conf): + doc_parser = subparsers.add_parser( + 'doc', + help='manage the document relating to a publication') + doc_subparsers = doc_parser.add_subparsers( + title='document actions', dest='action', + help='actions to interact with the documents') doc_subparsers.required = True 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') + 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_parser.add_argument('citekey', nargs=1, help='citekey of the publication' + ).completer = CiteKeyCompletion(conf) 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)') + 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') + remove_parser.add_argument('citekeys', nargs='+', help='citekeys of the publications' + ).completer = CiteKeyCompletion(conf) + 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('citekeys', nargs='+', + help='citekeys of the documents to export' + ).completer = CiteKeyCompletion(conf) 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('citekey', nargs=1, help='citekey of the document to open' + ).completer = CiteKeyCompletion(conf) open_parser.add_argument('-w', '--with', dest='cmd', help='command to open the file with') return doc_parser diff --git a/pubs/commands/edit_cmd.py b/pubs/commands/edit_cmd.py index 60f04d6..0af4e5e 100644 --- a/pubs/commands/edit_cmd.py +++ b/pubs/commands/edit_cmd.py @@ -4,15 +4,19 @@ from .. import repo from ..uis import get_ui from ..endecoder import EnDecoder from ..utils import resolve_citekey +from ..completion import CiteKeyCompletion -def parser(subparsers): - parser = subparsers.add_parser('edit', - help='open the paper bibliographic file in an editor') - parser.add_argument('-m', '--meta', action='store_true', default=False, - help='edit metadata') - parser.add_argument('citekey', - help='citekey of the paper') +def parser(subparsers, conf): + parser = subparsers.add_parser( + 'edit', + help='open the paper bibliographic file in an editor') + parser.add_argument( + '-m', '--meta', action='store_true', default=False, + help='edit metadata') + parser.add_argument( + 'citekey', + help='citekey of the paper').completer = CiteKeyCompletion(conf) return parser diff --git a/pubs/commands/export_cmd.py b/pubs/commands/export_cmd.py index 54e48f8..1224a03 100644 --- a/pubs/commands/export_cmd.py +++ b/pubs/commands/export_cmd.py @@ -4,12 +4,15 @@ from .. import repo from ..uis import get_ui from .. import endecoder from ..utils import resolve_citekey_list +from ..completion import CiteKeyCompletion -def parser(subparsers): + +def parser(subparsers, conf): parser = subparsers.add_parser('export', help='export bibliography') # parser.add_argument('-f', '--bib-format', default='bibtex', # help='export format') - parser.add_argument('citekeys', nargs='*', help='one or several citekeys') + parser.add_argument('citekeys', nargs='*', help='one or several citekeys' + ).completer = CiteKeyCompletion(conf) return parser diff --git a/pubs/commands/import_cmd.py b/pubs/commands/import_cmd.py index 990fc8c..03935b4 100644 --- a/pubs/commands/import_cmd.py +++ b/pubs/commands/import_cmd.py @@ -11,7 +11,7 @@ from ..uis import get_ui from ..content import system_path, read_text_file -def parser(subparsers): +def parser(subparsers, conf): parser = subparsers.add_parser('import', help='import paper(s) to the repository') parser.add_argument('bibpath', diff --git a/pubs/commands/init_cmd.py b/pubs/commands/init_cmd.py index f4f2a4c..322800e 100644 --- a/pubs/commands/init_cmd.py +++ b/pubs/commands/init_cmd.py @@ -9,7 +9,8 @@ from ..repo import Repository from ..content import system_path, check_directory from .. import config -def parser(subparsers): + +def parser(subparsers, conf): parser = subparsers.add_parser('init', help="initialize the pubs directory") parser.add_argument('-p', '--pubsdir', default=None, diff --git a/pubs/commands/list_cmd.py b/pubs/commands/list_cmd.py index 6901f08..5884cdc 100644 --- a/pubs/commands/list_cmd.py +++ b/pubs/commands/list_cmd.py @@ -10,7 +10,7 @@ class InvalidQuery(ValueError): pass -def parser(subparsers): +def parser(subparsers, conf): parser = subparsers.add_parser('list', help="list papers") parser.add_argument('-k', '--citekeys-only', action='store_true', default=False, dest='citekeys', diff --git a/pubs/commands/note_cmd.py b/pubs/commands/note_cmd.py index 8e1fab1..696f99d 100644 --- a/pubs/commands/note_cmd.py +++ b/pubs/commands/note_cmd.py @@ -2,19 +2,19 @@ from .. import repo from .. import content from ..uis import get_ui from ..utils import resolve_citekey +from ..completion import CiteKeyCompletion -def parser(subparsers): +def parser(subparsers, conf): parser = subparsers.add_parser('note', - help='edit the note attached to a paper') + help='edit the note attached to a paper') parser.add_argument('citekey', - help='citekey of the paper') + help='citekey of the paper' + ).completer = CiteKeyCompletion(conf) return parser def command(conf, args): - """ - """ ui = get_ui() rp = repo.Repository(conf) diff --git a/pubs/commands/remove_cmd.py b/pubs/commands/remove_cmd.py index 83785e2..cbeb9e9 100644 --- a/pubs/commands/remove_cmd.py +++ b/pubs/commands/remove_cmd.py @@ -3,13 +3,16 @@ from .. import color from ..uis import get_ui from ..utils import resolve_citekey_list from ..p3 import ustr +from ..completion import CiteKeyCompletion -def parser(subparsers): + +def parser(subparsers, conf): parser = subparsers.add_parser('remove', help='removes a publication') parser.add_argument('-f', '--force', action='store_true', default=None, help="does not prompt for confirmation.") parser.add_argument('citekeys', nargs='+', - help="one or several citekeys") + help="one or several citekeys" + ).completer = CiteKeyCompletion(conf) return parser diff --git a/pubs/commands/rename_cmd.py b/pubs/commands/rename_cmd.py index dd4b903..ae08f51 100644 --- a/pubs/commands/rename_cmd.py +++ b/pubs/commands/rename_cmd.py @@ -2,13 +2,15 @@ from ..uis import get_ui from .. import color from .. import repo from ..utils import resolve_citekey +from ..completion import CiteKeyCompletion -def parser(subparsers): - parser = subparsers.add_parser('rename', help='rename the citekey of a repository') - parser.add_argument('citekey', - help='current citekey') - parser.add_argument('new_citekey', - help='new citekey') + +def parser(subparsers, conf): + parser = subparsers.add_parser('rename', + help='rename the citekey of a repository') + parser.add_argument('citekey', help='current citekey' + ).completer = CiteKeyCompletion(conf) + parser.add_argument('new_citekey', help='new citekey') return parser @@ -26,7 +28,7 @@ def command(conf, args): paper = rp.pull_paper(key) rp.rename_paper(paper, args.new_citekey) ui.message("The '{}' citekey has been renamed into '{}'".format( - color.dye_out(args.citekey, 'citekey'), - color.dye_out(args.new_citekey, 'citekey'))) + color.dye_out(args.citekey, 'citekey'), + color.dye_out(args.new_citekey, 'citekey'))) rp.close() diff --git a/pubs/commands/tag_cmd.py b/pubs/commands/tag_cmd.py index 5ce91a7..c37eac4 100644 --- a/pubs/commands/tag_cmd.py +++ b/pubs/commands/tag_cmd.py @@ -24,12 +24,13 @@ from ..uis import get_ui from .. import pretty from .. import color from ..utils import resolve_citekey +from ..completion import CiteKeyCompletion -def parser(subparsers): +def parser(subparsers, conf): parser = subparsers.add_parser('tag', help="add, remove and show tags") parser.add_argument('citekeyOrTag', nargs='?', default=None, - help='citekey or tag.') + help='citekey or tag.').completer = CiteKeyCompletion(conf) parser.add_argument('tags', nargs='?', default=None, help='If the previous argument was a citekey, then ' 'a list of tags separated by a +.') diff --git a/pubs/commands/websearch_cmd.py b/pubs/commands/websearch_cmd.py index ee7ee20..7599f0d 100644 --- a/pubs/commands/websearch_cmd.py +++ b/pubs/commands/websearch_cmd.py @@ -3,7 +3,8 @@ import urllib from ..uis import get_ui -def parser(subparsers): + +def parser(subparsers, conf): parser = subparsers.add_parser('websearch', help="launch a search on Google Scholar") parser.add_argument("search_string", nargs = '*', diff --git a/pubs/completion.py b/pubs/completion.py new file mode 100644 index 0000000..3752cb3 --- /dev/null +++ b/pubs/completion.py @@ -0,0 +1,38 @@ +from . import repo +try: + import argcomplete +except ModuleNotFoundError: + + class FakeModule: + + @staticmethod + def _fun(**kwargs): + pass + + def __getattr__(self, _): + return self._fun + + argcomplete = FakeModule() + + +def autocomplete(parser): + argcomplete.autocomplete(parser) + + +class BaseCompleter(object): + + def __init__(self, conf): + self.conf = conf + + def __call__(self, **kwargs): + try: + return self._complete(**kwargs) + except Exception as e: + argcomplete.warn(e) + + +class CiteKeyCompletion(BaseCompleter): + + def _complete(self, **kwargs): + rp = repo.Repository(self.conf) + return rp.citekeys diff --git a/pubs/pubs_cmd.py b/pubs/pubs_cmd.py index 976be3e..b1c28c8 100644 --- a/pubs/pubs_cmd.py +++ b/pubs/pubs_cmd.py @@ -7,10 +7,7 @@ from . import commands from . import update from . import plugins from .__init__ import __version__ -try: - import argcomplete -except ModuleNotFoundError: - argcomplete = None +from .completion import autocomplete CORE_CMDS = collections.OrderedDict([ @@ -77,7 +74,7 @@ def execute(raw_args=sys.argv): # Populate the parser with core commands for cmd_name, cmd_mod in CORE_CMDS.items(): - cmd_parser = cmd_mod.parser(subparsers) + cmd_parser = cmd_mod.parser(subparsers, conf) cmd_parser.set_defaults(func=cmd_mod.command) # Extend with plugin commands @@ -85,8 +82,8 @@ def execute(raw_args=sys.argv): for p in plugins.get_plugins().values(): p.update_parser(subparsers) - if argcomplete is not None: - argcomplete.autocomplete(parser) + # Eventually autocomplete + autocomplete(parser) # Parse and run appropriate command args = parser.parse_args(remaining_args) args.prog = "pubs" # FIXME? diff --git a/readme.md b/readme.md index 960b6f3..9be385f 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Alternatively Arch Linux users can also use the [pubs-git](https://aur.archlinux ## Getting started -Create your library (by default, goes to '~/.pubs/'). +Create your library (by default, goes to `~/.pubs/`). pubs init From e1a6ad216711a894aa6d81c303dff6be6e8053a3 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 19:02:35 -0400 Subject: [PATCH 05/11] Adds basic tag completion. --- pubs/commands/tag_cmd.py | 8 +++++--- pubs/completion.py | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/pubs/commands/tag_cmd.py b/pubs/commands/tag_cmd.py index c37eac4..b5031f9 100644 --- a/pubs/commands/tag_cmd.py +++ b/pubs/commands/tag_cmd.py @@ -24,16 +24,17 @@ from ..uis import get_ui from .. import pretty from .. import color from ..utils import resolve_citekey -from ..completion import CiteKeyCompletion +from ..completion import CiteKeyOrTagCompletion, TagModifierCompletion def parser(subparsers, conf): parser = subparsers.add_parser('tag', help="add, remove and show tags") parser.add_argument('citekeyOrTag', nargs='?', default=None, - help='citekey or tag.').completer = CiteKeyCompletion(conf) + help='citekey or tag.').completer = CiteKeyOrTagCompletion(conf) parser.add_argument('tags', nargs='?', default=None, help='If the previous argument was a citekey, then ' - 'a list of tags separated by a +.') + 'a list of tags separated by + and -.' + ).completer = TagModifierCompletion(conf) # TODO find a way to display clear help for multiple command semantics, # indistinguisable for argparse. (fabien, 201306) return parser @@ -71,6 +72,7 @@ def _tag_groups(tags): minus_tags.append(tag[1:]) return set(plus_tags), set(minus_tags) + def command(conf, args): """Add, remove and show tags""" diff --git a/pubs/completion.py b/pubs/completion.py index 3752cb3..f05fe36 100644 --- a/pubs/completion.py +++ b/pubs/completion.py @@ -1,4 +1,4 @@ -from . import repo +import re try: import argcomplete except ModuleNotFoundError: @@ -14,6 +14,8 @@ except ModuleNotFoundError: argcomplete = FakeModule() +from . import repo + def autocomplete(parser): argcomplete.autocomplete(parser) @@ -36,3 +38,22 @@ class CiteKeyCompletion(BaseCompleter): def _complete(self, **kwargs): rp = repo.Repository(self.conf) return rp.citekeys + + +class CiteKeyOrTagCompletion(BaseCompleter): + + def _complete(self, **kwargs): + rp = repo.Repository(self.conf) + return rp.citekeys.union(rp.get_tags()) + + +class TagModifierCompletion(BaseCompleter): + + regxp = r"[^:+-]*$" # prefix of tag after last separator + + def _complete(self, prefix, **kwargs): + tags = repo.Repository(self.conf).get_tags() + start, _ = re.search(self.regxp, prefix).span() + partial_expr = prefix[:start] + t_prefix = prefix[start:] + return [partial_expr + t for t in tags if t.startswith(t_prefix)] From ce3ed1e52ff1d6252ae8c027ba1b198e81190b34 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 23:12:19 -0400 Subject: [PATCH 06/11] Use ImportError for older python versions. --- pubs/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubs/completion.py b/pubs/completion.py index f05fe36..da228b1 100644 --- a/pubs/completion.py +++ b/pubs/completion.py @@ -1,7 +1,7 @@ import re try: import argcomplete -except ModuleNotFoundError: +except ImportError: class FakeModule: From 92fb40387178bef3443b7d326a5e6bcf5e361cd9 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Fri, 12 May 2017 23:22:05 -0400 Subject: [PATCH 07/11] Fix function in FakeModule. --- pubs/completion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubs/completion.py b/pubs/completion.py index da228b1..0b2054c 100644 --- a/pubs/completion.py +++ b/pubs/completion.py @@ -6,7 +6,7 @@ except ImportError: class FakeModule: @staticmethod - def _fun(**kwargs): + def _fun(*args, **kwargs): pass def __getattr__(self, _): From 576d907a78a77b636e850f7d7ed9a119e9934cd6 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Mon, 15 May 2017 14:02:02 -0400 Subject: [PATCH 08/11] Adds instructions for zsh completion. --- readme.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 9be385f..6a93968 100644 --- a/readme.md +++ b/readme.md @@ -92,12 +92,21 @@ The second starts with a bang: `!`, and is treated as a shell command. For autocompletion to work, you need the [argcomplete](https://argcomplete.readthedocs.io) python package. -For bash completion, just activate it globally with the command `activate-global-python-argcomplete`, or `activate-global-python-argcomplete --user`, that will copy `python-argcomplete.sh` to `/etc/bash_completion.d/` or `~/.bash_completion.d/`. You need to make sure that the file is evaluated on bash start. For more information or other shells please report to [argcomplete's documentation](https://argcomplete.readthedocs.io). +For *bash* completion, just activate it globally with the command `activate-global-python-argcomplete`, or `activate-global-python-argcomplete --user`, that will copy `python-argcomplete.sh` to `/etc/bash_completion.d/` or `~/.bash_completion.d/`. You need to make sure that the file is evaluated on bash start. For more information or other shells please report to [argcomplete's documentation](https://argcomplete.readthedocs.io). + +For *zsh* completion, the global activation is not supported but bash completion compatibility can be used for pubs. For that, add the following to your `.zshrc`: + + # Enable and load bashcompinit + autoload -Uz compinit bashcompinit + compinit + bashcompinit + # Argcomplete explicit registration for pubs + eval "$(register-python-argcomplete pubs)" ## Need more help ? -You can access the self-documented configuration by using `pubs conf`, and all the commands's help is available with the `--help` option. Did not find an answer to your question? Drop us an issue. We may not answer right away (science comes first!) but we'll eventually look into it. +You can access the self-documented configuration by using `pubs conf`, and all the commands' help is available with the `--help` option. Did not find an answer to your question? Drop us an issue. We may not answer right away (science comes first!) but we'll eventually look into it. ## Requirements From e129398311dfda380c6b30e8ad4a334960955729 Mon Sep 17 00:00:00 2001 From: Olivier Mangin Date: Mon, 22 May 2017 19:09:24 -0400 Subject: [PATCH 09/11] Pass the config to plugins on parser modification to allow completion. --- pubs/plugins.py | 2 +- pubs/plugs/alias/alias.py | 2 +- pubs/pubs_cmd.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubs/plugins.py b/pubs/plugins.py index 17e2256..c888715 100644 --- a/pubs/plugins.py +++ b/pubs/plugins.py @@ -14,7 +14,7 @@ class PapersPlugin(object): name = None - def get_commands(self, subparsers): + def get_commands(self, subparsers, conf): """Populates the parser with plugins specific command. Returns iterable of pairs (command name, command function to call). """ diff --git a/pubs/plugs/alias/alias.py b/pubs/plugs/alias/alias.py index eebf1f9..7a75467 100644 --- a/pubs/plugs/alias/alias.py +++ b/pubs/plugs/alias/alias.py @@ -65,7 +65,7 @@ class AliasPlugin(PapersPlugin): for name, definition in conf['plugins']['alias'].items(): self.aliases.append(Alias.create_alias(name, definition)) - def update_parser(self, subparsers): + def update_parser(self, subparsers, conf): """Add subcommand to the provided subparser""" for alias in self.aliases: alias_parser = alias.parser(subparsers) diff --git a/pubs/pubs_cmd.py b/pubs/pubs_cmd.py index b1c28c8..bd956cc 100644 --- a/pubs/pubs_cmd.py +++ b/pubs/pubs_cmd.py @@ -80,7 +80,7 @@ def execute(raw_args=sys.argv): # Extend with plugin commands plugins.load_plugins(conf, ui) for p in plugins.get_plugins().values(): - p.update_parser(subparsers) + p.update_parser(subparsers, conf) # Eventually autocomplete autocomplete(parser) From 4ac588912390b89ebc439ef1742060641efb692e Mon Sep 17 00:00:00 2001 From: "Fabien C. Y. Benureau" Date: Sun, 9 Jul 2017 20:58:56 +0200 Subject: [PATCH 10/11] Update installation section in readme --- readme.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 6a93968..273f733 100644 --- a/readme.md +++ b/readme.md @@ -15,11 +15,13 @@ Pubs is built with the following principles in mind: ## Installation -Until pubs is uploaded to Pypi, the standard way to install it is to clone the repository and call `setup.py`. +Currently, the Pypi version is outdated. You can install the development version of `pubs`, which should be stable, with: - git clone https://github.com/pubs/pubs.git - cd pubs - sudo python setup.py install # remove sudo and add --user for local installation instead + pip install --upgrade git+https://github.com/pubs/pubs + +If `pubs` is already installed, you can upgrade with: + + pip install --upgrade git+https://github.com/pubs/pubs Alternatively Arch Linux users can also use the [pubs-git](https://aur.archlinux.org/packages/pubs-git/) AUR package. From a99e97c716ce1a16e6a275b7de66f130362d0953 Mon Sep 17 00:00:00 2001 From: "Fabien C. Y. Benureau" Date: Sun, 9 Jul 2017 21:09:14 +0200 Subject: [PATCH 11/11] Streamline autocompletion explanations in readme Important details about Bash were not mentioned in our explanation. Furthermore, these instructions may get obsolete at any moment. Better to delegate to the argcomplete project. --- readme.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 273f733..074bde9 100644 --- a/readme.md +++ b/readme.md @@ -92,9 +92,7 @@ The second starts with a bang: `!`, and is treated as a shell command. ## Autocompletion -For autocompletion to work, you need the [argcomplete](https://argcomplete.readthedocs.io) python package. - -For *bash* completion, just activate it globally with the command `activate-global-python-argcomplete`, or `activate-global-python-argcomplete --user`, that will copy `python-argcomplete.sh` to `/etc/bash_completion.d/` or `~/.bash_completion.d/`. You need to make sure that the file is evaluated on bash start. For more information or other shells please report to [argcomplete's documentation](https://argcomplete.readthedocs.io). +For autocompletion to work, you need the [argcomplete](https://argcomplete.readthedocs.io) Python package, and Bash 4.2 or newer. For activating *bash* or *tsch* completion, consult the [argcomplete documentation](https://argcomplete.readthedocs.io/en/latest/#global-completion). For *zsh* completion, the global activation is not supported but bash completion compatibility can be used for pubs. For that, add the following to your `.zshrc`: