diff --git a/CODESTYLE.md b/CODESTYLE.md index 5d3987f..6446a4e 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -15,10 +15,12 @@ Git This project use git-flow {nvie.com/posts/a-successful-git-branching-model} as a model for branches management. In particular : -- master is for release only -- when you commit to develop, run nosetests before. All tests should pass. -- in feature/branches, you do whatever you want. -- when developping a new feature, write tests for it. +1. master is for release only +2. when you commit to develop, run nosetests before. All tests should pass. +3. when you commit to develop, run nosetests before. All tests should pass. +4. when you commit to develop, run nosetests before. All tests should pass. +5. in 'feat/' branches, you do whatever you want. +6. when developping a new feature, write tests for it. Alignement @@ -52,7 +54,7 @@ Names Avoid at all cost to name a variable like a module from the package, a dependency or the standart lib. This breaks coherence across the code, makes it harder to read. -Change either the module or variable name, I don't care. +Change either the module or variable name. Function that have only local uses should be preceded by an underscore. @@ -66,3 +68,14 @@ These functiona won't be imported automatically with the module. It keeps the interface clean, makes occasional hacks explicit, and inform other developers that theses functions may need special care when uses outside their natural habitat. + + +Files +===== + +Unless you have a good reason, use 'open' as such : +yes: with open(path, 'w') as f: + f.read() +no : f = open(path, 'r') + f.read() + f.close() diff --git a/papers/__init__.py b/papers/__init__.py index e69de29..fe95ae2 100644 --- a/papers/__init__.py +++ b/papers/__init__.py @@ -0,0 +1 @@ +__version__ = 3 \ No newline at end of file diff --git a/papers/commands/add_cmd.py b/papers/commands/add_cmd.py index 8a97537..838e090 100644 --- a/papers/commands/add_cmd.py +++ b/papers/commands/add_cmd.py @@ -1,11 +1,11 @@ from .. import repo from .. import files from ..paper import Paper, NoDocumentFile, get_bibentry_from_string -from .. import configs +from ..configs import config from .helpers import add_paper_with_docfile, extract_doc_path_from_bibdata -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('add', help='add a paper to the repository') parser.add_argument('-b', '--bibfile', help='bibtex, bibtexml or bibyaml file', default=None) @@ -19,14 +19,14 @@ def parser(subparsers, config): return parser -def command(config, ui, bibfile, docfile, tags, copy): +def command(ui, bibfile, docfile, tags, copy): """ :param bibfile: bibtex file (in .bib, .bibml or .yaml format. :param docfile: path (no url yet) to a pdf or ps file """ if copy is None: - copy = config.get(configs.MAIN_SECTION, 'import-copy') - rp = repo.Repository.from_directory(config) + copy = config().import_copy + rp = repo.Repository(config()) if bibfile is None: cont = True bibstr = '' diff --git a/papers/commands/add_library_cmd.py b/papers/commands/add_library_cmd.py index 3161d50..7fbdd9e 100644 --- a/papers/commands/add_library_cmd.py +++ b/papers/commands/add_library_cmd.py @@ -1,18 +1,18 @@ from .. import repo from ..paper import Paper +from ..configs import config - -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('add_library', help='add a set of papers to the repository') parser.add_argument('bibfile', help='bibtex, bibtexml or bibyaml file') return parser -def command(config, ui, bibfile): +def command(ui, bibfile): """ :param bibtex bibtex file (in .bib, .bibml or .yaml format. """ - rp = repo.Repository.from_directory(config) + rp = repo.Repository.from_directory(config()) for p in Paper.many_from_bib(bibfile): rp.add_paper(p) diff --git a/papers/commands/attach_cmd.py b/papers/commands/attach_cmd.py index b0d8eb3..3b51945 100644 --- a/papers/commands/attach_cmd.py +++ b/papers/commands/attach_cmd.py @@ -1,10 +1,10 @@ from .. import repo -from .. import configs +from ..configs import config from .helpers import (add_references_argument, parse_reference, add_docfile_to_paper) -def parser(subparsers, config): +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=None, @@ -16,14 +16,14 @@ def parser(subparsers, config): return parser -def command(config, ui, copy, reference, document): +def command(ui, copy, reference, document): """ :param bibfile: bibtex file (in .bib, .bibml or .yaml format. :param docfile: path (no url yet) to a pdf or ps file """ if copy is None: - copy = config.get(configs.MAIN_SECTION, 'import-copy') - rp = repo.Repository.from_directory(config) + copy = config().import_copy + rp = repo.Repository(config()) key = parse_reference(ui, rp, reference) paper = rp.get_paper(key) try: diff --git a/papers/commands/edit_cmd.py b/papers/commands/edit_cmd.py index dd630ef..2d379ca 100644 --- a/papers/commands/edit_cmd.py +++ b/papers/commands/edit_cmd.py @@ -2,9 +2,10 @@ from ..files import editor_input from .. import repo from ..paper import get_bibentry_from_string, get_safe_metadata_from_content from .helpers import add_references_argument, parse_reference +from ..configs import config -def parser(subparsers, config): +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, @@ -13,8 +14,8 @@ def parser(subparsers, config): return parser -def command(config, ui, meta, reference): - rp = repo.Repository.from_directory(config) +def command(ui, meta, reference): + rp = repo.Repository.from_directory(config()) key = parse_reference(ui, rp, reference) paper = rp.get_paper(key) to_edit = 'bib' @@ -25,7 +26,7 @@ def command(config, ui, meta, reference): content = f.read() while True: # Get new content from user - content = editor_input(config, content) + content = editor_input(config().edit_cmd, content) new_key = key bib = None metadata = None diff --git a/papers/commands/export_cmd.py b/papers/commands/export_cmd.py index 338026a..82e1f04 100644 --- a/papers/commands/export_cmd.py +++ b/papers/commands/export_cmd.py @@ -5,9 +5,9 @@ from pybtex.database import BibliographyData from .. import repo from .. import files from .helpers import parse_references, add_references_argument +from ..configs import config - -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('export', help='export bibliography') parser.add_argument('-f', '--bib-format', default='bibtex', @@ -16,11 +16,11 @@ def parser(subparsers, config): return parser -def command(config, ui, bib_format, references): +def command(ui, bib_format, references): """ :param bib_format (in 'bibtex', 'yaml') """ - rp = repo.Repository.from_directory(config) + rp = repo.Repository.from_directory(config()) papers = [rp.get_paper(c) for c in parse_references(ui, rp, references)] if len(papers) == 0: diff --git a/papers/commands/helpers.py b/papers/commands/helpers.py index 33988b4..c9df8a7 100644 --- a/papers/commands/helpers.py +++ b/papers/commands/helpers.py @@ -42,7 +42,7 @@ def extract_doc_path_from_bibdata(paper, ui): def parse_reference(ui, rp, ref): try: - return rp.citekey_from_ref(ref) + return rp.ref2citekey(ref) except InvalidReference: ui.error("no paper with reference: %s." % color.dye(ref, color.citekey)) diff --git a/papers/commands/import_cmd.py b/papers/commands/import_cmd.py index bb2a3cd..b883310 100644 --- a/papers/commands/import_cmd.py +++ b/papers/commands/import_cmd.py @@ -1,10 +1,9 @@ from .. import repo from ..paper import Paper from .helpers import add_paper_with_docfile, extract_doc_path_from_bibdata -from .. import configs +from ..configs import config - -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('import', help='import paper(s) to the repository') parser.add_argument('bibpath', @@ -16,13 +15,13 @@ def parser(subparsers, config): return parser -def command(config, ui, bibpath, copy): +def command(ui, bibpath, copy): """ :param bibpath: path (no url yet) to a bibliography file """ if copy is None: - copy = config.get(configs.MAIN_SECTION, 'import-copy') - rp = repo.Repository.from_directory(config) + copy = config().import_copy + rp = repo.Repository.from_directory(config()) # Extract papers from bib papers = Paper.many_from_path(bibpath, fatal=False) for p in papers: diff --git a/papers/commands/init_cmd.py b/papers/commands/init_cmd.py index 948a66b..d80f649 100644 --- a/papers/commands/init_cmd.py +++ b/papers/commands/init_cmd.py @@ -3,10 +3,11 @@ import os from ..repo import Repository -from .. import configs +from ..configs import config from .. import color +from .. import files -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('init', help="initialize the papers directory") parser.add_argument('-p', '--path', default=None, @@ -17,21 +18,19 @@ def parser(subparsers, config): return parser -def command(config, ui, path, doc_dir): +def command(ui, path, doc_dir): """Create a .papers directory""" - if path is None: - papersdir = config.get('papers', 'papers-directory') - else: - papersdir = os.path.join(os.getcwd(), path) - configs.add_and_write_option('papers', 'papers-directory', papersdir) - if os.path.exists(papersdir): - if len(os.listdir(papersdir)) > 0: + if path is not None: + config().papers_dir = files.clean_path(os.getcwd(), path) + ppd = config().papers_dir + if os.path.exists(ppd) and len(os.listdir(ppd)) > 0: ui.error('directory {} is not empty.'.format( - color.dye(papersdir, color.filepath))) + color.dye(ppd, color.filepath))) ui.exit() ui.print_('Initializing papers in {}.'.format( - color.dye(papersdir, color.filepath))) - repo = Repository() - repo.init(papersdir) # Creates directories - repo.save() # Saves empty repository description \ No newline at end of file + color.dye(ppd, color.filepath))) + + repo = Repository(config(), load = False) + repo.save() + config().save() diff --git a/papers/commands/list_cmd.py b/papers/commands/list_cmd.py index bfa8edd..88f4f4b 100644 --- a/papers/commands/list_cmd.py +++ b/papers/commands/list_cmd.py @@ -2,9 +2,10 @@ from .. import pretty from .. import repo from .. import color from . import helpers +from ..configs import config -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('list', help="list papers") parser.add_argument('-k', '--citekeys-only', action='store_true', default=False, dest='citekeys', @@ -14,8 +15,8 @@ def parser(subparsers, config): return parser -def command(config, ui, citekeys, query): - rp = repo.Repository.from_directory(config) +def command(ui, citekeys, query): + rp = repo.Repository(config()) papers = [(n, p) for n, p in enumerate(rp.all_papers()) if test_paper(query, p)] ui.print_('\n'.join(helpers.paper_oneliner(p, n = n, citekey_only = citekeys) for n, p in papers)) diff --git a/papers/commands/open_cmd.py b/papers/commands/open_cmd.py index ad1c832..26623d2 100644 --- a/papers/commands/open_cmd.py +++ b/papers/commands/open_cmd.py @@ -2,12 +2,12 @@ import subprocess from .. import repo from ..paper import NoDocumentFile -from .. import configs +from ..configs import config from .. import color from .helpers import add_references_argument, parse_reference -def parser(subparsers, config): +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, @@ -16,12 +16,12 @@ def parser(subparsers, config): return parser -def command(config, ui, with_command, reference): +def command(ui, with_command, reference): rp = repo.Repository.from_directory(config) key = parse_reference(ui, rp, reference) paper = rp.get_paper(key) if with_command is None: - with_command = config.get(configs.MAIN_SECTION, 'open-cmd') + with_command = config().open_cmd try: filepath = paper.get_document_path() subprocess.Popen([with_command, filepath]) diff --git a/papers/commands/remove_cmd.py b/papers/commands/remove_cmd.py index 00147f6..8c38483 100644 --- a/papers/commands/remove_cmd.py +++ b/papers/commands/remove_cmd.py @@ -1,12 +1,12 @@ from .. import repo from .. import color -from .. import configs +from ..configs import config from .helpers import add_references_argument, parse_references from ..events import RemoveEvent -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('remove', help='removes a paper') parser.add_argument('-f', '--force', action='store_true', default=None, help="does not prompt for confirmation.") @@ -14,8 +14,8 @@ def parser(subparsers, config): return parser -def command(config, ui, force, references): - rp = repo.Repository.from_directory(config) +def command(ui, force, references): + rp = repo.Repository(config()) citekeys = parse_references(ui, rp, references) if force is None: are_you_sure = ("Are you sure you want to delete paper(s) [%s]" @@ -24,7 +24,7 @@ def command(config, ui, force, references): sure = ui.input_yn(question=are_you_sure, default='n') if force or sure: for c in citekeys: - rmevent = RemoveEvent(config, ui, c) + rmevent = RemoveEvent(ui, c) rmevent.send() - rp.remove(c) + rp.remove_paper(c) diff --git a/papers/commands/tag_cmd.py b/papers/commands/tag_cmd.py index d76deb4..0228ee3 100644 --- a/papers/commands/tag_cmd.py +++ b/papers/commands/tag_cmd.py @@ -19,8 +19,9 @@ The different use cases are : from ..repo import Repository, InvalidReference from . import helpers +from ..configs import config -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('tag', help="add, remove and show tags") parser.add_argument('referenceOrTag', nargs='?', default = None, help='reference to the paper (citekey or number), or ' @@ -43,12 +44,12 @@ def _parse_tags(s): for m in re.finditer(r'[+-]', s): if m.start() == last: if last != 0: - raise ValueError, 'could not match tag expression' + raise ValueError('could not match tag expression') else: tags.append(s[last:(m.start())]) last = m.start() if last == len(s): - raise ValueError, 'could not match tag expression' + raise ValueError('could not match tag expression') else: tags.append(s[last:]) return tags @@ -63,16 +64,16 @@ def _tag_groups(tags): minus_tags.append(tag[1:]) return set(plus_tags), set(minus_tags) -def command(config, ui, referenceOrTag, tags): +def command(ui, referenceOrTag, tags): """Add, remove and show tags""" - rp = Repository.from_directory(config) + rp = Repository(config()) if referenceOrTag is None: for tag in rp.get_tags(): ui.print_(tag) else: try: - citekey = rp.citekey_from_ref(referenceOrTag) + citekey = rp.ref2citekey(referenceOrTag) p = rp.get_paper(citekey) if tags is None: ui.print_(' '.join(p.tags)) diff --git a/papers/commands/update_cmd.py b/papers/commands/update_cmd.py index 6c521b9..6c6eb91 100644 --- a/papers/commands/update_cmd.py +++ b/papers/commands/update_cmd.py @@ -1,19 +1,60 @@ +import sys + from .. import repo from .. import color +from ..configs import config +from ..__init__ import __version__ -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('update', help='update the repository to the lastest format') return parser -def command(config, ui): - rp = repo.Repository.from_directory(config) - msg = ("You should backup the paper directory {} before continuing." - "Continue ?").format(color.dye(rp.papersdir, color.filepath)) - sure = ui.input_yn(question=msg, default='n') - if sure: - for p in rp.all_papers(): - tags = set(p.metadata['tags']) - tags = tags.union(p.metadata['labels']) - p.metadata.pop('labels', []) - rp.save_paper(p) +def command(ui): + code_version = __version__ + repo_version = int(config().version) + + if repo_version == code_version: + ui.print_('You papers repository is up-to-date.') + sys.exit(0) + elif repo_version > code_version: + ui.print_('Your repository was generated with an newer version of papers.\n' + 'You should not use papers until you install the newest version.') + sys.exit(0) + else: + msg = ("You should backup the paper directory {} before continuing." + "Continue ?").format(color.dye(config().papers_dir, color.filepath)) + sure = ui.input_yn(question=msg, default='n') + if not sure: + sys.exit(0) + + if repo_version == 1: + rp = repo.Repository(config()) + for p in rp.all_papers(): + tags = set(p.metadata['tags']) + tags = tags.union(p.metadata['labels']) + p.metadata.pop('labels', []) + rp.save_paper(p) + repo_version = 2 + + + if repo_version == 2: + # update config + print 'bla' + cfg_update = [('papers-directory', 'papers_dir'), + ('open-cmd', 'open_cmd'), + ('edit-cmd', 'edit_cmd'), + ('import-copy', 'import_copy'), + ('import-move', 'import_move'), + ] + for old, new in cfg_update: + try: + config()._cfg.set('papers', new, config()._cfg.get('papers', old)) + config()._cfg.remove_option('papers', old) + except Exception: + pass + config().save() + repo_version = 3 + + config().version = repo_version + config().save() diff --git a/papers/commands/websearch_cmd.py b/papers/commands/websearch_cmd.py index be05e4e..1f61670 100644 --- a/papers/commands/websearch_cmd.py +++ b/papers/commands/websearch_cmd.py @@ -2,7 +2,7 @@ import webbrowser import urllib -def parser(subparsers, config): +def parser(subparsers): parser = subparsers.add_parser('websearch', help="launch a search on Google Scholar") parser.add_argument("search_string", nargs = '*', @@ -10,7 +10,7 @@ def parser(subparsers, config): return parser -def command(config, ui, search_string): +def command(ui, search_string): print search_string url = ("https://scholar.google.fr/scholar?q=%s&lr=" % (urllib.quote_plus(' '.join(search_string)))) diff --git a/papers/configs.py b/papers/configs.py index ae1ed2d..5a9fbdf 100644 --- a/papers/configs.py +++ b/papers/configs.py @@ -1,59 +1,92 @@ import os -import ConfigParser +import copy +from p3 import configparser +# constant stuff (DFT = DEFAULT) -MAIN_SECTION = 'papers' -CONFIG_PATH = os.path.expanduser('~/.papersrc') -DEFAULT_PAPERS_DIRECTORY = os.path.expanduser('~/.papers') -DEFAULT_OPEN_CMD = 'open' +MAIN_SECTION = 'papers' +DFT_CONFIG_PATH = os.path.expanduser('~/.papersrc') try: - DEFAULT_EDIT_CMD = os.environ['EDITOR'] + DFT_EDIT_CMD = os.environ['EDITOR'] except KeyError: - DEFAULT_EDIT_CMD = 'vi' + DFT_EDIT_CMD = 'vi' -DEFAULT_IMPORT_COPY = 'yes' -DEFAULT_IMPORT_MOVE = 'no' -DEFAULT_COLOR = 'yes' -DEFAULT_PLUGINS = 'texnote' +DFT_PLUGINS = 'texnote' -CONFIG = ConfigParser.SafeConfigParser({ - 'papers-directory': DEFAULT_PAPERS_DIRECTORY, - 'open-cmd': DEFAULT_OPEN_CMD, - 'edit-cmd': DEFAULT_EDIT_CMD, - 'import-copy': DEFAULT_IMPORT_COPY, - 'import-move': DEFAULT_IMPORT_MOVE, - 'color': DEFAULT_COLOR, - 'plugins': DEFAULT_PLUGINS}) -CONFIG.add_section(MAIN_SECTION) +DFT_CONFIG = {'papers_dir' : os.path.expanduser('~/.papers'), + 'doc_dir' : 'doc', + 'import_copy' : 'yes', + 'import_move' : 'no', + 'color' : 'yes', + 'version' : 3, + 'open_cmd' : 'open', + 'edit_cmd' : DFT_EDIT_CMD, + 'plugins' : DFT_PLUGINS + } -def read_config(): - CONFIG.read(CONFIG_PATH) - return CONFIG +BOOLEANS = {'import_copy', 'import_move', 'color'} -def add_and_write_option(section, option, value): - cfg = ConfigParser.ConfigParser() - cfg.read(CONFIG_PATH) - if not cfg.has_section(section): - cfg.add_section(section) +# package-shared config that can be accessed using : +# from configs import config +_config = None +def config(section = MAIN_SECTION): + if _config is None: + raise ValueError, 'not config instanciated yet' + _config._section = section + return _config - cfg.set(section, option, value) +class Config(object): - f = open(CONFIG_PATH, 'w') - cfg.write(f) - f.close() + def __init__(self, **kwargs): + object.__setattr__(self, '_section', MAIN_SECTION) # active section + object.__setattr__(self, '_cfg', configparser.SafeConfigParser()) + self._cfg.add_section(self._section) + for name, value in DFT_CONFIG.items(): + self._cfg.set(self._section, name, str(value)) -def get_plugins(cfg): - return cfg.get(MAIN_SECTION, 'plugins').split() + for name, value in kwargs.items(): + self.__setattr__(name, value) + def as_global(self): + global _config + _config = self -def get_boolean(value, default = False): - value = str(value).lower() - if value in ('yes', 'true', 't', 'y', '1'): - return True - elif value in ('no', 'false', 'f', 'n', '0'): - return False - else: - return default + def load(self, path = DFT_CONFIG_PATH): + self._cfg.read(path) + return self + + def save(self, path = DFT_CONFIG_PATH): + with open(path, 'w') as f: + self._cfg.write(f) + + def __setattr__(self, name, value): + if name in ('_cfg', '_section'): + object.__setattr__(self, name, value) + else: + if type(value) is bool: + BOOLEANS.add(name) + self._cfg.set(self._section, name, str(value)) + + def __getattr__(self, name): + value = self._cfg.get(self._section, name) + if name in BOOLEANS: + value = str2bool(value) + return value + + def get(self, name, default = None): + try: + return self.__getattr__(name) + except (configparser.NoOptionError, configparser.NoSectionError): + return default + + def items(self): + for name, value in self._cfg.items(self._section): + if name in BOOLEANS: + value = str2bool(value) + yield name, value + +def str2bool(s): + return str(s).lower() in ('yes', 'true', 't', 'y', '1') diff --git a/papers/events.py b/papers/events.py index efd94d6..29771a6 100644 --- a/papers/events.py +++ b/papers/events.py @@ -28,7 +28,6 @@ class Event(object): class RemoveEvent(Event): - def __init__(self, config, ui, citekey): - self.config = config + def __init__(self, ui, citekey): self.ui = ui self.citekey = citekey diff --git a/papers/files.py b/papers/files.py index 8a0867f..b83a89a 100644 --- a/papers/files.py +++ b/papers/files.py @@ -1,3 +1,9 @@ +""" +This module can't depend on configs. +If you feel the need to import configs, you are not in the right place. +""" + + import os import subprocess import tempfile @@ -6,7 +12,6 @@ from cStringIO import StringIO import yaml from . import ui -from . import configs from . import color try: @@ -30,21 +35,21 @@ except ImportError: _papersdir = None BIB_EXTENSIONS = ['.bib', '.bibyaml', '.bibml', '.yaml'] -FORMATS_INPUT = {'bib': pybtex.database.input.bibtex, - 'xml': pybtex.database.input.bibtexml, - 'yml': pybtex.database.input.bibyaml, - 'yaml': pybtex.database.input.bibyaml, - 'bibyaml': pybtex.database.input.bibyaml} -FORMATS_OUTPUT = {'bib': pybtex.database.output.bibtex, - 'bibtex': pybtex.database.output.bibtex, - 'xml': pybtex.database.output.bibtexml, - 'yml': pybtex.database.output.bibyaml, - 'yaml': pybtex.database.output.bibyaml, +FORMATS_INPUT = {'bib' : pybtex.database.input.bibtex, + 'xml' : pybtex.database.input.bibtexml, + 'yml' : pybtex.database.input.bibyaml, + 'yaml' : pybtex.database.input.bibyaml, + 'bibyaml': pybtex.database.input.bibyaml} +FORMATS_OUTPUT = {'bib' : pybtex.database.output.bibtex, + 'bibtex' : pybtex.database.output.bibtex, + 'xml' : pybtex.database.output.bibtexml, + 'yml' : pybtex.database.output.bibyaml, + 'yaml' : pybtex.database.output.bibyaml, 'bibyaml': pybtex.database.output.bibyaml} -def clean_path(path): - return os.path.abspath(os.path.expanduser(path)) +def clean_path(*args): + return os.path.abspath(os.path.expanduser(os.path.join(*args))) def name_from_path(fullpdfpath, verbose=False): @@ -168,11 +173,10 @@ def parse_bibdata(content, format_ = None): raise ValueError, 'content format is not recognized.' -def editor_input(config, initial="", suffix=None): +def editor_input(editor, initial="", suffix=None): """Use an editor to get input""" if suffix is None: suffix = '.tmp' - editor = config.get(configs.MAIN_SECTION, 'edit-cmd') with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file: tfile_name = temp_file.name temp_file.write(initial) diff --git a/papers/paper.py b/papers/paper.py index 96577bd..679ada0 100644 --- a/papers/paper.py +++ b/papers/paper.py @@ -166,7 +166,7 @@ class Paper(object): citekey = u'{}{}'.format(u''.join(first_author.last()), year) return str2citekey(citekey) - def save_to_disc(self, bib_filepath, meta_filepath): + def save(self, bib_filepath, meta_filepath): """Creates a BibliographyData object containing a single entry and saves it to disc. """ diff --git a/papers/papers_cmd.py b/papers/papers_cmd.py index eb59bff..dfa93fa 100644 --- a/papers/papers_cmd.py +++ b/papers/papers_cmd.py @@ -28,11 +28,15 @@ cmds = collections.OrderedDict([ def execute(raw_args = sys.argv): - config = configs.read_config() + # loading config + config = configs.Config() + config.load() + config.as_global() + ui = UI(config) # Extend with plugin commands - plugin.load_plugins(config, ui, configs.get_plugins(config)) + plugin.load_plugins(ui, config.plugins.split()) for p in plugin.get_plugins().values(): cmds.update(collections.OrderedDict([(p.name, p)])) @@ -40,10 +44,9 @@ def execute(raw_args = sys.argv): subparsers = parser.add_subparsers(title="valid commands", dest="command") for cmd_mod in cmds.values(): - subparser = cmd_mod.parser(subparsers, config) # why do we return the subparser ? + subparser = cmd_mod.parser(subparsers) # why do we return the subparser ? args = parser.parse_args(raw_args[1:]) - args.config = config args.ui = ui cmd = args.command diff --git a/papers/plugin.py b/papers/plugin.py index 7794e3f..f5479a6 100644 --- a/papers/plugin.py +++ b/papers/plugin.py @@ -1,4 +1,5 @@ import importlib +from .configs import config PLUGIN_NAMESPACE = 'plugs' @@ -11,20 +12,19 @@ class PapersPlugin(object): functionality by defining a subclass of PapersPlugin and overriding the abstract methods defined here. """ - def __init__(self, config, ui): + def __init__(self, ui): """Perform one-time plugin setup. """ self.name = self.__module__.split('.')[-1] - self.config = config self.ui = ui - #config and ui and given again to stay consistent with the core papers cmd. + #ui and given again to stay consistent with the core papers cmd. #two options: #- create specific cases in script papers/papers - #- do not store self.config and self.ui and use them if needed when command is called + #- do not store self.ui and use them if needed when command is called #this may end up with a lot of function with config/ui in argument #or just keep it that way... - def parser(self, subparsers, config): + def parser(self, subparsers): """ Should retrun the parser with plugins specific command. This is a basic example """ @@ -32,7 +32,7 @@ class PapersPlugin(object): parser.add_argument('strings', nargs='*', help='the strings') return parser - def command(self, config, ui, strings): + def command(self, ui, strings): """This function will be called with argument defined in the parser above This is a basic example """ @@ -47,7 +47,7 @@ class PapersPlugin(object): raise RuntimeError("{} instance not created".format(cls.__name__)) -def load_plugins(config, ui, names): +def load_plugins(ui, names): """Imports the modules for a sequence of plugin names. Each name must be the name of a Python module under the "PLUGIN_NAMESPACE" namespace package in sys.path; the module indicated should contain the @@ -69,7 +69,7 @@ def load_plugins(config, ui, names): if isinstance(obj, type) and issubclass(obj, PapersPlugin) \ and obj != PapersPlugin: _classes.append(obj) - _instances[obj] = obj(config, ui) + _instances[obj] = obj(ui) except: ui.warning('error loading plugin {}'.format(name)) diff --git a/papers/plugs/texnote/texnote.py b/papers/plugs/texnote/texnote.py index d550156..bd33326 100644 --- a/papers/plugs/texnote/texnote.py +++ b/papers/plugs/texnote/texnote.py @@ -3,7 +3,7 @@ import shutil import subprocess from ... import repo -from ... import configs +from ...configs import config from ... import files from ...plugin import PapersPlugin from ...commands.helpers import add_references_argument, parse_reference @@ -18,7 +18,7 @@ TEXNOTE_DIR = 'texnote' class TexnotePlugin(PapersPlugin): - def parser(self, subparsers, config): + def parser(self, subparsers): parser = subparsers.add_parser(self.name, help="edit advance note in latex") sub = parser.add_subparsers(title="valid texnote commands", dest="texcmd") p = sub.add_parser("remove", help="remove a reference") @@ -29,11 +29,11 @@ class TexnotePlugin(PapersPlugin): parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None) return parser - def command(self, config, ui, texcmd, reference, view): + def command(self, ui, texcmd, reference, view): if view is not None: subprocess.Popen(['papers', 'open', reference]) if texcmd == 'edit': - open_texnote(config, ui, reference) + open_texnote(ui, reference) def toto(self): print "toto" @@ -47,7 +47,8 @@ class TexnotePlugin(PapersPlugin): def remove(rmevent): texplug = TexnotePlugin.get_instance() texplug.toto() - rp = repo.Repository.from_directory(rmevent.config) + # HACK : transfer repo via RemoveEvent, do not recreate one + rp = repo.Repository(config()) paper = rp.get_paper(parse_reference(rmevent.ui, rp, rmevent.citekey)) if 'texnote' in paper.metadata: try: @@ -59,8 +60,9 @@ def remove(rmevent): files.save_meta(paper.metadata, metapath) -def open_texnote(config, ui, ref): - rp = repo.Repository.from_directory(config) +def open_texnote(ui, ref): + # HACK : transfer repo via arguments, do not recreate one + rp = repo.Repository(config()) paper = rp.get_paper(parse_reference(ui, rp, ref)) #ugly to recode like for the doc field @@ -83,12 +85,8 @@ def open_texnote(config, ui, ref): autofill_texnote(texnote_path, paper.bibentry) #open the file using the config editor - if config.has_option(TEXNOTE_SECTION, 'edit-cmd'): - #os.system(config.get(TEXNOTE_SECTION, 'edit-cmd') + ' ' + texnote_path + " &") - subprocess.Popen([config.get(TEXNOTE_SECTION, 'edit-cmd'), texnote_path]) - else: - #os.system(config.get(configs.MAIN_SECTION, 'edit-cmd') + ' ' + texnote_path + " &") - subprocess.Popen([config.get(configs.MAIN_SECTION, 'edit-cmd'), texnote_path]) + edit_cmd = config(TEXTNOTE_SECTION).get('edit_cmd', config().edit_cmd) + subprocess.Popen([edit_cmd, texnote_path]) ##### ugly replace by proper ##### diff --git a/papers/repo.py b/papers/repo.py index 6486890..8f67424 100644 --- a/papers/repo.py +++ b/papers/repo.py @@ -1,20 +1,21 @@ import os import shutil import glob +import itertools from . import files from .paper import PaperInRepo, NoDocumentFile from . import configs -ALPHABET = 'abcdefghijklmopqrstuvwxyz' + BASE_FILE = 'papers.yaml' BIB_DIR = 'bibdata' META_DIR = 'meta' DOC_DIR = 'doc' -class CiteKeyAlreadyExists(Exception): +class CiteKeyCollision(Exception): pass @@ -24,24 +25,70 @@ class InvalidReference(Exception): class Repository(object): - def __init__(self, config=None): - self.papersdir = None - self.citekeys = [] - if config is None: - config = configs.CONFIG - self.config = config + def __init__(self, config, load = True): + """Initialize the repository. - def has_paper(self, citekey): + :param load: if load is True, load the repository from disk, + from path config.papers_dir. + """ + self.config = config + self.citekeys = [] + if load: + self.load() + + # @classmethod + # def from_directory(cls, config, papersdir=None): + # repo = cls(config) + # if papersdir is None: + # papersdir = config.papers_dir + # repo.papersdir = files.clean_path(papersdir) + # repo.load() + # return repo + + def __contains__(self, citekey): + """Allows to use 'if citekey in repo' pattern""" return citekey in self.citekeys - def get_paper(self, citekey): - """Load a paper by its citekey from disk, if necessary.""" - return PaperInRepo.load( - self, self.path_to_paper_file(citekey, 'bib'), - metapath=self.path_to_paper_file(citekey, 'meta')) + def __len__(self): + return len(self.citekeys) + + + # load, save repo + + def _init_dirs(self, autodoc = True): + """Create, if necessary, the repository directories. + + Should only be called by load or save. + """ + self.bib_dir = files.clean_path(self.config.papers_dir, BIB_DIR) + self.meta_dir = files.clean_path(self.config.papers_dir, META_DIR) + if self.config.doc_dir == 'doc': + self.doc_dir = files.clean_path(self.config.papers_dir, DOC_DIR) + else: + self.doc_dir = files.clean_path(self.config.doc_dir) + self.cfg_path = files.clean_path(self.config.papers_dir, 'papers.yaml') + + for d in [self.bib_dir, self.meta_dir, self.doc_dir]: + if not os.path.exists(d): + os.makedirs(d) + + def load(self): + """Load the repository, creating dirs if necessary""" + self._init_dirs() + repo_config = files.read_yamlfile(self.cfg_path) + self.citekeys = repo_config['citekeys'] + + def save(self): + """Save the repo, creating dirs if necessary""" + self._init_dirs() + repo_cfg = {'citekeys': self.citekeys} + files.write_yamlfile(self.cfg_path, repo_cfg) + + + # reference - def citekey_from_ref(self, ref): - """Tries to get citekey from given ref. + def ref2citekey(self, ref): + """Tries to get citekey from given reference. Ref can be a citekey or a number. """ if ref in self.citekeys: @@ -52,150 +99,112 @@ class Repository(object): except (IndexError, ValueError): raise(InvalidReference) - # creating new papers - def add_paper(self, p): + # papers + + def all_papers(self): + for key in self.citekeys: + yield self.get_paper(key) + + def get_paper(self, citekey): + """Load a paper by its citekey from disk, if necessary.""" + return PaperInRepo.load(self, self._bibfile(citekey), + self._metafile(citekey)) + + + # add, remove papers + + def add_paper(self, p, overwrite = False): if p.citekey is None: # TODO also test if citekey is valid raise(ValueError("Invalid citekey: %s." % p.citekey)) - elif self.has_paper(p.citekey): - raise(ValueError("Citekey already exists in repository: %s" - % p.citekey)) + if not overwrite and p.citekey in self: + raise CiteKeyCollision('citekey {} already in use'.format( + p.citekey)) self.citekeys.append(p.citekey) - # write paper files self.save_paper(p) - # update repository files self.save() # TODO change to logging system (17/12/2012) - print "Added: %s" % p.citekey - - def add_or_update(self, paper): - if not self.has_paper(paper.citekey): - self.add_paper(paper) - else: - self.save_paper(paper) - - def update(self, paper, old_citekey=None, overwrite=False): - """Updates a paper, eventually changing its citekey. - The paper should be in repository. If the citekey changes, - the new citekey should be free except if the overwrite argument - is set to True. - """ - if old_citekey is None: - old_citekey = paper.citekey - if old_citekey not in self.citekeys: - raise(ValueError, 'Paper not in repository. Add first') - else: - if paper.citekey == old_citekey: - self.save_paper(paper) - else: - if self.has_paper(paper.citekey): - if not overwrite: - raise(CiteKeyAlreadyExists, - "There is already a paper with citekey: %s." - % paper.citekey) - else: - self.save_paper(paper) - else: - self.add_paper(paper) - # Eventually move document file - paper = PaperInRepo.from_paper(paper, self) - try: - path = self.find_document(old_citekey) - self.import_document(paper.citekey, path) - except NoDocumentFile: - pass - self.remove(old_citekey) - - def remove(self, citekey): - paper = self.get_paper(citekey) - self.citekeys.remove(citekey) - self.save() - for f in ('bib', 'meta'): - os.remove(self.path_to_paper_file(citekey, f)) - # Eventually remove associated document + print('Added: {}'.format(p.citekey)) + return p + + def rename_paper(self, paper, new_citekey, overwrite=False): + """Modify the citekey of a paper, and propagate changes to disk""" + if paper.citekey not in self: + raise ValueError( + 'paper {} not in repository'.format(paper.citekey)) + if (not overwrite and paper.citekey != new_citekey + and new_citekey in self): + raise CiteKeyCollision('citekey {} already in use'.format( + new_citekey)) + + self.remove_paper(paper.citekey, remove_doc = False) + old_citekey = paper.citekey + paper.citekey = new_citekey + self.add_paper(paper, overwrite = overwrite) + self._move_doc(old_citekey, paper) + + def _move_doc(self, old_citekey, paper): + """Fragile. Make more robust""" try: - path = paper.get_document_path_in_repo() - os.remove(path) + old_docfile = self.find_document(old_citekey) + ext = os.path.splitext(old_docfile)[1] + new_docfile = os.path.join(self.doc_dir, paper.citekey + ext) + shutil.move(old_docfile, new_docfile) + paper.set_external_document(new_docfile) except NoDocumentFile: pass - def save_paper(self, paper): - if not self.has_paper(paper.citekey): - raise(ValueError('Paper not in repository, first add it.')) - paper.save_to_disc(self.path_to_paper_file(paper.citekey, 'bib'), - self.path_to_paper_file(paper.citekey, 'meta')) + def _bibfile(self, citekey): + return os.path.join(self.bib_dir, citekey + '.bibyaml') - def get_free_citekey(self, paper, citekey=None): - """Create a unique citekey for the given paper. - """ - if citekey is None: - citekey = paper.generate_citekey() - num = [] - while citekey + _to_suffix(num) in self.citekeys: - _str_incr(num) - return citekey + _to_suffix(num) - - def base_file_path(self): - return os.path.join(self.papersdir, 'papers.yaml') + def _metafile(self, citekey): + return os.path.join(self.meta_dir, citekey + '.meta') - def size(self): - return len(self.citekeys) + def remove_paper(self, citekey, remove_doc = True): + paper = self.get_paper(citekey) + self.citekeys.remove(citekey) + os.remove(self._metafile(citekey)) + os.remove(self._bibfile(citekey)) - def save(self): - papers_config = {'citekeys': self.citekeys} - files.write_yamlfile(self.base_file_path(), papers_config) + # Eventually remove associated document + if remove_doc: + try: + path = paper.get_document_path_in_repo() + os.remove(path) + except NoDocumentFile: + pass - def load(self): - papers_config = files.read_yamlfile(self.base_file_path()) - self.citekeys = papers_config['citekeys'] - - def init(self, papersdir): - self.papersdir = papersdir - os.makedirs(os.path.join(self.papersdir, BIB_DIR)) - os.makedirs(os.path.join(self.papersdir, META_DIR)) - doc_dir = self.get_document_directory() - if not os.path.exists(doc_dir): - os.makedirs(doc_dir) self.save() - def path_to_paper_file(self, citekey, file_): - if file_ == 'bib': - return os.path.join(self.papersdir, BIB_DIR, citekey + '.bibyaml') - elif file_ == 'meta': - return os.path.join(self.papersdir, META_DIR, citekey + '.meta') - else: - raise(ValueError("%s is not a valid paper file." % file_)) + def save_paper(self, paper): + if not paper.citekey in self: + raise(ValueError('Paper not in repository, first add it.')) + paper.save(self._bibfile(paper.citekey), + self._metafile(paper.citekey)) - def get_document_directory(self): - if self.config.has_option(configs.MAIN_SECTION, 'document-directory'): - doc_dir = self.config.get(configs.MAIN_SECTION, - 'document-directory') - else: - doc_dir = os.path.join(self.papersdir, DOC_DIR) - return files.clean_path(doc_dir) + def generate_citekey(self, paper, citekey=None): + """Create a unique citekey for the given paper.""" + if citekey is None: + citekey = paper.generate_citekey() + for n in itertools.count(): + if not citekey + _base27(n) in self.citekeys: + return citekey + _base27(n) def find_document(self, citekey): - doc_dir = self.get_document_directory() - found = glob.glob(doc_dir + "/%s.*" % citekey) + found = glob.glob('{}/{}.*'.format(self.doc_dir, citekey)) if found: return found[0] else: raise NoDocumentFile - def all_papers(self): - for key in self.citekeys: - yield self.get_paper(key) - def import_document(self, citekey, doc_file): if citekey not in self.citekeys: raise(ValueError, "Unknown citekey: %s." % citekey) else: - doc_path = self.get_document_directory() - if not (os.path.exists(doc_path) and os.path.isdir(doc_path)): - raise(NoDocumentFile, - "Document directory %s, does not exist." % doc_path) + if not os.path.isfile(doc_file): + raise ValueError("No file {} found.".format(doc_file)) ext = os.path.splitext(doc_file)[1] - new_doc_file = os.path.join(doc_path, citekey + ext) + new_doc_file = os.path.join(self.doc_dir, citekey + ext) shutil.copy(doc_file, new_doc_file) def get_tags(self): @@ -204,35 +213,12 @@ class Repository(object): tags = tags.union(p.tags) return tags - @classmethod - def from_directory(cls, config, papersdir=None): - repo = cls(config=config) - if papersdir is None: - papersdir = config.get(configs.MAIN_SECTION, 'papers-directory') - repo.papersdir = files.clean_path(papersdir) - repo.load() - return repo -def _char_incr(c): - return chr(ord(c) + 1) - - -def _str_incr(l): - """Increment a number in a list string representation. - - Numbers are represented in base 26 with letters as digits. - """ - pos = 0 - while pos < len(l): - if l[pos] == 'z': - l[pos] = 'a' - pos += 1 - else: - l[pos] = _char_incr(l[pos]) - return - l.append('a') +def _base27(n): + return _base27((n-1) // 26) + chr(97+((n-1)% 26)) if n else '' -def _to_suffix(l): - return ''.join(l[::-1]) +def _base(num, b): + q, r = divmod(num - 1, len(b)) + return _base(q, b) + b[r] if num else '' diff --git a/papers/ui.py b/papers/ui.py index 50cd382..fa6c1a4 100644 --- a/papers/ui.py +++ b/papers/ui.py @@ -3,8 +3,6 @@ import sys from .beets_ui import _encoding, input_ from . import color -from . import configs - class UI: """UI class. Stores configuration parameters and system information. @@ -12,8 +10,7 @@ class UI: def __init__(self, config): self.encoding = _encoding(config) - color_enable = configs.get_boolean(config.get('papers', 'color')) - color.setup(color_enable) + color.setup(config.color) def print_(self, *strings): """Like print, but rather than raising an error when a character diff --git a/setup.py b/setup.py index e0c3119..2512683 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages setup(name='papers', - version='1', + version='3', author='Fabien Benureau, Olivier Mangin, Jonathan Grizou', author_email='fabien.benureau+inria@gmail.com', url='', diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..1f7faa6 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +import unittest + +import testenv +from papers import configs +from papers.configs import config +from papers.p3 import configparser + +class TestConfig(unittest.TestCase): + + def test_create_config(self): + a = configs.Config() + a.as_global() + self.assertEqual(a, config()) + + def test_config_content(self): + a = configs.Config() + a.as_global() + + self.assertEqual(config().papers_dir, configs.DFT_CONFIG['papers_dir']) + self.assertEqual(config().color, configs.str2bool(configs.DFT_CONFIG['color'])) + + def test_set(self): + a = configs.Config() + a.as_global() + config().color = 'no' + self.assertEqual(config().color, False) + self.assertEqual(config('papers').color, False) + # booleans type for new variables are memorized, but not saved. + config().bla = True + self.assertEqual(config().bla, True) + self.assertEqual(config('papers').bla, True) + + with self.assertRaises(configparser.NoOptionError): + config()._cfg.get(configs.MAIN_SECTION, '_section') + + def test_reload(self): + + default_color = configs.DFT_CONFIG['color'] + + a = configs.Config() + a.as_global() + a.color = False + a.bla = 'foo' + config.color = not configs.str2bool(default_color) + self.assertEqual(config().color, not configs.str2bool(default_color)) + + b = configs.Config() + b.as_global() + self.assertEqual(b, config()) + self.assertEqual(config().color, configs.str2bool(default_color)) + + def test_exception(self): + + a = configs.Config() + a.as_global() + + with self.assertRaises(configparser.NoOptionError): + config().color2 + self.assertEqual(config().get('color2', default = 'blue'), 'blue') + + with self.assertRaises(configparser.NoSectionError): + config(section = 'bla3').color + self.assertEqual(config(section = 'bla3').get('color', default = 'green'), 'green') + self.assertEqual(config(section = 'bla3').get('color', default = config().color), True) + + def test_keywords(self): + a = configs.Config(papers_dir = '/blabla') + self.assertEqual(a.papers_dir, '/blabla') diff --git a/tests/test_events.py b/tests/test_events.py index 0b509a3..705925f 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -1,5 +1,6 @@ from unittest import TestCase +import testenv from papers.events import Event diff --git a/tests/test_paper.py b/tests/test_paper.py index c2c8446..03fa010 100644 --- a/tests/test_paper.py +++ b/tests/test_paper.py @@ -7,6 +7,7 @@ import shutil import yaml from pybtex.database import Person +import testenv import fixtures from papers.paper import Paper @@ -73,25 +74,25 @@ class TestSaveLoad(unittest.TestCase): def test_save_fails_with_no_citekey(self): p = Paper() with self.assertRaises(ValueError): - p.save_to_disc(self.dest_bibfile, self.dest_metafile) + p.save(self.dest_bibfile, self.dest_metafile) def test_save_creates_bib(self): - fixtures.turing1950.save_to_disc(self.dest_bibfile, self.dest_metafile) + fixtures.turing1950.save(self.dest_bibfile, self.dest_metafile) self.assertTrue(os.path.exists(self.dest_bibfile)) def test_save_creates_meta(self): - fixtures.turing1950.save_to_disc(self.dest_bibfile, self.dest_metafile) + fixtures.turing1950.save(self.dest_bibfile, self.dest_metafile) self.assertTrue(os.path.exists(self.dest_metafile)) def test_save_right_bib(self): - fixtures.turing1950.save_to_disc(self.dest_bibfile, self.dest_metafile) + fixtures.turing1950.save(self.dest_bibfile, self.dest_metafile) with open(self.dest_bibfile, 'r') as f: written = yaml.load(f) ok = yaml.load(BIB) self.assertEqual(written, ok) def test_save_right_meta(self): - fixtures.turing1950.save_to_disc(self.dest_bibfile, self.dest_metafile) + fixtures.turing1950.save(self.dest_bibfile, self.dest_metafile) with open(self.dest_metafile, 'r') as f: written = yaml.load(f) ok = yaml.load(META) diff --git a/tests/test_repo.py b/tests/test_repo.py index 730c44c..58ef258 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -3,31 +3,27 @@ import tempfile import shutil import os +import testenv import fixtures -from papers.repo import (Repository, _str_incr, _to_suffix, BIB_DIR, META_DIR, - CiteKeyAlreadyExists) +from papers.repo import (Repository, _base27, BIB_DIR, META_DIR, + CiteKeyCollision) from papers.paper import PaperInRepo - +from papers import configs, files class TestCitekeyGeneration(unittest.TestCase): def test_string_increment(self): - l = [] - self.assertEqual(_to_suffix(l), '') - _str_incr(l) - self.assertEqual(_to_suffix(l), 'a') - _str_incr(l) - self.assertEqual(_to_suffix(l), 'b') - l = ['z'] - _str_incr(l) - self.assertEqual(_to_suffix(l), 'aa') + self.assertEqual(_base27(0), '') + for i in range(26): + self.assertEqual(_base27(i+1), chr(97+i)) + self.assertEqual(_base27(26+i+1), 'a' + chr(97+i)) def test_generated_key_is_unique(self): - repo = Repository() + repo = Repository(configs.Config(), load = False) repo.citekeys = ['Turing1950', 'Doe2003'] - c = repo.get_free_citekey(fixtures.turing1950) + c = repo.generate_citekey(fixtures.turing1950) repo.citekeys.append('Turing1950a') - c = repo.get_free_citekey(fixtures.turing1950) + c = repo.generate_citekey(fixtures.turing1950) self.assertEqual(c, 'Turing1950b') @@ -35,8 +31,8 @@ class TestRepo(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() - self.repo = Repository() - self.repo.init(self.tmpdir) + self.repo = Repository(configs.Config(papers_dir = self.tmpdir), load = False) + self.repo.save() self.repo.add_paper(fixtures.turing1950) def tearDown(self): @@ -46,15 +42,19 @@ class TestRepo(unittest.TestCase): class TestAddPaper(TestRepo): def test_raises_value_error_on_existing_key(self): - with self.assertRaises(ValueError): + with self.assertRaises(CiteKeyCollision): self.repo.add_paper(fixtures.turing1950) def test_saves_bib(self): - self.assertTrue(os.path.exists(os.path.join(self.tmpdir, BIB_DIR, + self.assertEqual(files.clean_path(self.tmpdir, BIB_DIR), + files.clean_path(self.repo.bib_dir)) + self.assertTrue(os.path.exists(os.path.join(self.repo.bib_dir, 'Turing1950.bibyaml'))) def test_saves_meta(self): - self.assertTrue(os.path.exists(os.path.join(self.tmpdir, META_DIR, + self.assertEqual(files.clean_path(self.tmpdir, META_DIR), + files.clean_path(self.repo.meta_dir)) + self.assertTrue(os.path.exists(os.path.join(self.repo.meta_dir, 'Turing1950.meta'))) @@ -62,34 +62,37 @@ class TestUpdatePaper(TestRepo): def test_raises_value_error_on_unknown_paper(self): with self.assertRaises(ValueError): - self.repo.update(fixtures.doe2013) + self.repo.save_paper(fixtures.doe2013) with self.assertRaises(ValueError): - self.repo.update(fixtures.doe2013, old_citekey='zou') + self.repo.rename_paper(fixtures.doe2013, 'zou') def test_error_on_existing_destination(self): self.repo.add_paper(fixtures.doe2013) - with self.assertRaises(CiteKeyAlreadyExists): - self.repo.update(fixtures.turing1950, old_citekey='Doe2013') + with self.assertRaises(CiteKeyCollision): + self.repo.rename_paper(fixtures.turing1950, 'Doe2013') def test_updates_same_key(self): new = self.repo.get_paper('Turing1950') new.bibentry.fields['journal'] = u'Mind' - self.repo.update(new) + self.repo.save_paper(new) self.assertEqual(new, self.repo.get_paper('Turing1950')) def test_updates_same_key_with_old_arg(self): new = self.repo.get_paper('Turing1950') new.bibentry.fields['journal'] = u'Mind' - self.repo.update(new, old_citekey='Turing1950') + self.repo.rename_paper(new, 'Turing1950') self.assertEqual(new, self.repo.get_paper('Turing1950')) def test_update_new_key_removes_old(self): - self.repo.update(fixtures.doe2013, old_citekey='Turing1950') - self.assertFalse(self.repo.has_paper('Turing1950')) + self.repo.add_paper(fixtures.doe2013) + self.repo.rename_paper(fixtures.doe2013, 'JohnDoe2003') + self.assertFalse('Doe2003' in self.repo) def test_update_new_key_updates(self): - self.repo.update(fixtures.doe2013, old_citekey='Turing1950') - self.assertTrue(self.repo.has_paper('Doe2013')) +# self.repo.rename(fixtures.doe2013, old_citekey='Turing1950') + fixtures.doe2013.citekey = 'Doe2013' + self.repo.add_paper(fixtures.doe2013) + self.assertTrue('Doe2013' in self.repo) self.assertEqual(self.repo.get_paper('Doe2013'), PaperInRepo.from_paper(fixtures.doe2013, self.repo)) @@ -97,8 +100,8 @@ class TestUpdatePaper(TestRepo): self.repo.import_document('Turing1950', os.path.join(os.path.dirname(__file__), 'data/pagerank.pdf')) - self.repo.update(fixtures.doe2013, old_citekey='Turing1950') + self.repo.rename_paper(self.repo.get_paper('Turing1950'), 'Doe2003') self.assertFalse(os.path.exists(os.path.join( - self.repo.get_document_directory(), 'Turing1950.pdf'))) + self.repo.doc_dir, 'Turing1950.pdf'))) self.assertTrue(os.path.exists(os.path.join( - self.repo.get_document_directory(), 'Doe2013.pdf'))) + self.repo.doc_dir, 'Doe2003.pdf'))) diff --git a/tests/test_usecase.py b/tests/test_usecase.py index 0c091df..16d008d 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -1,10 +1,11 @@ -import sys, os +import sys, os, shutil, glob import unittest import pkgutil import testenv import fake_filesystem import fake_filesystem_shutil +import fake_filesystem_glob from papers import papers_cmd from papers import color @@ -12,23 +13,27 @@ from papers.p3 import io real_os = os real_open = open +real_shutil = shutil +real_glob = glob -fake_os, fake_open, fake_shutil = None, None, None +fake_os, fake_open, fake_shutil, fake_glob = None, None, None, None def _create_fake_fs(): - global fake_os, fake_open, fake_shutil + global fake_os, fake_open, fake_shutil, fake_glob fake_fs = fake_filesystem.FakeFilesystem() fake_os = fake_filesystem.FakeOsModule(fake_fs) fake_open = fake_filesystem.FakeFileOpen(fake_fs) fake_shutil = fake_filesystem_shutil.FakeShutilModule(fake_fs) + fake_glob = fake_filesystem_glob.FakeGlobModule(fake_fs) fake_fs.CreateDirectory(fake_os.path.expanduser('~')) __builtins__['open'] = fake_open __builtins__['file'] = fake_open - sys.modules['os'] = fake_os + sys.modules['os'] = fake_os sys.modules['shutil'] = fake_shutil + sys.modules['glob'] = fake_glob import papers for importer, modname, ispkg in pkgutil.walk_packages( @@ -122,7 +127,7 @@ class TestUsecase(unittest.TestCase): def test_first(self): - correct = ['Initializing papers in /paper_first/.\n', + correct = ['Initializing papers in /paper_first.\n', 'Added: Page99\n', '0: [Page99] L. Page et al. "The PageRank Citation Ranking Bringing Order to the Web" (1999) \n', '',