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}
|
This project use git-flow {nvie.com/posts/a-successful-git-branching-model}
|
||||||
as a model for branches management. In particular :
|
as a model for branches management. In particular :
|
||||||
- master is for release only
|
1. master is for release only
|
||||||
- when you commit to develop, run nosetests before. All tests should pass.
|
2. when you commit to develop, run nosetests before. All tests should pass.
|
||||||
- in feature/branches, you do whatever you want.
|
3. when you commit to develop, run nosetests before. All tests should pass.
|
||||||
- when developping a new feature, write tests for it.
|
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
|
Alignement
|
||||||
@ -52,7 +54,7 @@ Names
|
|||||||
Avoid at all cost to name a variable like a module from the package, a
|
Avoid at all cost to name a variable like a module from the package, a
|
||||||
dependency or the standart lib.
|
dependency or the standart lib.
|
||||||
This breaks coherence across the code, makes it harder to read.
|
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.
|
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
|
It keeps the interface clean, makes occasional hacks explicit, and inform other
|
||||||
developers that theses functions may need special care when uses outside their
|
developers that theses functions may need special care when uses outside their
|
||||||
natural habitat.
|
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 repo
|
||||||
from .. import files
|
from .. import files
|
||||||
from ..paper import Paper, NoDocumentFile, get_bibentry_from_string
|
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
|
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 = subparsers.add_parser('add', help='add a paper to the repository')
|
||||||
parser.add_argument('-b', '--bibfile',
|
parser.add_argument('-b', '--bibfile',
|
||||||
help='bibtex, bibtexml or bibyaml file', default=None)
|
help='bibtex, bibtexml or bibyaml file', default=None)
|
||||||
@ -19,14 +19,14 @@ def parser(subparsers, config):
|
|||||||
return parser
|
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 bibfile: bibtex file (in .bib, .bibml or .yaml format.
|
||||||
:param docfile: path (no url yet) to a pdf or ps file
|
:param docfile: path (no url yet) to a pdf or ps file
|
||||||
"""
|
"""
|
||||||
if copy is None:
|
if copy is None:
|
||||||
copy = config.get(configs.MAIN_SECTION, 'import-copy')
|
copy = config().import_copy
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository(config())
|
||||||
if bibfile is None:
|
if bibfile is None:
|
||||||
cont = True
|
cont = True
|
||||||
bibstr = ''
|
bibstr = ''
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from ..paper import Paper
|
from ..paper import Paper
|
||||||
|
from ..configs import config
|
||||||
|
|
||||||
|
def parser(subparsers):
|
||||||
def parser(subparsers, config):
|
|
||||||
parser = subparsers.add_parser('add_library',
|
parser = subparsers.add_parser('add_library',
|
||||||
help='add a set of papers to the repository')
|
help='add a set of papers to the repository')
|
||||||
parser.add_argument('bibfile', help='bibtex, bibtexml or bibyaml file')
|
parser.add_argument('bibfile', help='bibtex, bibtexml or bibyaml file')
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, bibfile):
|
def command(ui, bibfile):
|
||||||
"""
|
"""
|
||||||
:param bibtex bibtex file (in .bib, .bibml or .yaml format.
|
: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):
|
for p in Paper.many_from_bib(bibfile):
|
||||||
rp.add_paper(p)
|
rp.add_paper(p)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from .. import configs
|
from ..configs import config
|
||||||
from .helpers import (add_references_argument, parse_reference,
|
from .helpers import (add_references_argument, parse_reference,
|
||||||
add_docfile_to_paper)
|
add_docfile_to_paper)
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('attach',
|
parser = subparsers.add_parser('attach',
|
||||||
help='attach a document to an existing paper')
|
help='attach a document to an existing paper')
|
||||||
parser.add_argument('-c', '--copy', action='store_true', default=None,
|
parser.add_argument('-c', '--copy', action='store_true', default=None,
|
||||||
@ -16,14 +16,14 @@ def parser(subparsers, config):
|
|||||||
return parser
|
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 bibfile: bibtex file (in .bib, .bibml or .yaml format.
|
||||||
:param docfile: path (no url yet) to a pdf or ps file
|
:param docfile: path (no url yet) to a pdf or ps file
|
||||||
"""
|
"""
|
||||||
if copy is None:
|
if copy is None:
|
||||||
copy = config.get(configs.MAIN_SECTION, 'import-copy')
|
copy = config().import_copy
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository(config())
|
||||||
key = parse_reference(ui, rp, reference)
|
key = parse_reference(ui, rp, reference)
|
||||||
paper = rp.get_paper(key)
|
paper = rp.get_paper(key)
|
||||||
try:
|
try:
|
||||||
|
@ -2,9 +2,10 @@ from ..files import editor_input
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from ..paper import get_bibentry_from_string, get_safe_metadata_from_content
|
from ..paper import get_bibentry_from_string, get_safe_metadata_from_content
|
||||||
from .helpers import add_references_argument, parse_reference
|
from .helpers import add_references_argument, parse_reference
|
||||||
|
from ..configs import config
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('edit',
|
parser = subparsers.add_parser('edit',
|
||||||
help='open the paper bibliographic file in an editor')
|
help='open the paper bibliographic file in an editor')
|
||||||
parser.add_argument('-m', '--meta', action='store_true', default=False,
|
parser.add_argument('-m', '--meta', action='store_true', default=False,
|
||||||
@ -13,8 +14,8 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, meta, reference):
|
def command(ui, meta, reference):
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository.from_directory(config())
|
||||||
key = parse_reference(ui, rp, reference)
|
key = parse_reference(ui, rp, reference)
|
||||||
paper = rp.get_paper(key)
|
paper = rp.get_paper(key)
|
||||||
to_edit = 'bib'
|
to_edit = 'bib'
|
||||||
@ -25,7 +26,7 @@ def command(config, ui, meta, reference):
|
|||||||
content = f.read()
|
content = f.read()
|
||||||
while True:
|
while True:
|
||||||
# Get new content from user
|
# Get new content from user
|
||||||
content = editor_input(config, content)
|
content = editor_input(config().edit_cmd, content)
|
||||||
new_key = key
|
new_key = key
|
||||||
bib = None
|
bib = None
|
||||||
metadata = None
|
metadata = None
|
||||||
|
@ -5,9 +5,9 @@ from pybtex.database import BibliographyData
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from .. import files
|
from .. import files
|
||||||
from .helpers import parse_references, add_references_argument
|
from .helpers import parse_references, add_references_argument
|
||||||
|
from ..configs import config
|
||||||
|
|
||||||
|
def parser(subparsers):
|
||||||
def parser(subparsers, config):
|
|
||||||
parser = subparsers.add_parser('export',
|
parser = subparsers.add_parser('export',
|
||||||
help='export bibliography')
|
help='export bibliography')
|
||||||
parser.add_argument('-f', '--bib-format', default='bibtex',
|
parser.add_argument('-f', '--bib-format', default='bibtex',
|
||||||
@ -16,11 +16,11 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, bib_format, references):
|
def command(ui, bib_format, references):
|
||||||
"""
|
"""
|
||||||
:param bib_format (in 'bibtex', 'yaml')
|
:param bib_format (in 'bibtex', 'yaml')
|
||||||
"""
|
"""
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository.from_directory(config())
|
||||||
papers = [rp.get_paper(c)
|
papers = [rp.get_paper(c)
|
||||||
for c in parse_references(ui, rp, references)]
|
for c in parse_references(ui, rp, references)]
|
||||||
if len(papers) == 0:
|
if len(papers) == 0:
|
||||||
|
@ -42,7 +42,7 @@ def extract_doc_path_from_bibdata(paper, ui):
|
|||||||
|
|
||||||
def parse_reference(ui, rp, ref):
|
def parse_reference(ui, rp, ref):
|
||||||
try:
|
try:
|
||||||
return rp.citekey_from_ref(ref)
|
return rp.ref2citekey(ref)
|
||||||
except InvalidReference:
|
except InvalidReference:
|
||||||
ui.error("no paper with reference: %s."
|
ui.error("no paper with reference: %s."
|
||||||
% color.dye(ref, color.citekey))
|
% color.dye(ref, color.citekey))
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from ..paper import Paper
|
from ..paper import Paper
|
||||||
from .helpers import add_paper_with_docfile, extract_doc_path_from_bibdata
|
from .helpers import add_paper_with_docfile, extract_doc_path_from_bibdata
|
||||||
from .. import configs
|
from ..configs import config
|
||||||
|
|
||||||
|
def parser(subparsers):
|
||||||
def parser(subparsers, config):
|
|
||||||
parser = subparsers.add_parser('import',
|
parser = subparsers.add_parser('import',
|
||||||
help='import paper(s) to the repository')
|
help='import paper(s) to the repository')
|
||||||
parser.add_argument('bibpath',
|
parser.add_argument('bibpath',
|
||||||
@ -16,13 +15,13 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, bibpath, copy):
|
def command(ui, bibpath, copy):
|
||||||
"""
|
"""
|
||||||
:param bibpath: path (no url yet) to a bibliography file
|
:param bibpath: path (no url yet) to a bibliography file
|
||||||
"""
|
"""
|
||||||
if copy is None:
|
if copy is None:
|
||||||
copy = config.get(configs.MAIN_SECTION, 'import-copy')
|
copy = config().import_copy
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository.from_directory(config())
|
||||||
# Extract papers from bib
|
# Extract papers from bib
|
||||||
papers = Paper.many_from_path(bibpath, fatal=False)
|
papers = Paper.many_from_path(bibpath, fatal=False)
|
||||||
for p in papers:
|
for p in papers:
|
||||||
|
@ -3,10 +3,11 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from ..repo import Repository
|
from ..repo import Repository
|
||||||
from .. import configs
|
from ..configs import config
|
||||||
from .. import color
|
from .. import color
|
||||||
|
from .. import files
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('init',
|
parser = subparsers.add_parser('init',
|
||||||
help="initialize the papers directory")
|
help="initialize the papers directory")
|
||||||
parser.add_argument('-p', '--path', default=None,
|
parser.add_argument('-p', '--path', default=None,
|
||||||
@ -17,21 +18,19 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, path, doc_dir):
|
def command(ui, path, doc_dir):
|
||||||
"""Create a .papers directory"""
|
"""Create a .papers directory"""
|
||||||
if path is None:
|
if path is not None:
|
||||||
papersdir = config.get('papers', 'papers-directory')
|
config().papers_dir = files.clean_path(os.getcwd(), path)
|
||||||
else:
|
ppd = config().papers_dir
|
||||||
papersdir = os.path.join(os.getcwd(), path)
|
if os.path.exists(ppd) and len(os.listdir(ppd)) > 0:
|
||||||
configs.add_and_write_option('papers', 'papers-directory', papersdir)
|
|
||||||
if os.path.exists(papersdir):
|
|
||||||
if len(os.listdir(papersdir)) > 0:
|
|
||||||
ui.error('directory {} is not empty.'.format(
|
ui.error('directory {} is not empty.'.format(
|
||||||
color.dye(papersdir, color.filepath)))
|
color.dye(ppd, color.filepath)))
|
||||||
ui.exit()
|
ui.exit()
|
||||||
|
|
||||||
ui.print_('Initializing papers in {}.'.format(
|
ui.print_('Initializing papers in {}.'.format(
|
||||||
color.dye(papersdir, color.filepath)))
|
color.dye(ppd, color.filepath)))
|
||||||
repo = Repository()
|
|
||||||
repo.init(papersdir) # Creates directories
|
repo = Repository(config(), load = False)
|
||||||
repo.save() # Saves empty repository description
|
repo.save()
|
||||||
|
config().save()
|
||||||
|
@ -2,9 +2,10 @@ from .. import pretty
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from .. import color
|
from .. import color
|
||||||
from . import helpers
|
from . import helpers
|
||||||
|
from ..configs import config
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('list', help="list papers")
|
parser = subparsers.add_parser('list', help="list papers")
|
||||||
parser.add_argument('-k', '--citekeys-only', action='store_true',
|
parser.add_argument('-k', '--citekeys-only', action='store_true',
|
||||||
default=False, dest='citekeys',
|
default=False, dest='citekeys',
|
||||||
@ -14,8 +15,8 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, citekeys, query):
|
def command(ui, citekeys, query):
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository(config())
|
||||||
papers = [(n, p) for n, p in enumerate(rp.all_papers())
|
papers = [(n, p) for n, p in enumerate(rp.all_papers())
|
||||||
if test_paper(query, p)]
|
if test_paper(query, p)]
|
||||||
ui.print_('\n'.join(helpers.paper_oneliner(p, n = n, citekey_only = citekeys) for n, p in papers))
|
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 .. import repo
|
||||||
from ..paper import NoDocumentFile
|
from ..paper import NoDocumentFile
|
||||||
from .. import configs
|
from ..configs import config
|
||||||
from .. import color
|
from .. import color
|
||||||
from .helpers import add_references_argument, parse_reference
|
from .helpers import add_references_argument, parse_reference
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('open',
|
parser = subparsers.add_parser('open',
|
||||||
help='open the paper in a pdf viewer')
|
help='open the paper in a pdf viewer')
|
||||||
parser.add_argument('-w', '--with', dest='with_command', default=None,
|
parser.add_argument('-w', '--with', dest='with_command', default=None,
|
||||||
@ -16,12 +16,12 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, with_command, reference):
|
def command(ui, with_command, reference):
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository.from_directory(config)
|
||||||
key = parse_reference(ui, rp, reference)
|
key = parse_reference(ui, rp, reference)
|
||||||
paper = rp.get_paper(key)
|
paper = rp.get_paper(key)
|
||||||
if with_command is None:
|
if with_command is None:
|
||||||
with_command = config.get(configs.MAIN_SECTION, 'open-cmd')
|
with_command = config().open_cmd
|
||||||
try:
|
try:
|
||||||
filepath = paper.get_document_path()
|
filepath = paper.get_document_path()
|
||||||
subprocess.Popen([with_command, filepath])
|
subprocess.Popen([with_command, filepath])
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from .. import repo
|
from .. import repo
|
||||||
from .. import color
|
from .. import color
|
||||||
from .. import configs
|
from ..configs import config
|
||||||
from .helpers import add_references_argument, parse_references
|
from .helpers import add_references_argument, parse_references
|
||||||
|
|
||||||
from ..events import RemoveEvent
|
from ..events import RemoveEvent
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('remove', help='removes a paper')
|
parser = subparsers.add_parser('remove', help='removes a paper')
|
||||||
parser.add_argument('-f', '--force', action='store_true', default=None,
|
parser.add_argument('-f', '--force', action='store_true', default=None,
|
||||||
help="does not prompt for confirmation.")
|
help="does not prompt for confirmation.")
|
||||||
@ -14,8 +14,8 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, force, references):
|
def command(ui, force, references):
|
||||||
rp = repo.Repository.from_directory(config)
|
rp = repo.Repository(config())
|
||||||
citekeys = parse_references(ui, rp, references)
|
citekeys = parse_references(ui, rp, references)
|
||||||
if force is None:
|
if force is None:
|
||||||
are_you_sure = ("Are you sure you want to delete paper(s) [%s]"
|
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')
|
sure = ui.input_yn(question=are_you_sure, default='n')
|
||||||
if force or sure:
|
if force or sure:
|
||||||
for c in citekeys:
|
for c in citekeys:
|
||||||
rmevent = RemoveEvent(config, ui, c)
|
rmevent = RemoveEvent(ui, c)
|
||||||
rmevent.send()
|
rmevent.send()
|
||||||
|
|
||||||
rp.remove(c)
|
rp.remove_paper(c)
|
||||||
|
@ -19,8 +19,9 @@ The different use cases are :
|
|||||||
|
|
||||||
from ..repo import Repository, InvalidReference
|
from ..repo import Repository, InvalidReference
|
||||||
from . import helpers
|
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 = subparsers.add_parser('tag', help="add, remove and show tags")
|
||||||
parser.add_argument('referenceOrTag', nargs='?', default = None,
|
parser.add_argument('referenceOrTag', nargs='?', default = None,
|
||||||
help='reference to the paper (citekey or number), or '
|
help='reference to the paper (citekey or number), or '
|
||||||
@ -43,12 +44,12 @@ def _parse_tags(s):
|
|||||||
for m in re.finditer(r'[+-]', s):
|
for m in re.finditer(r'[+-]', s):
|
||||||
if m.start() == last:
|
if m.start() == last:
|
||||||
if last != 0:
|
if last != 0:
|
||||||
raise ValueError, 'could not match tag expression'
|
raise ValueError('could not match tag expression')
|
||||||
else:
|
else:
|
||||||
tags.append(s[last:(m.start())])
|
tags.append(s[last:(m.start())])
|
||||||
last = m.start()
|
last = m.start()
|
||||||
if last == len(s):
|
if last == len(s):
|
||||||
raise ValueError, 'could not match tag expression'
|
raise ValueError('could not match tag expression')
|
||||||
else:
|
else:
|
||||||
tags.append(s[last:])
|
tags.append(s[last:])
|
||||||
return tags
|
return tags
|
||||||
@ -63,16 +64,16 @@ def _tag_groups(tags):
|
|||||||
minus_tags.append(tag[1:])
|
minus_tags.append(tag[1:])
|
||||||
return set(plus_tags), set(minus_tags)
|
return set(plus_tags), set(minus_tags)
|
||||||
|
|
||||||
def command(config, ui, referenceOrTag, tags):
|
def command(ui, referenceOrTag, tags):
|
||||||
"""Add, remove and show tags"""
|
"""Add, remove and show tags"""
|
||||||
rp = Repository.from_directory(config)
|
rp = Repository(config())
|
||||||
|
|
||||||
if referenceOrTag is None:
|
if referenceOrTag is None:
|
||||||
for tag in rp.get_tags():
|
for tag in rp.get_tags():
|
||||||
ui.print_(tag)
|
ui.print_(tag)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
citekey = rp.citekey_from_ref(referenceOrTag)
|
citekey = rp.ref2citekey(referenceOrTag)
|
||||||
p = rp.get_paper(citekey)
|
p = rp.get_paper(citekey)
|
||||||
if tags is None:
|
if tags is None:
|
||||||
ui.print_(' '.join(p.tags))
|
ui.print_(' '.join(p.tags))
|
||||||
|
@ -1,19 +1,60 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
from .. import repo
|
from .. import repo
|
||||||
from .. import color
|
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')
|
parser = subparsers.add_parser('update', help='update the repository to the lastest format')
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui):
|
def command(ui):
|
||||||
rp = repo.Repository.from_directory(config)
|
code_version = __version__
|
||||||
msg = ("You should backup the paper directory {} before continuing."
|
repo_version = int(config().version)
|
||||||
"Continue ?").format(color.dye(rp.papersdir, color.filepath))
|
|
||||||
sure = ui.input_yn(question=msg, default='n')
|
if repo_version == code_version:
|
||||||
if sure:
|
ui.print_('You papers repository is up-to-date.')
|
||||||
for p in rp.all_papers():
|
sys.exit(0)
|
||||||
tags = set(p.metadata['tags'])
|
elif repo_version > code_version:
|
||||||
tags = tags.union(p.metadata['labels'])
|
ui.print_('Your repository was generated with an newer version of papers.\n'
|
||||||
p.metadata.pop('labels', [])
|
'You should not use papers until you install the newest version.')
|
||||||
rp.save_paper(p)
|
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
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
def parser(subparsers, config):
|
def parser(subparsers):
|
||||||
parser = subparsers.add_parser('websearch',
|
parser = subparsers.add_parser('websearch',
|
||||||
help="launch a search on Google Scholar")
|
help="launch a search on Google Scholar")
|
||||||
parser.add_argument("search_string", nargs = '*',
|
parser.add_argument("search_string", nargs = '*',
|
||||||
@ -10,7 +10,7 @@ def parser(subparsers, config):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def command(config, ui, search_string):
|
def command(ui, search_string):
|
||||||
print search_string
|
print search_string
|
||||||
url = ("https://scholar.google.fr/scholar?q=%s&lr="
|
url = ("https://scholar.google.fr/scholar?q=%s&lr="
|
||||||
% (urllib.quote_plus(' '.join(search_string))))
|
% (urllib.quote_plus(' '.join(search_string))))
|
||||||
|
@ -1,59 +1,92 @@
|
|||||||
import os
|
import os
|
||||||
import ConfigParser
|
import copy
|
||||||
|
from p3 import configparser
|
||||||
|
|
||||||
|
# constant stuff (DFT = DEFAULT)
|
||||||
|
|
||||||
MAIN_SECTION = 'papers'
|
MAIN_SECTION = 'papers'
|
||||||
CONFIG_PATH = os.path.expanduser('~/.papersrc')
|
DFT_CONFIG_PATH = os.path.expanduser('~/.papersrc')
|
||||||
DEFAULT_PAPERS_DIRECTORY = os.path.expanduser('~/.papers')
|
|
||||||
DEFAULT_OPEN_CMD = 'open'
|
|
||||||
try:
|
try:
|
||||||
DEFAULT_EDIT_CMD = os.environ['EDITOR']
|
DFT_EDIT_CMD = os.environ['EDITOR']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
DEFAULT_EDIT_CMD = 'vi'
|
DFT_EDIT_CMD = 'vi'
|
||||||
|
|
||||||
DEFAULT_IMPORT_COPY = 'yes'
|
DFT_PLUGINS = 'texnote'
|
||||||
DEFAULT_IMPORT_MOVE = 'no'
|
|
||||||
DEFAULT_COLOR = 'yes'
|
|
||||||
DEFAULT_PLUGINS = 'texnote'
|
|
||||||
|
|
||||||
CONFIG = ConfigParser.SafeConfigParser({
|
DFT_CONFIG = {'papers_dir' : os.path.expanduser('~/.papers'),
|
||||||
'papers-directory': DEFAULT_PAPERS_DIRECTORY,
|
'doc_dir' : 'doc',
|
||||||
'open-cmd': DEFAULT_OPEN_CMD,
|
'import_copy' : 'yes',
|
||||||
'edit-cmd': DEFAULT_EDIT_CMD,
|
'import_move' : 'no',
|
||||||
'import-copy': DEFAULT_IMPORT_COPY,
|
'color' : 'yes',
|
||||||
'import-move': DEFAULT_IMPORT_MOVE,
|
'version' : 3,
|
||||||
'color': DEFAULT_COLOR,
|
|
||||||
'plugins': DEFAULT_PLUGINS})
|
'open_cmd' : 'open',
|
||||||
CONFIG.add_section(MAIN_SECTION)
|
'edit_cmd' : DFT_EDIT_CMD,
|
||||||
|
'plugins' : DFT_PLUGINS
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOLEANS = {'import_copy', 'import_move', 'color'}
|
||||||
|
|
||||||
|
|
||||||
def read_config():
|
# package-shared config that can be accessed using :
|
||||||
CONFIG.read(CONFIG_PATH)
|
# from configs import config
|
||||||
return 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):
|
def __init__(self, **kwargs):
|
||||||
cfg = ConfigParser.ConfigParser()
|
object.__setattr__(self, '_section', MAIN_SECTION) # active section
|
||||||
cfg.read(CONFIG_PATH)
|
object.__setattr__(self, '_cfg', configparser.SafeConfigParser())
|
||||||
if not cfg.has_section(section):
|
|
||||||
cfg.add_section(section)
|
|
||||||
|
|
||||||
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')
|
for name, value in kwargs.items():
|
||||||
cfg.write(f)
|
self.__setattr__(name, value)
|
||||||
f.close()
|
|
||||||
|
|
||||||
|
def as_global(self):
|
||||||
|
global _config
|
||||||
|
_config = self
|
||||||
|
|
||||||
def get_plugins(cfg):
|
def load(self, path = DFT_CONFIG_PATH):
|
||||||
return cfg.get(MAIN_SECTION, 'plugins').split()
|
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):
|
def __setattr__(self, name, value):
|
||||||
value = str(value).lower()
|
if name in ('_cfg', '_section'):
|
||||||
if value in ('yes', 'true', 't', 'y', '1'):
|
object.__setattr__(self, name, value)
|
||||||
return True
|
else:
|
||||||
elif value in ('no', 'false', 'f', 'n', '0'):
|
if type(value) is bool:
|
||||||
return False
|
BOOLEANS.add(name)
|
||||||
else:
|
self._cfg.set(self._section, name, str(value))
|
||||||
return default
|
|
||||||
|
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):
|
class RemoveEvent(Event):
|
||||||
def __init__(self, config, ui, citekey):
|
def __init__(self, ui, citekey):
|
||||||
self.config = config
|
|
||||||
self.ui = ui
|
self.ui = ui
|
||||||
self.citekey = citekey
|
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 os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -6,7 +12,6 @@ from cStringIO import StringIO
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from . import ui
|
from . import ui
|
||||||
from . import configs
|
|
||||||
from . import color
|
from . import color
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -30,21 +35,21 @@ except ImportError:
|
|||||||
_papersdir = None
|
_papersdir = None
|
||||||
|
|
||||||
BIB_EXTENSIONS = ['.bib', '.bibyaml', '.bibml', '.yaml']
|
BIB_EXTENSIONS = ['.bib', '.bibyaml', '.bibml', '.yaml']
|
||||||
FORMATS_INPUT = {'bib': pybtex.database.input.bibtex,
|
FORMATS_INPUT = {'bib' : pybtex.database.input.bibtex,
|
||||||
'xml': pybtex.database.input.bibtexml,
|
'xml' : pybtex.database.input.bibtexml,
|
||||||
'yml': pybtex.database.input.bibyaml,
|
'yml' : pybtex.database.input.bibyaml,
|
||||||
'yaml': pybtex.database.input.bibyaml,
|
'yaml' : pybtex.database.input.bibyaml,
|
||||||
'bibyaml': pybtex.database.input.bibyaml}
|
'bibyaml': pybtex.database.input.bibyaml}
|
||||||
FORMATS_OUTPUT = {'bib': pybtex.database.output.bibtex,
|
FORMATS_OUTPUT = {'bib' : pybtex.database.output.bibtex,
|
||||||
'bibtex': pybtex.database.output.bibtex,
|
'bibtex' : pybtex.database.output.bibtex,
|
||||||
'xml': pybtex.database.output.bibtexml,
|
'xml' : pybtex.database.output.bibtexml,
|
||||||
'yml': pybtex.database.output.bibyaml,
|
'yml' : pybtex.database.output.bibyaml,
|
||||||
'yaml': pybtex.database.output.bibyaml,
|
'yaml' : pybtex.database.output.bibyaml,
|
||||||
'bibyaml': pybtex.database.output.bibyaml}
|
'bibyaml': pybtex.database.output.bibyaml}
|
||||||
|
|
||||||
|
|
||||||
def clean_path(path):
|
def clean_path(*args):
|
||||||
return os.path.abspath(os.path.expanduser(path))
|
return os.path.abspath(os.path.expanduser(os.path.join(*args)))
|
||||||
|
|
||||||
|
|
||||||
def name_from_path(fullpdfpath, verbose=False):
|
def name_from_path(fullpdfpath, verbose=False):
|
||||||
@ -168,11 +173,10 @@ def parse_bibdata(content, format_ = None):
|
|||||||
raise ValueError, 'content format is not recognized.'
|
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"""
|
"""Use an editor to get input"""
|
||||||
if suffix is None:
|
if suffix is None:
|
||||||
suffix = '.tmp'
|
suffix = '.tmp'
|
||||||
editor = config.get(configs.MAIN_SECTION, 'edit-cmd')
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
|
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
|
||||||
tfile_name = temp_file.name
|
tfile_name = temp_file.name
|
||||||
temp_file.write(initial)
|
temp_file.write(initial)
|
||||||
|
@ -166,7 +166,7 @@ class Paper(object):
|
|||||||
citekey = u'{}{}'.format(u''.join(first_author.last()), year)
|
citekey = u'{}{}'.format(u''.join(first_author.last()), year)
|
||||||
return str2citekey(citekey)
|
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
|
"""Creates a BibliographyData object containing a single entry and
|
||||||
saves it to disc.
|
saves it to disc.
|
||||||
"""
|
"""
|
||||||
|
@ -28,11 +28,15 @@ cmds = collections.OrderedDict([
|
|||||||
|
|
||||||
|
|
||||||
def execute(raw_args = sys.argv):
|
def execute(raw_args = sys.argv):
|
||||||
config = configs.read_config()
|
# loading config
|
||||||
|
config = configs.Config()
|
||||||
|
config.load()
|
||||||
|
config.as_global()
|
||||||
|
|
||||||
ui = UI(config)
|
ui = UI(config)
|
||||||
|
|
||||||
# Extend with plugin commands
|
# 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():
|
for p in plugin.get_plugins().values():
|
||||||
cmds.update(collections.OrderedDict([(p.name, p)]))
|
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")
|
subparsers = parser.add_subparsers(title="valid commands", dest="command")
|
||||||
|
|
||||||
for cmd_mod in cmds.values():
|
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 = parser.parse_args(raw_args[1:])
|
||||||
args.config = config
|
|
||||||
|
|
||||||
args.ui = ui
|
args.ui = ui
|
||||||
cmd = args.command
|
cmd = args.command
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import importlib
|
import importlib
|
||||||
|
from .configs import config
|
||||||
|
|
||||||
PLUGIN_NAMESPACE = 'plugs'
|
PLUGIN_NAMESPACE = 'plugs'
|
||||||
|
|
||||||
@ -11,20 +12,19 @@ class PapersPlugin(object):
|
|||||||
functionality by defining a subclass of PapersPlugin and overriding
|
functionality by defining a subclass of PapersPlugin and overriding
|
||||||
the abstract methods defined here.
|
the abstract methods defined here.
|
||||||
"""
|
"""
|
||||||
def __init__(self, config, ui):
|
def __init__(self, ui):
|
||||||
"""Perform one-time plugin setup.
|
"""Perform one-time plugin setup.
|
||||||
"""
|
"""
|
||||||
self.name = self.__module__.split('.')[-1]
|
self.name = self.__module__.split('.')[-1]
|
||||||
self.config = config
|
|
||||||
self.ui = ui
|
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:
|
#two options:
|
||||||
#- create specific cases in script papers/papers
|
#- 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
|
#this may end up with a lot of function with config/ui in argument
|
||||||
#or just keep it that way...
|
#or just keep it that way...
|
||||||
def parser(self, subparsers, config):
|
def parser(self, subparsers):
|
||||||
""" Should retrun the parser with plugins specific command.
|
""" Should retrun the parser with plugins specific command.
|
||||||
This is a basic example
|
This is a basic example
|
||||||
"""
|
"""
|
||||||
@ -32,7 +32,7 @@ class PapersPlugin(object):
|
|||||||
parser.add_argument('strings', nargs='*', help='the strings')
|
parser.add_argument('strings', nargs='*', help='the strings')
|
||||||
return parser
|
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 function will be called with argument defined in the parser above
|
||||||
This is a basic example
|
This is a basic example
|
||||||
"""
|
"""
|
||||||
@ -47,7 +47,7 @@ class PapersPlugin(object):
|
|||||||
raise RuntimeError("{} instance not created".format(cls.__name__))
|
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
|
"""Imports the modules for a sequence of plugin names. Each name
|
||||||
must be the name of a Python module under the "PLUGIN_NAMESPACE" namespace
|
must be the name of a Python module under the "PLUGIN_NAMESPACE" namespace
|
||||||
package in sys.path; the module indicated should contain the
|
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) \
|
if isinstance(obj, type) and issubclass(obj, PapersPlugin) \
|
||||||
and obj != PapersPlugin:
|
and obj != PapersPlugin:
|
||||||
_classes.append(obj)
|
_classes.append(obj)
|
||||||
_instances[obj] = obj(config, ui)
|
_instances[obj] = obj(ui)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
ui.warning('error loading plugin {}'.format(name))
|
ui.warning('error loading plugin {}'.format(name))
|
||||||
|
@ -3,7 +3,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from ... import repo
|
from ... import repo
|
||||||
from ... import configs
|
from ...configs import config
|
||||||
from ... import files
|
from ... import files
|
||||||
from ...plugin import PapersPlugin
|
from ...plugin import PapersPlugin
|
||||||
from ...commands.helpers import add_references_argument, parse_reference
|
from ...commands.helpers import add_references_argument, parse_reference
|
||||||
@ -18,7 +18,7 @@ TEXNOTE_DIR = 'texnote'
|
|||||||
|
|
||||||
class TexnotePlugin(PapersPlugin):
|
class TexnotePlugin(PapersPlugin):
|
||||||
|
|
||||||
def parser(self, subparsers, config):
|
def parser(self, subparsers):
|
||||||
parser = subparsers.add_parser(self.name, help="edit advance note in latex")
|
parser = subparsers.add_parser(self.name, help="edit advance note in latex")
|
||||||
sub = parser.add_subparsers(title="valid texnote commands", dest="texcmd")
|
sub = parser.add_subparsers(title="valid texnote commands", dest="texcmd")
|
||||||
p = sub.add_parser("remove", help="remove a reference")
|
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)
|
parser.add_argument('-v', '--view', action='store_true', help='open the paper in a pdf viewer', default=None)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def command(self, config, ui, texcmd, reference, view):
|
def command(self, ui, texcmd, reference, view):
|
||||||
if view is not None:
|
if view is not None:
|
||||||
subprocess.Popen(['papers', 'open', reference])
|
subprocess.Popen(['papers', 'open', reference])
|
||||||
if texcmd == 'edit':
|
if texcmd == 'edit':
|
||||||
open_texnote(config, ui, reference)
|
open_texnote(ui, reference)
|
||||||
|
|
||||||
def toto(self):
|
def toto(self):
|
||||||
print "toto"
|
print "toto"
|
||||||
@ -47,7 +47,8 @@ class TexnotePlugin(PapersPlugin):
|
|||||||
def remove(rmevent):
|
def remove(rmevent):
|
||||||
texplug = TexnotePlugin.get_instance()
|
texplug = TexnotePlugin.get_instance()
|
||||||
texplug.toto()
|
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))
|
paper = rp.get_paper(parse_reference(rmevent.ui, rp, rmevent.citekey))
|
||||||
if 'texnote' in paper.metadata:
|
if 'texnote' in paper.metadata:
|
||||||
try:
|
try:
|
||||||
@ -59,8 +60,9 @@ def remove(rmevent):
|
|||||||
files.save_meta(paper.metadata, metapath)
|
files.save_meta(paper.metadata, metapath)
|
||||||
|
|
||||||
|
|
||||||
def open_texnote(config, ui, ref):
|
def open_texnote(ui, ref):
|
||||||
rp = repo.Repository.from_directory(config)
|
# HACK : transfer repo via arguments, do not recreate one
|
||||||
|
rp = repo.Repository(config())
|
||||||
paper = rp.get_paper(parse_reference(ui, rp, ref))
|
paper = rp.get_paper(parse_reference(ui, rp, ref))
|
||||||
|
|
||||||
#ugly to recode like for the doc field
|
#ugly to recode like for the doc field
|
||||||
@ -83,12 +85,8 @@ def open_texnote(config, ui, ref):
|
|||||||
autofill_texnote(texnote_path, paper.bibentry)
|
autofill_texnote(texnote_path, paper.bibentry)
|
||||||
|
|
||||||
#open the file using the config editor
|
#open the file using the config editor
|
||||||
if config.has_option(TEXNOTE_SECTION, 'edit-cmd'):
|
edit_cmd = config(TEXTNOTE_SECTION).get('edit_cmd', config().edit_cmd)
|
||||||
#os.system(config.get(TEXNOTE_SECTION, 'edit-cmd') + ' ' + texnote_path + " &")
|
subprocess.Popen([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])
|
|
||||||
|
|
||||||
|
|
||||||
##### ugly replace by proper #####
|
##### ugly replace by proper #####
|
||||||
|
340
papers/repo.py
340
papers/repo.py
@ -1,20 +1,21 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import glob
|
import glob
|
||||||
|
import itertools
|
||||||
|
|
||||||
from . import files
|
from . import files
|
||||||
from .paper import PaperInRepo, NoDocumentFile
|
from .paper import PaperInRepo, NoDocumentFile
|
||||||
from . import configs
|
from . import configs
|
||||||
|
|
||||||
|
|
||||||
ALPHABET = 'abcdefghijklmopqrstuvwxyz'
|
|
||||||
BASE_FILE = 'papers.yaml'
|
BASE_FILE = 'papers.yaml'
|
||||||
BIB_DIR = 'bibdata'
|
BIB_DIR = 'bibdata'
|
||||||
META_DIR = 'meta'
|
META_DIR = 'meta'
|
||||||
DOC_DIR = 'doc'
|
DOC_DIR = 'doc'
|
||||||
|
|
||||||
|
|
||||||
class CiteKeyAlreadyExists(Exception):
|
class CiteKeyCollision(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@ -24,24 +25,70 @@ class InvalidReference(Exception):
|
|||||||
|
|
||||||
class Repository(object):
|
class Repository(object):
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config, load = True):
|
||||||
self.papersdir = None
|
"""Initialize the repository.
|
||||||
self.citekeys = []
|
|
||||||
if config is None:
|
|
||||||
config = configs.CONFIG
|
|
||||||
self.config = config
|
|
||||||
|
|
||||||
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
|
return citekey in self.citekeys
|
||||||
|
|
||||||
def get_paper(self, citekey):
|
def __len__(self):
|
||||||
"""Load a paper by its citekey from disk, if necessary."""
|
return len(self.citekeys)
|
||||||
return PaperInRepo.load(
|
|
||||||
self, self.path_to_paper_file(citekey, 'bib'),
|
|
||||||
metapath=self.path_to_paper_file(citekey, 'meta'))
|
|
||||||
|
|
||||||
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.
|
Ref can be a citekey or a number.
|
||||||
"""
|
"""
|
||||||
if ref in self.citekeys:
|
if ref in self.citekeys:
|
||||||
@ -52,150 +99,112 @@ class Repository(object):
|
|||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
raise(InvalidReference)
|
raise(InvalidReference)
|
||||||
|
|
||||||
# creating new papers
|
|
||||||
|
|
||||||
def add_paper(self, p):
|
# papers
|
||||||
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
|
|
||||||
|
|
||||||
def all_papers(self):
|
def all_papers(self):
|
||||||
for key in self.citekeys:
|
for key in self.citekeys:
|
||||||
yield self.get_paper(key)
|
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):
|
def import_document(self, citekey, doc_file):
|
||||||
if citekey not in self.citekeys:
|
if citekey not in self.citekeys:
|
||||||
raise(ValueError, "Unknown citekey: %s." % citekey)
|
raise(ValueError, "Unknown citekey: %s." % citekey)
|
||||||
else:
|
else:
|
||||||
doc_path = self.get_document_directory()
|
if not os.path.isfile(doc_file):
|
||||||
if not (os.path.exists(doc_path) and os.path.isdir(doc_path)):
|
raise ValueError("No file {} found.".format(doc_file))
|
||||||
raise(NoDocumentFile,
|
|
||||||
"Document directory %s, does not exist." % doc_path)
|
|
||||||
ext = os.path.splitext(doc_file)[1]
|
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)
|
shutil.copy(doc_file, new_doc_file)
|
||||||
|
|
||||||
def get_tags(self):
|
def get_tags(self):
|
||||||
@ -204,35 +213,12 @@ class Repository(object):
|
|||||||
tags = tags.union(p.tags)
|
tags = tags.union(p.tags)
|
||||||
return 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):
|
def _base(num, b):
|
||||||
"""Increment a number in a list string representation.
|
q, r = divmod(num - 1, len(b))
|
||||||
|
return _base(q, b) + b[r] if num else ''
|
||||||
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])
|
|
||||||
|
@ -3,8 +3,6 @@ import sys
|
|||||||
from .beets_ui import _encoding, input_
|
from .beets_ui import _encoding, input_
|
||||||
|
|
||||||
from . import color
|
from . import color
|
||||||
from . import configs
|
|
||||||
|
|
||||||
|
|
||||||
class UI:
|
class UI:
|
||||||
"""UI class. Stores configuration parameters and system information.
|
"""UI class. Stores configuration parameters and system information.
|
||||||
@ -12,8 +10,7 @@ class UI:
|
|||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.encoding = _encoding(config)
|
self.encoding = _encoding(config)
|
||||||
color_enable = configs.get_boolean(config.get('papers', 'color'))
|
color.setup(config.color)
|
||||||
color.setup(color_enable)
|
|
||||||
|
|
||||||
def print_(self, *strings):
|
def print_(self, *strings):
|
||||||
"""Like print, but rather than raising an error when a character
|
"""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
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
setup(name='papers',
|
setup(name='papers',
|
||||||
version='1',
|
version='3',
|
||||||
author='Fabien Benureau, Olivier Mangin, Jonathan Grizou',
|
author='Fabien Benureau, Olivier Mangin, Jonathan Grizou',
|
||||||
author_email='fabien.benureau+inria@gmail.com',
|
author_email='fabien.benureau+inria@gmail.com',
|
||||||
url='',
|
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
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import testenv
|
||||||
from papers.events import Event
|
from papers.events import Event
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import shutil
|
|||||||
import yaml
|
import yaml
|
||||||
from pybtex.database import Person
|
from pybtex.database import Person
|
||||||
|
|
||||||
|
import testenv
|
||||||
import fixtures
|
import fixtures
|
||||||
from papers.paper import Paper
|
from papers.paper import Paper
|
||||||
|
|
||||||
@ -73,25 +74,25 @@ class TestSaveLoad(unittest.TestCase):
|
|||||||
def test_save_fails_with_no_citekey(self):
|
def test_save_fails_with_no_citekey(self):
|
||||||
p = Paper()
|
p = Paper()
|
||||||
with self.assertRaises(ValueError):
|
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):
|
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))
|
self.assertTrue(os.path.exists(self.dest_bibfile))
|
||||||
|
|
||||||
def test_save_creates_meta(self):
|
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))
|
self.assertTrue(os.path.exists(self.dest_metafile))
|
||||||
|
|
||||||
def test_save_right_bib(self):
|
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:
|
with open(self.dest_bibfile, 'r') as f:
|
||||||
written = yaml.load(f)
|
written = yaml.load(f)
|
||||||
ok = yaml.load(BIB)
|
ok = yaml.load(BIB)
|
||||||
self.assertEqual(written, ok)
|
self.assertEqual(written, ok)
|
||||||
|
|
||||||
def test_save_right_meta(self):
|
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:
|
with open(self.dest_metafile, 'r') as f:
|
||||||
written = yaml.load(f)
|
written = yaml.load(f)
|
||||||
ok = yaml.load(META)
|
ok = yaml.load(META)
|
||||||
|
@ -3,31 +3,27 @@ import tempfile
|
|||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import testenv
|
||||||
import fixtures
|
import fixtures
|
||||||
from papers.repo import (Repository, _str_incr, _to_suffix, BIB_DIR, META_DIR,
|
from papers.repo import (Repository, _base27, BIB_DIR, META_DIR,
|
||||||
CiteKeyAlreadyExists)
|
CiteKeyCollision)
|
||||||
from papers.paper import PaperInRepo
|
from papers.paper import PaperInRepo
|
||||||
|
from papers import configs, files
|
||||||
|
|
||||||
class TestCitekeyGeneration(unittest.TestCase):
|
class TestCitekeyGeneration(unittest.TestCase):
|
||||||
|
|
||||||
def test_string_increment(self):
|
def test_string_increment(self):
|
||||||
l = []
|
self.assertEqual(_base27(0), '')
|
||||||
self.assertEqual(_to_suffix(l), '')
|
for i in range(26):
|
||||||
_str_incr(l)
|
self.assertEqual(_base27(i+1), chr(97+i))
|
||||||
self.assertEqual(_to_suffix(l), 'a')
|
self.assertEqual(_base27(26+i+1), 'a' + chr(97+i))
|
||||||
_str_incr(l)
|
|
||||||
self.assertEqual(_to_suffix(l), 'b')
|
|
||||||
l = ['z']
|
|
||||||
_str_incr(l)
|
|
||||||
self.assertEqual(_to_suffix(l), 'aa')
|
|
||||||
|
|
||||||
def test_generated_key_is_unique(self):
|
def test_generated_key_is_unique(self):
|
||||||
repo = Repository()
|
repo = Repository(configs.Config(), load = False)
|
||||||
repo.citekeys = ['Turing1950', 'Doe2003']
|
repo.citekeys = ['Turing1950', 'Doe2003']
|
||||||
c = repo.get_free_citekey(fixtures.turing1950)
|
c = repo.generate_citekey(fixtures.turing1950)
|
||||||
repo.citekeys.append('Turing1950a')
|
repo.citekeys.append('Turing1950a')
|
||||||
c = repo.get_free_citekey(fixtures.turing1950)
|
c = repo.generate_citekey(fixtures.turing1950)
|
||||||
self.assertEqual(c, 'Turing1950b')
|
self.assertEqual(c, 'Turing1950b')
|
||||||
|
|
||||||
|
|
||||||
@ -35,8 +31,8 @@ class TestRepo(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.tmpdir = tempfile.mkdtemp()
|
self.tmpdir = tempfile.mkdtemp()
|
||||||
self.repo = Repository()
|
self.repo = Repository(configs.Config(papers_dir = self.tmpdir), load = False)
|
||||||
self.repo.init(self.tmpdir)
|
self.repo.save()
|
||||||
self.repo.add_paper(fixtures.turing1950)
|
self.repo.add_paper(fixtures.turing1950)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -46,15 +42,19 @@ class TestRepo(unittest.TestCase):
|
|||||||
class TestAddPaper(TestRepo):
|
class TestAddPaper(TestRepo):
|
||||||
|
|
||||||
def test_raises_value_error_on_existing_key(self):
|
def test_raises_value_error_on_existing_key(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(CiteKeyCollision):
|
||||||
self.repo.add_paper(fixtures.turing1950)
|
self.repo.add_paper(fixtures.turing1950)
|
||||||
|
|
||||||
def test_saves_bib(self):
|
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')))
|
'Turing1950.bibyaml')))
|
||||||
|
|
||||||
def test_saves_meta(self):
|
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')))
|
'Turing1950.meta')))
|
||||||
|
|
||||||
|
|
||||||
@ -62,34 +62,37 @@ class TestUpdatePaper(TestRepo):
|
|||||||
|
|
||||||
def test_raises_value_error_on_unknown_paper(self):
|
def test_raises_value_error_on_unknown_paper(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.repo.update(fixtures.doe2013)
|
self.repo.save_paper(fixtures.doe2013)
|
||||||
with self.assertRaises(ValueError):
|
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):
|
def test_error_on_existing_destination(self):
|
||||||
self.repo.add_paper(fixtures.doe2013)
|
self.repo.add_paper(fixtures.doe2013)
|
||||||
with self.assertRaises(CiteKeyAlreadyExists):
|
with self.assertRaises(CiteKeyCollision):
|
||||||
self.repo.update(fixtures.turing1950, old_citekey='Doe2013')
|
self.repo.rename_paper(fixtures.turing1950, 'Doe2013')
|
||||||
|
|
||||||
def test_updates_same_key(self):
|
def test_updates_same_key(self):
|
||||||
new = self.repo.get_paper('Turing1950')
|
new = self.repo.get_paper('Turing1950')
|
||||||
new.bibentry.fields['journal'] = u'Mind'
|
new.bibentry.fields['journal'] = u'Mind'
|
||||||
self.repo.update(new)
|
self.repo.save_paper(new)
|
||||||
self.assertEqual(new, self.repo.get_paper('Turing1950'))
|
self.assertEqual(new, self.repo.get_paper('Turing1950'))
|
||||||
|
|
||||||
def test_updates_same_key_with_old_arg(self):
|
def test_updates_same_key_with_old_arg(self):
|
||||||
new = self.repo.get_paper('Turing1950')
|
new = self.repo.get_paper('Turing1950')
|
||||||
new.bibentry.fields['journal'] = u'Mind'
|
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'))
|
self.assertEqual(new, self.repo.get_paper('Turing1950'))
|
||||||
|
|
||||||
def test_update_new_key_removes_old(self):
|
def test_update_new_key_removes_old(self):
|
||||||
self.repo.update(fixtures.doe2013, old_citekey='Turing1950')
|
self.repo.add_paper(fixtures.doe2013)
|
||||||
self.assertFalse(self.repo.has_paper('Turing1950'))
|
self.repo.rename_paper(fixtures.doe2013, 'JohnDoe2003')
|
||||||
|
self.assertFalse('Doe2003' in self.repo)
|
||||||
|
|
||||||
def test_update_new_key_updates(self):
|
def test_update_new_key_updates(self):
|
||||||
self.repo.update(fixtures.doe2013, old_citekey='Turing1950')
|
# self.repo.rename(fixtures.doe2013, old_citekey='Turing1950')
|
||||||
self.assertTrue(self.repo.has_paper('Doe2013'))
|
fixtures.doe2013.citekey = 'Doe2013'
|
||||||
|
self.repo.add_paper(fixtures.doe2013)
|
||||||
|
self.assertTrue('Doe2013' in self.repo)
|
||||||
self.assertEqual(self.repo.get_paper('Doe2013'),
|
self.assertEqual(self.repo.get_paper('Doe2013'),
|
||||||
PaperInRepo.from_paper(fixtures.doe2013, self.repo))
|
PaperInRepo.from_paper(fixtures.doe2013, self.repo))
|
||||||
|
|
||||||
@ -97,8 +100,8 @@ class TestUpdatePaper(TestRepo):
|
|||||||
self.repo.import_document('Turing1950',
|
self.repo.import_document('Turing1950',
|
||||||
os.path.join(os.path.dirname(__file__),
|
os.path.join(os.path.dirname(__file__),
|
||||||
'data/pagerank.pdf'))
|
'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.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.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 unittest
|
||||||
import pkgutil
|
import pkgutil
|
||||||
|
|
||||||
import testenv
|
import testenv
|
||||||
import fake_filesystem
|
import fake_filesystem
|
||||||
import fake_filesystem_shutil
|
import fake_filesystem_shutil
|
||||||
|
import fake_filesystem_glob
|
||||||
|
|
||||||
from papers import papers_cmd
|
from papers import papers_cmd
|
||||||
from papers import color
|
from papers import color
|
||||||
@ -12,23 +13,27 @@ from papers.p3 import io
|
|||||||
|
|
||||||
real_os = os
|
real_os = os
|
||||||
real_open = open
|
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():
|
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_fs = fake_filesystem.FakeFilesystem()
|
||||||
fake_os = fake_filesystem.FakeOsModule(fake_fs)
|
fake_os = fake_filesystem.FakeOsModule(fake_fs)
|
||||||
fake_open = fake_filesystem.FakeFileOpen(fake_fs)
|
fake_open = fake_filesystem.FakeFileOpen(fake_fs)
|
||||||
fake_shutil = fake_filesystem_shutil.FakeShutilModule(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('~'))
|
fake_fs.CreateDirectory(fake_os.path.expanduser('~'))
|
||||||
__builtins__['open'] = fake_open
|
__builtins__['open'] = fake_open
|
||||||
__builtins__['file'] = fake_open
|
__builtins__['file'] = fake_open
|
||||||
|
|
||||||
sys.modules['os'] = fake_os
|
sys.modules['os'] = fake_os
|
||||||
sys.modules['shutil'] = fake_shutil
|
sys.modules['shutil'] = fake_shutil
|
||||||
|
sys.modules['glob'] = fake_glob
|
||||||
|
|
||||||
import papers
|
import papers
|
||||||
for importer, modname, ispkg in pkgutil.walk_packages(
|
for importer, modname, ispkg in pkgutil.walk_packages(
|
||||||
@ -122,7 +127,7 @@ class TestUsecase(unittest.TestCase):
|
|||||||
|
|
||||||
def test_first(self):
|
def test_first(self):
|
||||||
|
|
||||||
correct = ['Initializing papers in /paper_first/.\n',
|
correct = ['Initializing papers in /paper_first.\n',
|
||||||
'Added: Page99\n',
|
'Added: Page99\n',
|
||||||
'0: [Page99] L. Page et al. "The PageRank Citation Ranking Bringing Order to the Web" (1999) \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