Merge branch 'develop' into texnote
This commit is contained in:
commit
4fe3b45836
1
TODO
1
TODO
@ -1,7 +1,6 @@
|
||||
TODO list
|
||||
=========
|
||||
- manage cross-references
|
||||
+ labels
|
||||
- find (authors) duplicates
|
||||
+ remove command
|
||||
- stats command
|
||||
|
@ -24,7 +24,7 @@ filepath = cyan
|
||||
def dye(s, color=end, bold=False):
|
||||
assert color[0] == '\033'
|
||||
if bold:
|
||||
s = '\033[1' + s[3:]
|
||||
color = '\033[1' + color[3:]
|
||||
return color + s + end
|
||||
|
||||
_dye = dye
|
||||
|
@ -8,5 +8,6 @@ import open_cmd
|
||||
import edit_cmd
|
||||
import remove_cmd
|
||||
import websearch_cmd
|
||||
import tags_cmd
|
||||
import tag_cmd
|
||||
import attach_cmd
|
||||
import update_cmd
|
||||
|
@ -10,7 +10,7 @@ def parser(subparsers, config):
|
||||
parser.add_argument('-b', '--bibfile',
|
||||
help='bibtex, bibtexml or bibyaml file', default=None)
|
||||
parser.add_argument('-d', '--docfile', help='pdf or ps file', default=None)
|
||||
parser.add_argument('-l', '--label', help='label associated to the paper',
|
||||
parser.add_argument('-t', '--tags', help='tags associated to the paper, separated by commas',
|
||||
default=None)
|
||||
parser.add_argument('-c', '--copy', action='store_true', default=None,
|
||||
help="copy document files into library directory (default)")
|
||||
@ -19,7 +19,7 @@ def parser(subparsers, config):
|
||||
return parser
|
||||
|
||||
|
||||
def command(config, ui, bibfile, docfile, label, copy):
|
||||
def command(config, 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
|
||||
@ -37,15 +37,15 @@ def command(config, ui, bibfile, docfile, label, copy):
|
||||
cont = False
|
||||
except Exception:
|
||||
cont = ui.input_yn(
|
||||
question='Invalid bibfile. Edit again or abort?',
|
||||
question='Invalid bibfile. Edit again ?',
|
||||
default='y')
|
||||
if not cont:
|
||||
ui.exit()
|
||||
p = Paper(bibentry=bib, citekey=key)
|
||||
else:
|
||||
p = Paper.load(bibfile)
|
||||
if label is not None:
|
||||
p.metadata['labels'] = label.split()
|
||||
if tags is not None:
|
||||
p.tags = set(tags.split(','))
|
||||
# Check if another doc file is specified in bibtex
|
||||
docfile2 = extract_doc_path_from_bibdata(p, ui)
|
||||
if docfile is None:
|
||||
|
@ -1,5 +1,6 @@
|
||||
from .. import files
|
||||
from .. import color
|
||||
from .. import pretty
|
||||
from ..repo import InvalidReference
|
||||
from ..paper import NoDocumentFile
|
||||
|
||||
@ -51,3 +52,16 @@ def parse_reference(ui, rp, ref):
|
||||
def parse_references(ui, rp, refs):
|
||||
citekeys = [parse_reference(ui, rp, ref) for ref in refs]
|
||||
return citekeys
|
||||
|
||||
def paper_oneliner(p, n = 0, citekey_only = False):
|
||||
if citekey_only:
|
||||
return p.citekey
|
||||
else:
|
||||
bibdesc = pretty.bib_oneliner(p.bibentry)
|
||||
return (u'{num:d}: [{citekey}] {descr} {tags}'.format(
|
||||
num=int(n),
|
||||
citekey=color.dye(p.citekey, color.purple),
|
||||
descr=bibdesc,
|
||||
tags=color.dye(' '.join(p.tags),
|
||||
color.purple, bold=True),
|
||||
)).encode('utf-8')
|
||||
|
@ -24,13 +24,14 @@ def command(config, ui, path, doc_dir):
|
||||
else:
|
||||
papersdir = os.path.join(os.getcwd(), path)
|
||||
configs.add_and_write_option('papers', 'papers-directory', papersdir)
|
||||
if not os.path.exists(papersdir):
|
||||
ui.print_('Initializing papers in {}.'.format(
|
||||
color.dye(papersdir, color.filepath)))
|
||||
repo = Repository()
|
||||
repo.init(papersdir) # Creates directories
|
||||
repo.save() # Saves empty repository description
|
||||
else:
|
||||
ui.error('papers already present in {}.'.format(
|
||||
color.dye(papersdir, color.filepath)))
|
||||
ui.exit()
|
||||
if os.path.exists(papersdir):
|
||||
if len(os.listdir(papersdir)) > 0:
|
||||
ui.error('directory {} is not empty.'.format(
|
||||
color.dye(papersdir, 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
|
@ -1,6 +1,7 @@
|
||||
from .. import pretty
|
||||
from .. import repo
|
||||
from .. import color
|
||||
from . import helpers
|
||||
|
||||
|
||||
def parser(subparsers, config):
|
||||
@ -9,7 +10,7 @@ def parser(subparsers, config):
|
||||
default=False, dest='citekeys',
|
||||
help='Only returns citekeys of matching papers.')
|
||||
parser.add_argument('query', nargs='*',
|
||||
help='Paper query (e.g. "year: 2000" or "labels: math")')
|
||||
help='Paper query (e.g. "year: 2000" or "tags: math")')
|
||||
return parser
|
||||
|
||||
|
||||
@ -17,20 +18,7 @@ def command(config, ui, citekeys, query):
|
||||
rp = repo.Repository.from_directory(config)
|
||||
papers = [(n, p) for n, p in enumerate(rp.all_papers())
|
||||
if test_paper(query, p)]
|
||||
if citekeys:
|
||||
paper_strings = [p.citekey for n, p in papers]
|
||||
else:
|
||||
paper_strings = []
|
||||
for n, p in papers:
|
||||
bibdesc = pretty.bib_oneliner(p.bibentry)
|
||||
paper_strings.append((u'{num:d}: [{citekey}] {descr} {labels}'.format(
|
||||
num=int(n),
|
||||
citekey=color.dye(p.citekey, color.purple),
|
||||
descr=bibdesc,
|
||||
labels=color.dye(' '.join(p.metadata.get('labels', [])),
|
||||
color.purple, bold=True),
|
||||
)).encode('utf-8'))
|
||||
ui.print_('\n'.join(paper_strings))
|
||||
ui.print_('\n'.join(helpers.paper_oneliner(p, n = n, citekey_only = citekeys) for n, p in papers))
|
||||
|
||||
|
||||
# TODO author is not implemented, should we do it by last name only or more
|
||||
@ -45,8 +33,8 @@ def test_paper(query_string, p):
|
||||
field = tmp[0]
|
||||
value = tmp[1]
|
||||
|
||||
if field in ['labels', 'l', 'tags', 't']:
|
||||
if value not in p.metadata['labels']:
|
||||
if field in ['tags', 't']:
|
||||
if value not in p.tags:
|
||||
return False
|
||||
elif field in ['author', 'authors', 'a']: # that is the very ugly
|
||||
if not 'author' in p.bibentry.persons:
|
||||
|
59
papers/commands/tag_cmd.py
Normal file
59
papers/commands/tag_cmd.py
Normal file
@ -0,0 +1,59 @@
|
||||
"""
|
||||
This command is all about tags.
|
||||
The different use cases are :
|
||||
1. > papers tag
|
||||
Returns the list of all tags
|
||||
2. > papers tag citekey
|
||||
Return the list of tags of the given citekey
|
||||
3. > papers tag citekey math
|
||||
Add 'math' to the list of tags of the given citekey
|
||||
4. > papers tag citekey :math
|
||||
Remove 'math' for the list of tags of the given citekey
|
||||
5. > papers tag citekey math,romance,:war
|
||||
Add 'math' and 'romance' tags to the given citekey, and remove the 'war' tag
|
||||
6. > papers tag math
|
||||
If 'math' is not a citekey, then display all papers with the tag 'math'
|
||||
"""
|
||||
|
||||
from ..repo import Repository, InvalidReference
|
||||
from . import helpers
|
||||
|
||||
def parser(subparsers, config):
|
||||
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 '
|
||||
'tag.')
|
||||
parser.add_argument('tags', nargs='?', default = None,
|
||||
help='If the previous argument was a reference, then '
|
||||
'then a list of tags separated by commas.')
|
||||
# TODO find a way to display clear help for multiple command semantics,
|
||||
# indistinguisable for argparse. (fabien, 201306)
|
||||
return parser
|
||||
|
||||
def command(config, ui, referenceOrTag, tags):
|
||||
"""Add, remove and show tags"""
|
||||
rp = Repository.from_directory(config)
|
||||
|
||||
if referenceOrTag is None:
|
||||
for tag in rp.get_tags():
|
||||
ui.print_(tag)
|
||||
else:
|
||||
try:
|
||||
citekey = rp.citekey_from_ref(referenceOrTag)
|
||||
p = rp.get_paper(citekey)
|
||||
if tags is None:
|
||||
ui.print_(' '.join(p.tags))
|
||||
else:
|
||||
tags = tags.split(',')
|
||||
for tag in tags:
|
||||
if tag[0] == ':':
|
||||
p.remove_tag(tag[1:])
|
||||
else:
|
||||
p.add_tag(tag)
|
||||
rp.save_paper(p)
|
||||
except InvalidReference:
|
||||
tag = referenceOrTag
|
||||
papers_list = [(p, n) for n, p in enumerate(rp.all_papers())
|
||||
if tag in p.tags]
|
||||
ui.print_('\n'.join(helpers.paper_oneliner(p, n)
|
||||
for p, n in papers_list))
|
@ -1,13 +0,0 @@
|
||||
from ..repo import Repository
|
||||
|
||||
|
||||
def parser(subparsers, config):
|
||||
parser = subparsers.add_parser('tags', help="list existing tags")
|
||||
return parser
|
||||
|
||||
|
||||
def command(config, ui):
|
||||
"""List existing tags"""
|
||||
rp = Repository.from_directory(config)
|
||||
for tag in rp.get_labels():
|
||||
ui.print_(tag)
|
19
papers/commands/update_cmd.py
Normal file
19
papers/commands/update_cmd.py
Normal file
@ -0,0 +1,19 @@
|
||||
from .. import repo
|
||||
from .. import color
|
||||
|
||||
def parser(subparsers, config):
|
||||
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)
|
@ -5,12 +5,13 @@ import urllib
|
||||
def parser(subparsers, config):
|
||||
parser = subparsers.add_parser('websearch',
|
||||
help="launch a search on Google Scholar")
|
||||
parser.add_argument("search_string",
|
||||
parser.add_argument("search_string", nargs = '*',
|
||||
help="the search query (anything googly is possible)")
|
||||
return parser
|
||||
|
||||
|
||||
def command(config, ui, search_string):
|
||||
print search_string
|
||||
url = ("https://scholar.google.fr/scholar?q=%s&lr="
|
||||
% (urllib.quote_plus(search_string)))
|
||||
% (urllib.quote_plus(' '.join(search_string))))
|
||||
webbrowser.open(url)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from cStringIO import StringIO
|
||||
|
||||
import yaml
|
||||
|
||||
@ -21,10 +22,10 @@ try:
|
||||
import pybtex.database.output.bibyaml
|
||||
|
||||
except ImportError:
|
||||
print(ui.dye('error', ui.error) +
|
||||
": you need to install Pybtex; try running 'pip install"
|
||||
print(color.dye('error', color.error) +
|
||||
": you need to install Pybtex; try running 'pip install "
|
||||
"pybtex' or 'easy_install pybtex'")
|
||||
|
||||
exit(-1)
|
||||
|
||||
_papersdir = None
|
||||
|
||||
@ -122,7 +123,7 @@ def load_externalbibfile(fullbibpath):
|
||||
filename, ext = os.path.splitext(os.path.split(fullbibpath)[1])
|
||||
if ext[1:] in FORMATS_INPUT.keys():
|
||||
with open(fullbibpath) as f:
|
||||
return parse_bibdata(f, ext[1:])
|
||||
return _parse_bibdata_formated_stream(f, ext[1:])
|
||||
else:
|
||||
print('{}: {} not recognized format for bibliography'.format(
|
||||
color.dye('error', color.error),
|
||||
@ -130,14 +131,38 @@ def load_externalbibfile(fullbibpath):
|
||||
exit(-1)
|
||||
|
||||
|
||||
def parse_bibdata(content, format_):
|
||||
"""Parse bib data from string.
|
||||
def _parse_bibdata_formated_stream(stream, fmt):
|
||||
"""Parse a stream for bibdata, using the supplied format."""
|
||||
try:
|
||||
parser = FORMATS_INPUT[fmt].Parser()
|
||||
data = parser.parse_stream(stream)
|
||||
if data.entries.keys() > 0:
|
||||
return data
|
||||
except Exception:
|
||||
pass
|
||||
raise ValueError, 'content format is not recognized.'
|
||||
|
||||
def parse_bibdata(content, format_ = None):
|
||||
"""Parse bib data from string or stream.
|
||||
|
||||
Raise ValueError if no bibdata is present.
|
||||
:content: stream
|
||||
:param format_: (bib|xml|yml)
|
||||
:param format_: (bib|xml|yml) if format is None, tries to recognize the
|
||||
format automatically.
|
||||
"""
|
||||
parser = FORMATS_INPUT[format_].Parser()
|
||||
return parser.parse_stream(content)
|
||||
fmts = [format_]
|
||||
if format_ is None:
|
||||
fmts = FORMATS_INPUT.keys()
|
||||
# we need to reuse the content
|
||||
content = content if type(content) == str else str(content.read())
|
||||
|
||||
for fmt in fmts:
|
||||
try:
|
||||
return _parse_bibdata_formated_stream(StringIO(content), fmt)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise ValueError, 'content format is not recognized.'
|
||||
|
||||
|
||||
def editor_input(config, initial="", suffix=None):
|
||||
|
@ -20,7 +20,7 @@ CITEKEY_EXCLUDE_RE = re.compile('[%s]'
|
||||
|
||||
BASE_META = {
|
||||
'external-document': None,
|
||||
'labels': [],
|
||||
'tags': [],
|
||||
'notes': [],
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ def get_bibentry_from_file(bibfile):
|
||||
def get_bibentry_from_string(content):
|
||||
"""Extract first entry (supposed to be the only one) from given file.
|
||||
"""
|
||||
bib_data = files.parse_bibdata(StringIO(content), 'yml')
|
||||
bib_data = files.parse_bibdata(StringIO(content))
|
||||
first_key = bib_data.entries.keys()[0]
|
||||
first_entry = bib_data.entries[first_key]
|
||||
return first_key, first_entry
|
||||
@ -69,6 +69,7 @@ def get_safe_metadata(meta):
|
||||
base_meta = Paper.create_meta()
|
||||
if meta is not None:
|
||||
base_meta.update(meta)
|
||||
base_meta['tags'] = set(base_meta['tags'])
|
||||
return base_meta
|
||||
|
||||
|
||||
@ -250,7 +251,27 @@ class Paper(object):
|
||||
return papers
|
||||
|
||||
|
||||
class PaperInRepo(Paper):
|
||||
# tags
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
return self.metadata.setdefault('tags', set())
|
||||
|
||||
@tags.setter
|
||||
def tags(self, value):
|
||||
if not hasattr(value, '__iter__'):
|
||||
raise ValueError, 'tags must be iterables'
|
||||
self.metadata['tags'] = set(value)
|
||||
|
||||
def add_tag(self, tag):
|
||||
self.tags.add(tag)
|
||||
|
||||
def remove_tag(self, tag):
|
||||
"""Remove a tag from a paper if present."""
|
||||
self.tags.discard(tag)
|
||||
|
||||
|
||||
class PaperInRepo(Paper): # TODO document why this class exists (fabien, 2013/06)
|
||||
|
||||
def __init__(self, repo, *args, **kwargs):
|
||||
Paper.__init__(self, *args, **kwargs)
|
||||
|
@ -11,18 +11,19 @@ from papers import commands
|
||||
from papers import plugins
|
||||
|
||||
cmds = collections.OrderedDict([
|
||||
('init', commands.init_cmd),
|
||||
('add', commands.add_cmd),
|
||||
('init', commands.init_cmd),
|
||||
('add', commands.add_cmd),
|
||||
('add_library', commands.add_library_cmd),
|
||||
('import', commands.import_cmd),
|
||||
('export', commands.export_cmd),
|
||||
('list', commands.list_cmd),
|
||||
('edit', commands.edit_cmd),
|
||||
('remove', commands.remove_cmd),
|
||||
('open', commands.open_cmd),
|
||||
('websearch', commands.websearch_cmd),
|
||||
('tags', commands.tags_cmd),
|
||||
('attach', commands.attach_cmd),
|
||||
('import', commands.import_cmd),
|
||||
('export', commands.export_cmd),
|
||||
('list', commands.list_cmd),
|
||||
('edit', commands.edit_cmd),
|
||||
('remove', commands.remove_cmd),
|
||||
('open', commands.open_cmd),
|
||||
('websearch', commands.websearch_cmd),
|
||||
('tag', commands.tag_cmd),
|
||||
('attach', commands.attach_cmd),
|
||||
('update', commands.update_cmd),
|
||||
])
|
||||
|
||||
config = configs.read_config()
|
||||
|
@ -198,11 +198,11 @@ class Repository(object):
|
||||
new_doc_file = os.path.join(doc_path, citekey + ext)
|
||||
shutil.copy(doc_file, new_doc_file)
|
||||
|
||||
def get_labels(self):
|
||||
labels = set()
|
||||
def get_tags(self):
|
||||
tags = set()
|
||||
for p in self.all_papers():
|
||||
labels = labels.union(p.metadata.get('labels', []))
|
||||
return labels
|
||||
tags = tags.union(p.tags)
|
||||
return tags
|
||||
|
||||
@classmethod
|
||||
def from_directory(cls, config, papersdir=None):
|
||||
|
@ -24,7 +24,7 @@ entries:
|
||||
META = """
|
||||
external-document: null
|
||||
notes: []
|
||||
labels: []
|
||||
tags: []
|
||||
"""
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user