Merge branch 'develop' into texnote

main
Olivier Mangin 12 years ago
commit 4fe3b45836

@ -1,7 +1,6 @@
TODO list TODO list
========= =========
- manage cross-references - manage cross-references
+ labels
- find (authors) duplicates - find (authors) duplicates
+ remove command + remove command
- stats command - stats command

@ -24,7 +24,7 @@ filepath = cyan
def dye(s, color=end, bold=False): def dye(s, color=end, bold=False):
assert color[0] == '\033' assert color[0] == '\033'
if bold: if bold:
s = '\033[1' + s[3:] color = '\033[1' + color[3:]
return color + s + end return color + s + end
_dye = dye _dye = dye

@ -8,5 +8,6 @@ import open_cmd
import edit_cmd import edit_cmd
import remove_cmd import remove_cmd
import websearch_cmd import websearch_cmd
import tags_cmd import tag_cmd
import attach_cmd import attach_cmd
import update_cmd

@ -10,7 +10,7 @@ def parser(subparsers, config):
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)
parser.add_argument('-d', '--docfile', help='pdf or ps 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) default=None)
parser.add_argument('-c', '--copy', action='store_true', default=None, parser.add_argument('-c', '--copy', action='store_true', default=None,
help="copy document files into library directory (default)") help="copy document files into library directory (default)")
@ -19,7 +19,7 @@ def parser(subparsers, config):
return parser 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 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
@ -37,15 +37,15 @@ def command(config, ui, bibfile, docfile, label, copy):
cont = False cont = False
except Exception: except Exception:
cont = ui.input_yn( cont = ui.input_yn(
question='Invalid bibfile. Edit again or abort?', question='Invalid bibfile. Edit again ?',
default='y') default='y')
if not cont: if not cont:
ui.exit() ui.exit()
p = Paper(bibentry=bib, citekey=key) p = Paper(bibentry=bib, citekey=key)
else: else:
p = Paper.load(bibfile) p = Paper.load(bibfile)
if label is not None: if tags is not None:
p.metadata['labels'] = label.split() p.tags = set(tags.split(','))
# Check if another doc file is specified in bibtex # Check if another doc file is specified in bibtex
docfile2 = extract_doc_path_from_bibdata(p, ui) docfile2 = extract_doc_path_from_bibdata(p, ui)
if docfile is None: if docfile is None:

@ -1,5 +1,6 @@
from .. import files from .. import files
from .. import color from .. import color
from .. import pretty
from ..repo import InvalidReference from ..repo import InvalidReference
from ..paper import NoDocumentFile from ..paper import NoDocumentFile
@ -51,3 +52,16 @@ def parse_reference(ui, rp, ref):
def parse_references(ui, rp, refs): def parse_references(ui, rp, refs):
citekeys = [parse_reference(ui, rp, ref) for ref in refs] citekeys = [parse_reference(ui, rp, ref) for ref in refs]
return citekeys 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: else:
papersdir = os.path.join(os.getcwd(), path) papersdir = os.path.join(os.getcwd(), path)
configs.add_and_write_option('papers', 'papers-directory', papersdir) configs.add_and_write_option('papers', 'papers-directory', papersdir)
if not os.path.exists(papersdir): 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( ui.print_('Initializing papers in {}.'.format(
color.dye(papersdir, color.filepath))) color.dye(papersdir, color.filepath)))
repo = Repository() repo = Repository()
repo.init(papersdir) # Creates directories repo.init(papersdir) # Creates directories
repo.save() # Saves empty repository description repo.save() # Saves empty repository description
else:
ui.error('papers already present in {}.'.format(
color.dye(papersdir, color.filepath)))
ui.exit()

@ -1,6 +1,7 @@
from .. import pretty from .. import pretty
from .. import repo from .. import repo
from .. import color from .. import color
from . import helpers
def parser(subparsers, config): def parser(subparsers, config):
@ -9,7 +10,7 @@ def parser(subparsers, config):
default=False, dest='citekeys', default=False, dest='citekeys',
help='Only returns citekeys of matching papers.') help='Only returns citekeys of matching papers.')
parser.add_argument('query', nargs='*', 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 return parser
@ -17,20 +18,7 @@ def command(config, ui, citekeys, query):
rp = repo.Repository.from_directory(config) rp = repo.Repository.from_directory(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)]
if citekeys: ui.print_('\n'.join(helpers.paper_oneliner(p, n = n, citekey_only = citekeys) for n, p in papers))
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))
# TODO author is not implemented, should we do it by last name only or more # 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] field = tmp[0]
value = tmp[1] value = tmp[1]
if field in ['labels', 'l', 'tags', 't']: if field in ['tags', 't']:
if value not in p.metadata['labels']: if value not in p.tags:
return False return False
elif field in ['author', 'authors', 'a']: # that is the very ugly elif field in ['author', 'authors', 'a']: # that is the very ugly
if not 'author' in p.bibentry.persons: if not 'author' in p.bibentry.persons:

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

