Merge branch 'feat/config' into develop

New config interface.
Mostly rewritten repo class.
Version support.
main
Fabien Benureau 12 years ago
commit 50e7d1bdab

@ -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()

@ -0,0 +1 @@
__version__ = 3

@ -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 = ''

@ -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)

@ -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:

@ -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

@ -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:

@ -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))

@ -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:

@ -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
color.dye(ppd, color.filepath)))
repo = Repository(config(), load = False)
repo.save()
config().save()

@ -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))

@ -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])

@ -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)

@ -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))

@ -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()

@ -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))))

@ -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')

@ -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

@ -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)

@ -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.
"""

@ -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

@ -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))

@ -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 #####

@ -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 ''

@ -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

@ -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='',

@ -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')

@ -1,5 +1,6 @@
from unittest import TestCase
import testenv
from papers.events import Event

@ -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)

@ -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')))

@ -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',
'',

Loading…
Cancel
Save