Merge branch 'feat/config' into develop
New config interface. Mostly rewritten repo class. Version support.
This commit is contained in:
commit
50e7d1bdab
23
CODESTYLE.md
23
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()
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
BOOLEANS = {'import_copy', 'import_move', 'color'}
|
||||
|
||||
|
||||
def read_config():
|
||||
CONFIG.read(CONFIG_PATH)
|
||||
return CONFIG
|
||||
# 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
|
||||
|
||||
class Config(object):
|
||||
|
||||
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)
|
||||
def __init__(self, **kwargs):
|
||||
object.__setattr__(self, '_section', MAIN_SECTION) # active section
|
||||
object.__setattr__(self, '_cfg', configparser.SafeConfigParser())
|
||||
|
||||
cfg.set(section, option, value)
|
||||
self._cfg.add_section(self._section)
|
||||
for name, value in DFT_CONFIG.items():
|
||||
self._cfg.set(self._section, name, str(value))
|
||||
|
||||
f = open(CONFIG_PATH, 'w')
|
||||
cfg.write(f)
|
||||
f.close()
|
||||
for name, value in kwargs.items():
|
||||
self.__setattr__(name, value)
|
||||
|
||||
def as_global(self):
|
||||
global _config
|
||||
_config = self
|
||||
|
||||
def get_plugins(cfg):
|
||||
return cfg.get(MAIN_SECTION, 'plugins').split()
|
||||
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 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 __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 #####
|
||||
|
340
papers/repo.py
340
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)
|
||||
|
||||
def citekey_from_ref(self, ref):
|
||||
"""Tries to get citekey from given ref.
|
||||
|
||||
# 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 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):
|
||||
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))
|
||||
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
|
||||
try:
|
||||
path = paper.get_document_path_in_repo()
|
||||
os.remove(path)
|
||||
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 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 size(self):
|
||||
return len(self.citekeys)
|
||||
|
||||
def save(self):
|
||||
papers_config = {'citekeys': self.citekeys}
|
||||
files.write_yamlfile(self.base_file_path(), papers_config)
|
||||
|
||||
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 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 find_document(self, citekey):
|
||||
doc_dir = self.get_document_directory()
|
||||
found = glob.glob(doc_dir + "/%s.*" % citekey)
|
||||
if found:
|
||||
return found[0]
|
||||
else:
|
||||
raise NoDocumentFile
|
||||
# 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))
|
||||
if not overwrite and p.citekey in self:
|
||||
raise CiteKeyCollision('citekey {} already in use'.format(
|
||||
p.citekey))
|
||||
self.citekeys.append(p.citekey)
|
||||
self.save_paper(p)
|
||||
self.save()
|
||||
# TODO change to logging system (17/12/2012)
|
||||
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:
|
||||
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 _bibfile(self, citekey):
|
||||
return os.path.join(self.bib_dir, citekey + '.bibyaml')
|
||||
|
||||
def _metafile(self, citekey):
|
||||
return os.path.join(self.meta_dir, citekey + '.meta')
|
||||
|
||||
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))
|
||||
|
||||
# Eventually remove associated document
|
||||
if remove_doc:
|
||||
try:
|
||||
path = paper.get_document_path_in_repo()
|
||||
os.remove(path)
|
||||
except NoDocumentFile:
|
||||
pass
|
||||
|
||||
self.save()
|
||||
|
||||
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 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):
|
||||
found = glob.glob('{}/{}.*'.format(self.doc_dir, citekey))
|
||||
if found:
|
||||
return found[0]
|
||||
else:
|
||||
raise NoDocumentFile
|
||||
|
||||
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 _base27(n):
|
||||
return _base27((n-1) // 26) + chr(97+((n-1)% 26)) if n else ''
|
||||
|
||||
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 _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
|
||||
|
2
setup.py
2
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='',
|
||||
|
69
tests/test_config.py
Normal file
69
tests/test_config.py
Normal file
@ -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…
x
Reference in New Issue
Block a user