diff --git a/pubs/commands/export_cmd.py b/pubs/commands/export_cmd.py index 1224a03..15f2653 100644 --- a/pubs/commands/export_cmd.py +++ b/pubs/commands/export_cmd.py @@ -1,14 +1,32 @@ from __future__ import print_function +import argparse + from .. import repo from ..uis import get_ui from .. import endecoder from ..utils import resolve_citekey_list -from ..completion import CiteKeyCompletion +from ..endecoder import BIBFIELD_ORDER +from ..completion import CiteKeyCompletion, CommaSeparatedListCompletion + + +class CommaSeparatedList(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, [s for s in values.split(',') if s]) + + +class FieldCommaSeparatedListCompletion(CommaSeparatedListCompletion): + + values = BIBFIELD_ORDER def parser(subparsers, conf): parser = subparsers.add_parser('export', help='export bibliography') + parser.add_argument( + '--ignore-fields', default=[], action=CommaSeparatedList, + help='exclude field(s) from output (comma separated if multiple)' + ).completer = FieldCommaSeparatedListCompletion(conf) # parser.add_argument('-f', '--bib-format', default='bibtex', # help='export format') parser.add_argument('citekeys', nargs='*', help='one or several citekeys' @@ -36,7 +54,7 @@ def command(conf, args): bib[p.citekey] = p.bibdata exporter = endecoder.EnDecoder() - bibdata_raw = exporter.encode_bibdata(bib) + bibdata_raw = exporter.encode_bibdata(bib, args.ignore_fields) ui.message(bibdata_raw) rp.close() diff --git a/pubs/completion.py b/pubs/completion.py index 0b2054c..f08af56 100644 --- a/pubs/completion.py +++ b/pubs/completion.py @@ -57,3 +57,15 @@ class TagModifierCompletion(BaseCompleter): partial_expr = prefix[:start] t_prefix = prefix[start:] return [partial_expr + t for t in tags if t.startswith(t_prefix)] + + +class CommaSeparatedListCompletion(BaseCompleter): + + values = [] + + def _complete(self, prefix, **kwargs): + split = prefix.split(',') + item_prefix = split[-1] + partial = split[:-1] + return [','.join(partial + [x]) for x in self.values + if x.startswith(item_prefix)] diff --git a/pubs/endecoder.py b/pubs/endecoder.py index 3456fc3..ab62e41 100644 --- a/pubs/endecoder.py +++ b/pubs/endecoder.py @@ -27,6 +27,11 @@ else: BP_ENTRYTYPE_KEY = 'type' +BIBFIELD_ORDER = ['author', 'title', 'journal', 'institution', 'publisher', + 'year', 'month', 'number', 'volume', 'pages', 'link', 'doi', + 'note', 'abstract'] + + def sanitize_citekey(record): record[BP_ID_KEY] = record[BP_ID_KEY].strip('\n') return record @@ -53,10 +58,6 @@ def customizations(record): return record -bibfield_order = ['author', 'title', 'journal', 'institution', 'publisher', - 'year', 'month', 'number', 'volume', 'pages', 'link', 'doi', 'note', - 'abstract'] - class EnDecoder(object): """ Encode and decode content. @@ -69,6 +70,9 @@ class EnDecoder(object): * encode_bibdata will try to recognize exceptions """ + bwriter = bp.bwriter.BibTexWriter() + bwriter.display_order = BIBFIELD_ORDER + def encode_metadata(self, metadata): return yaml.safe_dump(metadata, allow_unicode=True, encoding=None, indent=4) @@ -76,41 +80,35 @@ class EnDecoder(object): def decode_metadata(self, metadata_raw): return yaml.safe_load(metadata_raw) - def encode_bibdata(self, bibdata): + def encode_bibdata(self, bibdata, ignore_fields=[]): """Encode bibdata """ - return '\n'.join(self._encode_bibentry(citekey, entry) - for citekey, entry in bibdata.items()) - - @staticmethod - def _encode_field(key, value): - if key == 'link': - return ', '.join(link['url'] for link in value) - elif key == 'author': - return ' and '.join(author for author in value) - elif key == 'editor': - return ' and '.join(editor['name'] for editor in value) - elif key == 'journal': - return value['name'] - elif key == 'keyword': - return ', '.join(keyword for keyword in value) - else: - return value - - @staticmethod - def _encode_bibentry(citekey, bibentry): - bibraw = '@{}{{{},\n'.format(bibentry[TYPE_KEY], citekey) - bibentry = copy.copy(bibentry) - for key in bibfield_order: - if key in bibentry: - value = bibentry.pop(key) - bibraw += ' {} = {{{}}},\n'.format( - key, EnDecoder._encode_field(key, value)) - for key, value in bibentry.items(): - if key != TYPE_KEY: - bibraw += ' {} = {{{}}},\n'.format( - key, EnDecoder._encode_field(key, value)) - bibraw += '}\n' - return bibraw + bpdata = bp.bibdatabase.BibDatabase() + bpdata.entries = [self._entry_to_bp_entry(k, copy.copy(bibdata[k]), + ignore_fields=ignore_fields) + for k in bibdata] + return self.bwriter.write(bpdata) + + def _entry_to_bp_entry(self, key, entry, ignore_fields=[]): + """Convert back entries to the format expected by bibtexparser.""" + entry[BP_ID_KEY] = key + # Convert internal 'type' to bibtexparser entrytype key + entry[BP_ENTRYTYPE_KEY] = entry.pop(TYPE_KEY) + for f in ignore_fields: + entry.pop(f, None) + if 'link' in entry: + entry['link'] = ', '.join(link['url'] for link in entry['link']) + if 'author' in entry: + entry['author'] = ' and '.join( + author for author in entry['author']) + if 'editor' in entry: + entry['editor'] = ' and '.join( + editor['name'] for editor in entry['editor']) + if 'journal' in entry: + entry['journal'] = entry['journal']['name'] + if 'keyword' in entry: + entry['keyword'] = ', '.join( + keyword for keyword in entry['keyword']) + return entry def decode_bibdata(self, bibdata): """""" diff --git a/tests/test_endecoder.py b/tests/test_endecoder.py index 4bf7a5f..68387d2 100644 --- a/tests/test_endecoder.py +++ b/tests/test_endecoder.py @@ -91,13 +91,41 @@ class TestEnDecode(unittest.TestCase): self.assertIn(u'keyword', entry) self.assertEqual(set(keywords), set(entry[u'keyword'])) - def test_endecode_metadata(self): decoder = endecoder.EnDecoder() entry = decoder.decode_metadata(metadata_raw0) metadata_output0 = decoder.encode_metadata(entry) self.assertEqual(set(metadata_raw0.split('\n')), set(metadata_output0.split('\n'))) + def test_endecode_bibtex_field_order(self): + decoder = endecoder.EnDecoder() + entry = decoder.decode_bibdata(bibtex_raw0) + lines = decoder.encode_bibdata(entry).splitlines() + self.assertEqual(lines[1].split('=')[0].strip(), u'author') + self.assertEqual(lines[2].split('=')[0].strip(), u'title') + self.assertEqual(lines[3].split('=')[0].strip(), u'institution') + self.assertEqual(lines[4].split('=')[0].strip(), u'publisher') + self.assertEqual(lines[5].split('=')[0].strip(), u'year') + self.assertEqual(lines[6].split('=')[0].strip(), u'month') + self.assertEqual(lines[7].split('=')[0].strip(), u'number') + self.assertEqual(lines[8].split('=')[0].strip(), u'link') + self.assertEqual(lines[9].split('=')[0].strip(), u'note') + self.assertEqual(lines[10].split('=')[0].strip(), u'abstract') + + def test_endecode_bibtex_ignores_fields(self): + decoder = endecoder.EnDecoder() + entry = decoder.decode_bibdata(bibtex_raw0) + + bibraw1 = decoder.encode_bibdata( + entry, ignore_fields=['title', 'note', 'abstract', 'journal']) + entry1 = list(decoder.decode_bibdata(bibraw1).values())[0] + + self.assertNotIn('title', entry1) + self.assertNotIn('note', entry1) + self.assertNotIn('abtract', entry1) + self.assertIn('author', entry1) + self.assertIn('institution', entry1) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_usecase.py b/tests/test_usecase.py index 78c3579..0b27780 100644 --- a/tests/test_usecase.py +++ b/tests/test_usecase.py @@ -669,6 +669,16 @@ class TestUsecase(DataCommandTestCase): self.assertEqual(endecoder.EnDecoder().decode_bibdata(outs[2]), fixtures.page_bibentry) + def test_export_ignore_field(self): + cmds = ['pubs init', + ('pubs add', [str_fixtures.bibtex_external0]), + 'pubs export --ignore-fields author,title Page99', + ] + outs = self.execute_cmds(cmds) + expected = endecoder.EnDecoder().encode_bibdata( + fixtures.page_bibentry, ignore_fields=['author', 'title']) + self.assertEqual(outs[2], expected + os.linesep) + def test_import(self): cmds = ['pubs init', 'pubs import data/',