@ -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): def parser(subparsers, config):
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", parser.add_argument("search_string", nargs = '*',
help="the search query (anything googly is possible)") help="the search query (anything googly is possible)")
return parser return parser
def command(config, ui, search_string): def command(config, ui, 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(search_string))) % (urllib.quote_plus(' '.join(search_string))))
webbrowser.open(url) webbrowser.open(url)

@ -1,6 +1,7 @@
import os import os
import subprocess import subprocess
import tempfile import tempfile
from cStringIO import StringIO
import yaml import yaml
@ -21,10 +22,10 @@ try:
import pybtex.database.output.bibyaml import pybtex.database.output.bibyaml
except ImportError: except ImportError:
print(ui.dye('error', ui.error) + print(color.dye('error', color.error) +
": you need to install Pybtex; try running 'pip install" ": you need to install Pybtex; try running 'pip install "
"pybtex' or 'easy_install pybtex'") "pybtex' or 'easy_install pybtex'")
exit(-1)
_papersdir = None _papersdir = None
@ -122,7 +123,7 @@ def load_externalbibfile(fullbibpath):
filename, ext = os.path.splitext(os.path.split(fullbibpath)[1]) filename, ext = os.path.splitext(os.path.split(fullbibpath)[1])
if ext[1:] in FORMATS_INPUT.keys(): if ext[1:] in FORMATS_INPUT.keys():
with open(fullbibpath) as f: with open(fullbibpath) as f:
return parse_bibdata(f, ext[1:]) return _parse_bibdata_formated_stream(f, ext[1:])
else: else:
print('{}: {} not recognized format for bibliography'.format( print('{}: {} not recognized format for bibliography'.format(
color.dye('error', color.error), color.dye('error', color.error),
@ -130,14 +131,38 @@ def load_externalbibfile(fullbibpath):
exit(-1) exit(-1)
def parse_bibdata(content, format_): def _parse_bibdata_formated_stream(stream, fmt):
"""Parse bib data from string. """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 :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() fmts = [format_]
return parser.parse_stream(content) 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): def editor_input(config, initial="", suffix=None):

@ -20,7 +20,7 @@ CITEKEY_EXCLUDE_RE = re.compile('[%s]'
BASE_META = { BASE_META = {
'external-document': None, 'external-document': None,
'labels': [], 'tags': [],
'notes': [], 'notes': [],
} }
@ -44,7 +44,7 @@ def get_bibentry_from_file(bibfile):
def get_bibentry_from_string(content): def get_bibentry_from_string(content):
"""Extract first entry (supposed to be the only one) from given file. """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_key = bib_data.entries.keys()[0]
first_entry = bib_data.entries[first_key] first_entry = bib_data.entries[first_key]
return first_key, first_entry return first_key, first_entry
@ -69,6 +69,7 @@ def get_safe_metadata(meta):
base_meta = Paper.create_meta() base_meta = Paper.create_meta()
if meta is not None: if meta is not None:
base_meta.update(meta) base_meta.update(meta)
base_meta['tags'] = set(base_meta['tags'])
return base_meta return base_meta
@ -250,7 +251,27 @@ class Paper(object):
return papers 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): def __init__(self, repo, *args, **kwargs):
Paper.__init__(self, *args, **kwargs) Paper.__init__(self, *args, **kwargs)

@ -21,8 +21,9 @@ cmds = collections.OrderedDict([
('remove', commands.remove_cmd), ('remove', commands.remove_cmd),
('open', commands.open_cmd), ('open', commands.open_cmd),
('websearch', commands.websearch_cmd), ('websearch', commands.websearch_cmd),
('tags', commands.tags_cmd), ('tag', commands.tag_cmd),
('attach', commands.attach_cmd), ('attach', commands.attach_cmd),
('update', commands.update_cmd),
]) ])
config = configs.read_config() config = configs.read_config()

@ -198,11 +198,11 @@ class Repository(object):
new_doc_file = os.path.join(doc_path, citekey + ext) new_doc_file = os.path.join(doc_path, citekey + ext)
shutil.copy(doc_file, new_doc_file) shutil.copy(doc_file, new_doc_file)
def get_labels(self): def get_tags(self):
labels = set() tags = set()
for p in self.all_papers(): for p in self.all_papers():
labels = labels.union(p.metadata.get('labels', [])) tags = tags.union(p.tags)
return labels return tags
@classmethod @classmethod
def from_directory(cls, config, papersdir=None): def from_directory(cls, config, papersdir=None):

@ -24,7 +24,7 @@ entries:
META = """ META = """
external-document: null external-document: null
notes: [] notes: []
labels: [] tags: []
""" """

Loading…
Cancel
Save