Merge branch 'develop' into texnote

main
Olivier Mangin 12 years ago
commit 4fe3b45836

@ -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):
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
else:
ui.error('papers already present in {}.'.format(
color.dye(papersdir, color.filepath)))
ui.exit()

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

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

@ -21,8 +21,9 @@ cmds = collections.OrderedDict([
('remove', commands.remove_cmd),
('open', commands.open_cmd),
('websearch', commands.websearch_cmd),
('tags', commands.tags_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…
Cancel
Save