1from __future__ import unicode_literals 2# Copyright (c) 2006-2021 Andrey Golovizin 3# Copyright (c) 2014 Matthias C. M. Troffaes 4# 5# Permission is hereby granted, free of charge, to any person obtaining 6# a copy of this software and associated documentation files (the 7# "Software"), to deal in the Software without restriction, including 8# without limitation the rights to use, copy, modify, merge, publish, 9# distribute, sublicense, and/or sell copies of the Software, and to 10# permit persons to whom the Software is furnished to do so, subject to 11# the following conditions: 12# 13# The above copyright notice and this permission notice shall be 14# included in all copies or substantial portions of the Software. 15# 16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 24 25import os.path # splitext 26import pkg_resources 27 28from pybtex.exceptions import PybtexError 29 30 31class Plugin(object): 32 pass 33 34 35#: default pybtex plugins 36_DEFAULT_PLUGINS = { 37 "pybtex.database.input": "bibtex", 38 "pybtex.database.output": "bibtex", 39 "pybtex.backends": "latex", 40 "pybtex.style.labels": "number", 41 "pybtex.style.names": "plain", 42 "pybtex.style.sorting": "none", 43 "pybtex.style.formatting": "unsrt", 44} 45 46 47class PluginGroupNotFound(PybtexError): 48 49 def __init__(self, group_name): 50 message = u'plugin group {group_name} not found'.format( 51 group_name=group_name, 52 ) 53 super(PluginGroupNotFound, self).__init__(message) 54 55 56class PluginNotFound(PybtexError): 57 58 def __init__(self, plugin_group, name): 59 if not name.startswith('.'): 60 message = u'plugin {plugin_group}.{name} not found'.format( 61 plugin_group=plugin_group, 62 name=name, 63 ) 64 else: 65 assert plugin_group.endswith('.suffixes') 66 message = ( 67 u'plugin {plugin_group} for suffix {suffix} not found'.format( 68 plugin_group=plugin_group, 69 suffix=name, 70 ) 71 ) 72 73 super(PluginNotFound, self).__init__(message) 74 75 76def _load_entry_point(group, name, use_aliases=False): 77 groups = [group, group + '.aliases'] if use_aliases else [group] 78 for search_group in groups: 79 for entry_point in pkg_resources.iter_entry_points(search_group, name): 80 return entry_point.load() 81 raise PluginNotFound(group, name) 82 83 84def find_plugin(plugin_group, name=None, filename=None): 85 """Find a :class:`Plugin` class within *plugin_group* which 86 matches *name*, or *filename* if *name* is not specified, or 87 the default plugin if neither *name* nor *filename* is 88 specified. 89 90 If *name* is specified, return the :class:`Plugin` class 91 registered under *name*. If *filename* is specified, look at 92 its suffix (i.e. extension) and return the :class:`Plugin` 93 class registered for this suffix. 94 95 If *name* is not a string, but a plugin class, just return it back. 96 (Used to make functions like :function:`make_bibliography` accept already 97 loaded plugins as well as plugin names. 98 99 """ 100 if isinstance(name, type) and issubclass(name, Plugin): 101 return name 102 103 if plugin_group not in _DEFAULT_PLUGINS: 104 raise PluginGroupNotFound(plugin_group) 105 if name: 106 return _load_entry_point(plugin_group, name, use_aliases=True) 107 elif filename: 108 suffix = os.path.splitext(filename)[1] 109 return _load_entry_point(plugin_group + '.suffixes', suffix) 110 else: 111 return _load_entry_point(plugin_group, _DEFAULT_PLUGINS[plugin_group]) 112 113 114def enumerate_plugin_names(plugin_group): 115 """Enumerate all plugin names for the given *plugin_group*.""" 116 return (entry_point.name 117 for entry_point in pkg_resources.iter_entry_points(plugin_group)) 118 119 120class _FakeEntryPoint(pkg_resources.EntryPoint): 121 122 def __init__(self, name, klass): 123 self.name = name 124 self.klass = klass 125 126 def __str__(self): 127 return "%s = :%s" % (self.name, self.klass.__name__) 128 129 def __repr__(self): 130 return ( 131 "_FakeEntryPoint(name=%r, klass=%s)" 132 % (self.name, self.klass.__name__)) 133 134 def load(self, require=True, env=None, installer=None): 135 return self.klass 136 137 def require(self, env=None, installer=None): 138 pass 139 140 141def register_plugin(plugin_group, name, klass, force=False): 142 """Register a plugin on the fly. 143 144 This works by adding *klass* as a pybtex entry point, under entry 145 point group *plugin_group* and entry point name *name*. 146 147 To register a suffix, append ".suffixes" to the plugin group, and 148 *name* is then simply the suffix, which should start with a 149 period. 150 151 To register an alias plugin name, append ".aliases" to the plugin group. 152 Aliases work just as real names, but are not returned by 153 :function:`enumerate_plugin_names` and not advertised in the command line 154 help. 155 156 If *force* is ``False``, then existing entry points are not 157 overwritten. If an entry point with the given group and name 158 already exists, then returns ``False``, otherwise returns 159 ``True``. 160 161 If *force* is ``True``, then existing entry points are 162 overwritten, and the function always returns ``True``. 163 164 """ 165 if plugin_group.endswith(".suffixes"): 166 base_group, _ = plugin_group.rsplit(".", 1) 167 if not name.startswith('.'): 168 raise ValueError("a suffix must start with a period") 169 elif plugin_group.endswith(".aliases"): 170 base_group, _ = plugin_group.rsplit(".", 1) 171 else: 172 base_group = plugin_group 173 if base_group not in _DEFAULT_PLUGINS: 174 raise PluginGroupNotFound(base_group) 175 176 dist = pkg_resources.get_distribution('pybtex') 177 ep_map = pkg_resources.get_entry_map(dist) 178 if plugin_group not in ep_map: 179 ep_map[plugin_group] = {} 180 if name in ep_map[plugin_group] and not force: 181 return False 182 else: 183 ep_map[plugin_group][name] = _FakeEntryPoint(name, klass) 184 return True 185