diff --git a/python/citation_vim/builder.py b/python/citation_vim/builder.py index 8a6b09a..021cd97 100644 --- a/python/citation_vim/builder.py +++ b/python/citation_vim/builder.py @@ -2,7 +2,6 @@ import os.path import string -import pickle from citation_vim.utils import raiseError from citation_vim.item import Item @@ -106,20 +105,6 @@ def get_parser(self): parser = ZoteroParser(self.context) return parser - def read_cache(self): - """ - Returns items from the cache file. - """ - with open(self.cache_file, 'rb') as in_file: - return pickle.load(in_file) - - def write_cache(self, items): - """ - Writes the cache file. - """ - with open(self.cache_file, 'wb') as out_file: - pickle.dump(items, out_file) - def is_cached(self): """ Returns boolean based on cache and target file dates diff --git a/python/citation_vim/utils.py b/python/citation_vim/utils.py index 6c7ea12..e53fe94 100644 --- a/python/citation_vim/utils.py +++ b/python/citation_vim/utils.py @@ -4,6 +4,7 @@ import re import os.path from datetime import datetime, timedelta +import pickle def compat_str(string): if sys.version_info[0] == 2: @@ -36,3 +37,17 @@ def raiseError(message): def strip_braces(string): return re.sub("[{.*}]+", "", string) + +def read_cache(cache_file): + """ + Returns items from the cache file. + """ + with open(cache_file, 'rb') as in_file: + return pickle.load(in_file) + +def write_cache(cache_file, items): + """ + Writes the cache file. + """ + with open(cache_file, 'wb') as out_file: + pickle.dump(items, out_file) diff --git a/rplugin/python3/denite/kind/citation.py b/rplugin/python3/denite/kind/citation.py new file mode 100644 index 0000000..1549469 --- /dev/null +++ b/rplugin/python3/denite/kind/citation.py @@ -0,0 +1,43 @@ +import os +import re +from .word import Kind as Word +from .command import Kind as Command +from .file import Kind as File +from .directory import Kind as Directory + + +class Kind(Word, Command, File, Directory): + + def __init__(self, vim): + super().__init__(vim) + + self.name = 'citation' + self.default_action = 'default' + # self.persist_actions = ['preview'] + + def action_default(self, context): + target = context['targets'][0] + source_context = target['source_context'] + + if source_context['__source'] is 'sub_sources': + self.action_field(context) + else: + Word.action_append(self, context) + + def action_field(self, context): + target = context['targets'][0] + cmd = 'Denite citation:{}'.format(target['action__source_field']) + self.vim.command(cmd) + + def action_open(self, context): + for target in context['targets']: + path = target['action__path'] + self.vim.call('denite#util#open', path) + + def action_directory(self, context): + Directory.action_open(self, context) + + def action_preview(self, context): + target = context['targets'][0] + if 'action__command' in target: + Command.action_execute(self, context) diff --git a/rplugin/python3/denite/source/citation.py b/rplugin/python3/denite/source/citation.py new file mode 100644 index 0000000..a7554e4 --- /dev/null +++ b/rplugin/python3/denite/source/citation.py @@ -0,0 +1,228 @@ +import sys +from os.path import abspath, join, pardir +from .base import Base + +sys.path.append( + abspath(join(__file__, pardir, pardir, pardir, pardir, pardir, 'python'))) +from citation_vim.utils import read_cache, write_cache, is_current, raiseError +from citation_vim.item import Item + +sub_sources = [ + "abstract", + "author", + "collection", + "combined", + "date", + "doi", + "duplicate_keys", + "file", + "isbn", + "publication", + "key", + "key_inner", + "language", + "issue", + "notes", + "pages", + "publisher", + "tags", + "title", + "type", + "url", + "volume", + "zotero_key" +] + +key_title_banned_regex = r"\b(a|an|the|some|from|on|in|to|of|do|with|der|die|das|ein|eine|einer|eines|einem|einen|un|une|la|le|l|el|las|los|al|uno|una|unos|unas|de|des|del|d)\W" +key_clean_regex = "[^A-Za-z0-9\!\$\&\*\+\-\.\/\:\;\<\>\?\[\]\^\_\`\|]+" + + +class Source(Base): + """ Zotero/Bibtex source for Denite.nvim """ + + def __init__(self, vim): + super().__init__(vim) + + self.name = 'citation' + self.kind = 'citation' + self.matchers = ['matcher_fuzzy'] + self.is_public_context = True + + self.sub_sources = sub_sources + self.__cache = False + self.__cache_file = None + self._parser = None + + self.vars = { + 'cache_path': "", + 'mode': 'zotero', + 'zotero_version': 5, + 'zotero_path': '~/Zotero', + 'zotero_attachment_path': '~/Zotero/library', + 'collection': "", + 'bibtex_file': "", + 'reverse_order': True, + 'et_al_limit': 5, + 'key_clean_regex': key_clean_regex, + 'key_title_banned_regex': key_title_banned_regex, + 'key_format': "", + 'key_outer_prefix': '[', + 'key_inner_prefix': '@', + 'key_suffix': ']', + 'desc_format': '{}∶ {} ‴{}‴ ₋{}₋ ₍{}₎', + 'desc_fields': ["type", "key", "title", "author", "date"], + 'wrap_chars': '||', + 'highlight_dash': "‾⁻−₋‐⋯┄–—―∼┈─▭▬┉━┅₌⁼‗", + 'highlight_bar': "‖│┃┆∥┇┊┋", + 'highlight_bracket': "⊂〔₍⁽⊃〕₎⁾", + 'highlight_arrow': "◀◁<‹▶▷>›", + 'highlight_colon': "∶∷→⇒≫", + 'highlight_blob': "♯♡◆◇◊○◎●◐◑∗∙⊙⊚⌂★☺☻▪■□▢▣▤▥▦▧▨▩", + 'highlight_tiny': "、。‸₊⁺∘♢☆☜☞♢☼", + 'highlight_text': "″‴‶‷", + 'searchkeys': [], + } + + def on_init(self, context): + if len(context['args']) >= 1: + context['__source'] = 'source_field' + context['__field'] = context['args'].pop(0) + + # Set mode; zotero or bibtex + self._set_mode(context) + else: + context['__source'] = 'sub_sources' + + def gather_candidates(self, context): + if context['__source'] is 'sub_sources': + return self._gather_sub_sources() + else: + return self._gather_items(context) + + def _set_mode(self, context): + """ Set up bibtex or zotero mode """ + if self.vars['mode'] == "bibtex" and self.vars['bibtex_file']: + # Enable cache + self._enable_cache() + # Get parser + from citation_vim.bibtex.parser import BibtexParser + self._parser = BibtexParser(self.vars) + elif self.vars['mode'] == "zotero" and self.vars['zotero_path']: + # Check if searchkeys are given and set cache mode accordingly. + self._get_searchkeys(context) + # Get parser + from citation_vim.zotero.parser import ZoteroParser + self.vars['zotero_version'] = int(self.vars['zotero_version']) + self._parser = ZoteroParser(self.vars) + else: + raiseError("'mode' must be set to 'zotero' or 'bibtex'") + + def _enable_cache(self): + self.__cache = True + self.__cache_file = join(self.vars['cache_path'], "citation_vim_cache") + + def _get_searchkeys(self, context): + if len(context['args']) > 0: + self.vars['searchkeys'] = context['args'].pop(0) + self.__cache = False + else: + self.vars['searchkeys'] = [] + self._enable_cache() + + def _gather_sub_sources(self): + # Generate candidates and return it + candidates = [] + for sub_source in self.sub_sources: + candidates.append({ + "word": sub_source, + "action__source_field": sub_source, + }) + return candidates + + def _gather_items(self, context): + candidates = [] + + if context['__field'] is 'duplicate_keys': + items = self._get_duplicate_key(context) + else: + items = self._get_items(context) + + if context['__field'] is 'key': + text = (self.vars['key_outer_prefix'] + + self.vars['key_inner_prefix'] + + '{}' + + self.vars['key_suffix']) + elif context['__field'] is 'key_inner': + text = self.vars['key_inner_prefix'] + '{}' + else: + text = '{}' + + if context['__field'] is 'url': + file_url = 'url' + else: + file_url = 'file' + + # Update vars with source field + self.vars['source_field'] = context['__field'] + + # Retirn items + for item in items: + if (not self.vars['collection'] + or self.vars['collection'] in item.collections): + candidate = { + "word": item.describe(self.vars), + "action__text": text.format(getattr(item, context['__field'])), + "action__path": getattr(item, file_url), + "action__command": self._set_message(item.combined), + } + candidates.append(candidate) + return candidates + + def _set_message(self, message): + return "echo {}".format(message) + + def _get_duplicate_keys(self, context): + """ + Returns an array of collections. + """ + self.vars['collection'] = "" + context['__field'] = 'key' + self.__cache = False + + # Get items + items = self._get_items(context) + + # Filter itesm for duplicate keys + items.sort(key=lambda item: item.key) + last_item = Item() + last_item.key = "" + filtered_items = [] + for item in items: + if last_item.key == item.key: + filtered_items.append(item) + last_item = item + return filtered_items + + def _get_items(self, context): + """ + Returns items from cache or parser + """ + if self.__cache and self._is_cached(): + return read_cache(self.__cache_file) + items = self._parser.load() + if self.vars['reverse_order']: + items.reverse() + if self.__cache: + write_cache(self.__cache_file, items) + return items + + def _is_cached(self): + """ + Returns boolean based on cache and target file dates + """ + if self.vars['mode'] == 'bibtex': + file_path = self.vars['bibtex_file'] + elif self.vars['mode'] == 'zotero': + zotero_database = join(self.vars['zotero_path'], "zotero.sqlite") + file_path = zotero_database + return is_current(file_path, self.__cache_file) diff --git a/rplugin/python3/denite/source/citation_collection.py b/rplugin/python3/denite/source/citation_collection.py new file mode 100644 index 0000000..fa7a424 --- /dev/null +++ b/rplugin/python3/denite/source/citation_collection.py @@ -0,0 +1,36 @@ +from os.path import dirname +from .citation import Source as Base + + +class Source(Base): + + def __init__(self, vim): + super().__init__(vim) + + self.name = 'citation_collection' + self.description = "search citation collection" + self.kind = 'command' + + def gather_candidates(self, context): + """ + Returns an array of collections. + """ + candidates = {} + collections = {} + for item in self._get_items(context): + for col in item.collections: + if not col in collections: + collections[col] = col + + candidates.append({ + "word": col, + "action__command": self._set_col(col), + # "action__type": ": ", + "action__text": col, + "action__path": col, + "action__directory": dirname(col), + }) + return candidates + + def _set_collection(self, collection): + return "call denite#custom#var('citation', 'collection', '{}')".format(collection)