Merge lp:~matthias-troffaes/pybtex/even-simpler-plugin-loader into lp:pybtex

Proposed by Matthias C. M. Troffaes
Status: Merged
Merged at revision: 893
Proposed branch: lp:~matthias-troffaes/pybtex/even-simpler-plugin-loader
Merge into: lp:pybtex
Diff against target: 1037 lines (+313/-345)
33 files modified
pybtex/__init__.py (+2/-2)
pybtex/backends/__init__.py (+2/-5)
pybtex/backends/html.py (+1/-3)
pybtex/backends/latex.py (+1/-3)
pybtex/backends/plaintext.py (+1/-4)
pybtex/bibtex/__init__.py (+1/-1)
pybtex/database/input/__init__.py (+1/-2)
pybtex/database/input/bibtex.py (+1/-2)
pybtex/database/input/bibtexml.py (+1/-2)
pybtex/database/input/bibyaml.py (+1/-3)
pybtex/database/output/__init__.py (+0/-1)
pybtex/database/output/bibtex.py (+0/-2)
pybtex/database/output/bibtexml.py (+0/-3)
pybtex/database/output/bibyaml.py (+0/-4)
pybtex/plugin/__init__.py (+122/-119)
pybtex/plugin/make_registry.py (+0/-63)
pybtex/plugin/registry.py (+0/-103)
pybtex/style/formatting/__init__.py (+0/-1)
pybtex/style/formatting/alpha.py (+0/-1)
pybtex/style/formatting/plain.py (+0/-1)
pybtex/style/formatting/unsrt.py (+0/-1)
pybtex/style/formatting/unsrtalpha.py (+0/-1)
pybtex/style/labels/__init__.py (+0/-4)
pybtex/style/labels/alpha.py (+0/-1)
pybtex/style/labels/number.py (+0/-1)
pybtex/style/names/__init__.py (+0/-5)
pybtex/style/names/lastfirst.py (+0/-2)
pybtex/style/names/plain.py (+0/-1)
pybtex/style/sorting/__init__.py (+0/-2)
pybtex/style/sorting/author_year_title.py (+0/-1)
pybtex/style/sorting/none.py (+0/-1)
pybtex/tests/plugin_test.py (+119/-0)
setup.py (+60/-0)
To merge this branch: bzr merge lp:~matthias-troffaes/pybtex/even-simpler-plugin-loader
Reviewer Review Type Date Requested Status
Andrey Golovizin Approve
Review via email: mp+200667@code.launchpad.net

Description of the change

An implementation just using entry points for all plugins.

To post a comment you must log in.
Revision history for this message
Matthias C. M. Troffaes (matthias-troffaes) wrote :

This is now ready for initial review. IMHO this implementation looks a lot cleaner than the old implementation

https://code.launchpad.net/~matthias-troffaes/pybtex/simple-plugin-loader/+merge/195366

Thoughts?

Revision history for this message
Andrey Golovizin (ero-sennin) wrote :

This makes sense.

There are some things though.

- I'd want to keep Plugin.name, Plugin.aliases and Plugin.suffixes. The idea is that all information about a plugin should be contained in a plugin itself and not be scattered all over. Also, this way it would be possible to call register_plugin(SomePlugin) without extra arguments, and register_plugin() would care about registering all the necessary entry points for you.

- Please add a license header to plugin_test.py and use two blank lines between top-level definitions. (Just a nitpick.)

Revision history for this message
Matthias C. M. Troffaes (matthias-troffaes) wrote :

Ok, great! License & pep8 fixes are of course no problem.

Concerning the first point, I sort of convinced myself that, eventually, names/aliases/suffixes do not belong to the plugin class, at least not as static class attributes, because we cannot know in advance what kind of aliases or suffixes a user is going to register for a plugin. This is already the case for the entry point plugins in the original code: in fact their name/suffixes/aliases attributes were entirely ignored IIRC. For reasons purely due to causality: with entry point plugins, we need to know the name before we even have the class.

Could you bring yourself to think of name, aliases, and suffixes as belonging to the registry system, rather than belonging to the Plugin classes?

Revision history for this message
Andrey Golovizin (ero-sennin) wrote :

Yes, I think you are right here. So, let's have it merged after you finish with PEP8 fixes.

Revision history for this message
Matthias C. M. Troffaes (matthias-troffaes) wrote :

(Sorry for the not so quick response.)

Ok, very good! I'll fix the pep8 issue as soon as I find some spare time.

Revision history for this message
Matthias C. M. Troffaes (matthias-troffaes) wrote :

Ready for review.

Revision history for this message
Andrey Golovizin (ero-sennin) wrote :

> Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012 Andrey Golovizin

Should be "Copyright (c) 2014 Matthias C. M. Troffaes" instead. :) And you should add a line with your name to pybtex/plugin/__init__.py too. Otherwise it's ready for merging.

928. By Matthias C. M. Troffaes

Update copyright notices.

Revision history for this message
Matthias C. M. Troffaes (matthias-troffaes) wrote :

Ok, done.

Revision history for this message
Andrey Golovizin (ero-sennin) wrote :

Great, merged. Thanks for the good work!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'pybtex/__init__.py'
2--- pybtex/__init__.py 2013-08-08 18:12:52 +0000
3+++ pybtex/__init__.py 2014-02-04 13:09:32 +0000
4@@ -48,7 +48,7 @@
5 encoding=bib_encoding,
6 wanted_entries=aux_data.citations,
7 min_crossrefs=min_crossrefs,
8- ).parse_files(aux_data.data, bib_parser.get_default_suffix())
9+ ).parse_files(aux_data.data, bib_parser.default_suffix)
10
11 if style is None:
12 style = aux_data.style
13@@ -63,5 +63,5 @@
14 formatted_bibliography = style.format_bibliography(bib_data, aux_data.citations)
15
16 output_backend = find_plugin('pybtex.backends', output_backend)
17- output_filename = filename + output_backend.get_default_suffix()
18+ output_filename = filename + output_backend.default_suffix
19 output_backend(output_encoding).write_to_file(formatted_bibliography, output_filename)
20
21=== modified file 'pybtex/backends/__init__.py'
22--- pybtex/backends/__init__.py 2013-08-08 11:51:24 +0000
23+++ pybtex/backends/__init__.py 2014-02-04 13:09:32 +0000
24@@ -24,12 +24,9 @@
25 from pybtex.utils import deprecated
26
27
28-available_plugins = ('latex', 'html', 'plaintext')
29-
30-
31 class BaseBackend(Plugin):
32- default_plugin = 'latex'
33- RenderType = basestring #: the result of render and render_sequence
34+ RenderType = basestring #: the result of render and render_sequence
35+ default_suffix = None #: the default suffix for an output file
36
37 def __init__(self, encoding=None):
38 self.encoding = encoding
39
40=== modified file 'pybtex/backends/html.py'
41--- pybtex/backends/html.py 2013-08-09 19:30:08 +0000
42+++ pybtex/backends/html.py 2014-02-04 13:09:32 +0000
43@@ -35,9 +35,7 @@
44 """
45
46 class Backend(BaseBackend):
47- name = 'html'
48- suffixes = '.html',
49-
50+ default_suffix = '.html'
51 symbols = {
52 'ndash': u'–',
53 'newblock': u'\n',
54
55=== modified file 'pybtex/backends/latex.py'
56--- pybtex/backends/latex.py 2013-08-09 19:38:46 +0000
57+++ pybtex/backends/latex.py 2014-02-04 13:09:32 +0000
58@@ -23,9 +23,7 @@
59
60
61 class Backend(BaseBackend):
62- name = 'latex'
63- suffixes = '.bbl', '.tex', '.latex'
64-
65+ default_suffix = '.bbl'
66 symbols = {
67 'ndash': u'--',
68 'newblock': u'\n\\newblock ',
69
70=== modified file 'pybtex/backends/plaintext.py'
71--- pybtex/backends/plaintext.py 2013-08-09 19:30:08 +0000
72+++ pybtex/backends/plaintext.py 2014-02-04 13:09:32 +0000
73@@ -23,10 +23,7 @@
74
75
76 class Backend(BaseBackend):
77- name = 'plaintext'
78- aliases = 'text',
79- suffixes = '.txt',
80-
81+ default_suffix = '.txt'
82 symbols = {
83 'ndash': u'-',
84 'newblock': u' ',
85
86=== modified file 'pybtex/bibtex/__init__.py'
87--- pybtex/bibtex/__init__.py 2013-08-08 18:12:52 +0000
88+++ pybtex/bibtex/__init__.py 2014-02-04 13:09:32 +0000
89@@ -50,7 +50,7 @@
90 bst_script = bst.parse_file(bst_filename, bst_encoding)
91 base_filename = path.splitext(aux_filename)[0]
92 bbl_filename = base_filename + path.extsep + 'bbl'
93- bib_filenames = [filename + bib_format.get_default_suffix() for filename in aux_data.data]
94+ bib_filenames = [filename + bib_format.default_suffix for filename in aux_data.data]
95 bbl_file = pybtex.io.open_unicode(bbl_filename, 'w', encoding=output_encoding)
96 interpreter = Interpreter(bib_format, bib_encoding)
97 interpreter.run(bst_script, aux_data.citations, bib_filenames, bbl_file, min_crossrefs=min_crossrefs)
98
99=== modified file 'pybtex/database/input/__init__.py'
100--- pybtex/database/input/__init__.py 2012-02-12 20:44:25 +0000
101+++ pybtex/database/input/__init__.py 2014-02-04 13:09:32 +0000
102@@ -30,9 +30,8 @@
103
104
105 class BaseParser(Plugin):
106- default_plugin = 'bibtex'
107+ default_suffix = None
108 filename = '<INPUT>'
109-
110 unicode_io = False
111
112 def __init__(self, encoding=None, wanted_entries=None, min_crossrefs=2, **kwargs):
113
114=== modified file 'pybtex/database/input/bibtex.py'
115--- pybtex/database/input/bibtex.py 2013-07-31 13:35:20 +0000
116+++ pybtex/database/input/bibtex.py 2014-02-04 13:09:32 +0000
117@@ -296,8 +296,7 @@
118
119
120 class Parser(BaseParser):
121- name = 'bibtex'
122- suffixes = '.bib',
123+ default_suffix = '.bib'
124 unicode_io = True
125
126 macros = None
127
128=== modified file 'pybtex/database/input/bibtexml.py'
129--- pybtex/database/input/bibtexml.py 2012-02-12 20:44:25 +0000
130+++ pybtex/database/input/bibtexml.py 2014-02-04 13:09:32 +0000
131@@ -32,8 +32,7 @@
132 return s[len(bibtexns):]
133
134 class Parser(BaseParser):
135- name = 'bibtexml'
136- suffixes = '.xml', '.bibtexml'
137+ default_suffix = '.xml'
138
139 def parse_stream(self, stream):
140 t = ET.parse(stream)
141
142=== modified file 'pybtex/database/input/bibyaml.py'
143--- pybtex/database/input/bibyaml.py 2012-02-12 20:44:25 +0000
144+++ pybtex/database/input/bibyaml.py 2014-02-04 13:09:32 +0000
145@@ -25,9 +25,7 @@
146
147
148 class Parser(BaseParser):
149- name = 'bibyaml'
150- aliases = 'yaml',
151- suffixes = '.yaml', '.bibyaml'
152+ default_suffix = '.yaml'
153
154 def parse_stream(self, stream):
155 t = yaml.safe_load(stream)
156
157=== modified file 'pybtex/database/output/__init__.py'
158--- pybtex/database/output/__init__.py 2012-02-12 20:44:25 +0000
159+++ pybtex/database/output/__init__.py 2014-02-04 13:09:32 +0000
160@@ -27,7 +27,6 @@
161
162 class BaseWriter(Plugin):
163 unicode_io = False
164- default_plugin = 'bibtex'
165
166 def __init__(self, encoding=None):
167 self.encoding = encoding or pybtex.io.get_default_encoding()
168
169=== modified file 'pybtex/database/output/bibtex.py'
170--- pybtex/database/output/bibtex.py 2013-08-02 10:39:18 +0000
171+++ pybtex/database/output/bibtex.py 2014-02-04 13:09:32 +0000
172@@ -28,8 +28,6 @@
173 class Writer(BaseWriter):
174 """Outputs BibTeX markup"""
175
176- name = 'bibtex'
177- suffixes = '.bib',
178 unicode_io = True
179
180 def quote(self, s):
181
182=== modified file 'pybtex/database/output/bibtexml.py'
183--- pybtex/database/output/bibtexml.py 2012-02-12 20:44:25 +0000
184+++ pybtex/database/output/bibtexml.py 2014-02-04 13:09:32 +0000
185@@ -67,9 +67,6 @@
186 class Writer(BaseWriter):
187 """Outputs BibTeXML markup"""
188
189- name = 'bibtexml'
190- suffixes = '.xml', '.bibtexml'
191-
192 def write_stream(self, bib_data, stream):
193 def write_persons(persons, role):
194 if persons:
195
196=== modified file 'pybtex/database/output/bibyaml.py'
197--- pybtex/database/output/bibyaml.py 2013-08-02 10:39:18 +0000
198+++ pybtex/database/output/bibyaml.py 2014-02-04 13:09:32 +0000
199@@ -27,10 +27,6 @@
200 class Writer(BaseWriter):
201 """Outputs YAML markup"""
202
203- name = 'bibyaml'
204- aliases = 'yaml',
205- suffixes = '.yaml', '.bibyaml'
206-
207 def write_stream(self, bib_data, stream):
208 def process_person_roles(entry):
209 for role, persons in entry.persons.iteritems():
210
211=== modified file 'pybtex/plugin/__init__.py'
212--- pybtex/plugin/__init__.py 2012-02-12 20:44:25 +0000
213+++ pybtex/plugin/__init__.py 2014-02-04 13:09:32 +0000
214@@ -1,4 +1,5 @@
215 # Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012 Andrey Golovizin
216+# Copyright (c) 2014 Matthias C. M. Troffaes
217 #
218 # Permission is hereby granted, free of charge, to any person obtaining
219 # a copy of this software and associated documentation files (the
220@@ -19,14 +20,31 @@
221 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
222 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
223
224-import os
225-import sys
226-from itertools import chain
227+
228+import os.path # splitext
229+import pkg_resources
230
231 from pybtex.exceptions import PybtexError
232
233
234+class Plugin(object):
235+ pass
236+
237+
238+#: default pybtex plugins
239+_DEFAULT_PLUGINS = {
240+ "pybtex.database.input": "bibtex",
241+ "pybtex.database.output": "bibtex",
242+ "pybtex.backends": "latex",
243+ "pybtex.style.labels": "number",
244+ "pybtex.style.names": "plain",
245+ "pybtex.style.sorting": "none",
246+ "pybtex.style.formatting": "unsrt",
247+}
248+
249+
250 class PluginGroupNotFound(PybtexError):
251+
252 def __init__(self, group_name):
253 message = u'plugin group {group_name} not found'.format(
254 group_name=group_name,
255@@ -35,135 +53,120 @@
256
257
258 class PluginNotFound(PybtexError):
259- def __init__(self, plugin_group, name=None, filename=None):
260- assert plugin_group
261- if name:
262+
263+ def __init__(self, plugin_group, name):
264+ if not name.startswith('.'):
265 message = u'plugin {plugin_group}.{name} not found'.format(
266 plugin_group=plugin_group,
267 name=name,
268 )
269- elif filename:
270- message = u'cannot determine file type for {filename}'.format(
271- plugin_group=plugin_group,
272- filename=filename,
273- )
274 else:
275- message = u'' # FIXME
276+ assert plugin_group.endswith('.suffixes')
277+ message = (
278+ u'plugin {plugin_group} for suffix {suffix} not found'.format(
279+ plugin_group=plugin_group,
280+ suffix=name,
281+ )
282+ )
283
284 super(PluginNotFound, self).__init__(message)
285
286
287-class Plugin(object):
288- name = None
289- aliases = ()
290- suffixes = ()
291- default_plugin = None
292-
293- @classmethod
294- def get_default_suffix(cls):
295- return cls.suffixes[0]
296-
297-
298-class PluginLoader(object):
299- def find_plugin(plugin_group, name):
300- raise NotImplementedError
301-
302- def enumerate_plugin_names(self, plugin_group):
303- raise NotImplementedError
304-
305-
306-class BuiltInPluginLoader(PluginLoader):
307- def get_group_info(self, plugin_group):
308- from pybtex.plugin.registry import plugin_registry
309- try:
310- return plugin_registry[plugin_group]
311- except KeyError:
312- raise PluginGroupNotFound(plugin_group)
313-
314- def find_plugin(self, plugin_group, name=None, filename=None):
315- plugin_group_info = self.get_group_info(plugin_group)
316- module_name = None
317- if name:
318- if name in plugin_group_info['plugins']:
319- module_name = name
320- elif name in plugin_group_info['aliases']:
321- module_name = plugin_group_info['aliases'][name]
322- else:
323- raise PluginNotFound(plugin_group, name=name)
324- elif filename:
325- suffix = os.path.splitext(filename)[1]
326- if suffix in plugin_group_info['suffixes']:
327- module_name = plugin_group_info['suffixes'][suffix]
328- else:
329- raise PluginNotFound(plugin_group, filename=filename)
330- else:
331- module_name = plugin_group_info['default_plugin']
332-
333- class_name = plugin_group_info['class_name']
334- return self.import_plugin(plugin_group, module_name, class_name)
335-
336- def import_plugin(self, plugin_group, module_name, class_name):
337- module = __import__(str(plugin_group), globals(), locals(), [str(module_name)])
338- return getattr(getattr(module, module_name), class_name)
339-
340- def enumerate_plugin_names(self, plugin_group):
341- plugin_group_info = self.get_group_info(plugin_group)
342- return plugin_group_info['plugins']
343-
344-
345-class EntryPointPluginLoader(PluginLoader):
346- def find_plugin(self, plugin_group, name=None, filename=None):
347- try:
348- import pkg_resources
349- except ImportError:
350- raise PluginNotFound(plugin_group)
351-
352- def load_entry_point(group, name):
353- entry_points = pkg_resources.iter_entry_points(group, name)
354- try:
355- entry_point = entry_points.next()
356- except StopIteration:
357- raise PluginNotFound(plugin_group, name, filename)
358- else:
359- return entry_point.load()
360-
361- if name:
362- return load_entry_point(plugin_group, name)
363- elif filename:
364- suffix = os.path.splitext(filename)[1]
365- return load_entry_point(plugin_group + '.suffixes', suffix)
366- else:
367- raise PluginNotFound(plugin_group)
368-
369- def enumerate_plugin_names(self, plugin_group):
370- try:
371- import pkg_resources
372- except ImportError:
373- return
374- entry_points = pkg_resources.iter_entry_points(plugin_group)
375- return [entry_point.name for entry_point in entry_points]
376-
377-
378-plugin_loaders = [EntryPointPluginLoader(), BuiltInPluginLoader()]
379+def _load_entry_point(group, name):
380+ entry_points = pkg_resources.iter_entry_points(group, name)
381+ try:
382+ entry_point = entry_points.next()
383+ except StopIteration:
384+ raise PluginNotFound(group, name)
385+ else:
386+ return entry_point.load()
387
388
389 def find_plugin(plugin_group, name=None, filename=None):
390- if isinstance(name, type) and issubclass(name, Plugin):
391- plugin = name
392- #assert plugin.group_name == plugin_group
393- return plugin
394+ """Find a :class:`Plugin` class within *plugin_group* which
395+ matches *name*, or *filename* if *name* is not specified, or
396+ the default plugin if neither *name* nor *filename* is
397+ specified.
398+
399+ If *name* is specified, return the :class:`Plugin` class
400+ registered under *name*. If *filename* is specified, look at
401+ its suffix (i.e. extension) and return the :class:`Plugin`
402+ class registered for this suffix.
403+ """
404+ if plugin_group not in _DEFAULT_PLUGINS:
405+ raise PluginGroupNotFound(plugin_group)
406+ if name:
407+ return _load_entry_point(plugin_group, name)
408+ elif filename:
409+ suffix = os.path.splitext(filename)[1]
410+ return _load_entry_point(plugin_group + '.suffixes', suffix)
411 else:
412- for loader in plugin_loaders:
413- try:
414- return loader.find_plugin(plugin_group, name, filename)
415- except PluginNotFound:
416- continue
417- raise PluginNotFound(plugin_group, name, filename)
418+ return _load_entry_point(plugin_group, _DEFAULT_PLUGINS[plugin_group])
419
420
421 def enumerate_plugin_names(plugin_group):
422- results = (
423- loader.enumerate_plugin_names(plugin_group)
424- for loader in plugin_loaders
425- )
426- return chain(*results)
427+ """Enumerate all plugin names for the given *plugin_group*."""
428+ return (entry_point.name
429+ for entry_point in pkg_resources.iter_entry_points(plugin_group))
430+
431+
432+class _FakeEntryPoint(pkg_resources.EntryPoint):
433+
434+ def __init__(self, name, klass):
435+ self.name = name
436+ self.klass = klass
437+
438+ def __str__(self):
439+ return "%s = :%s" % (self.name, self.klass.__name__)
440+
441+ def __repr__(self):
442+ return (
443+ "_FakeEntryPoint(name=%r, klass=%s)"
444+ % (self.name, self.klass.__name__))
445+
446+ def load(self, require=True, env=None, installer=None):
447+ return self.klass
448+
449+ def require(self, env=None, installer=None):
450+ pass
451+
452+
453+def register_plugin(plugin_group, name, klass, force=False):
454+ """Register a plugin on the fly.
455+
456+ This works by adding *klass* as a pybtex entry point, under entry
457+ point group *plugin_group* and entry point name *name*.
458+
459+ To register a suffix, append ".suffixes" to the plugin group, and
460+ *name* is then simply the suffix, which should start with a
461+ period.
462+
463+ If *force* is ``False``, then existing entry points are not
464+ overwritten. If an entry point with the given group and name
465+ already exists, then returns ``False``, otherwise returns
466+ ``True``.
467+
468+ If *force* is ``True``, then existing entry points are
469+ overwritten, and the function always returns ``True``.
470+ """
471+ if not plugin_group.endswith(".suffixes"):
472+ # registering a name
473+ if plugin_group not in _DEFAULT_PLUGINS:
474+ raise PluginGroupNotFound(plugin_group)
475+ else:
476+ # registering a suffix
477+ group, _1, _2 = plugin_group.rpartition(".suffixes")
478+ assert _1 == ".suffixes" and _2 == ""
479+ if not name.startswith('.'):
480+ raise ValueError("a suffix must start with a period")
481+ if group not in _DEFAULT_PLUGINS:
482+ raise PluginGroupNotFound(group)
483+ dist = pkg_resources.get_distribution('pybtex')
484+ ep_map = pkg_resources.get_entry_map(dist)
485+ if plugin_group not in ep_map:
486+ ep_map[plugin_group] = {}
487+ if name in ep_map[plugin_group] and not force:
488+ return False
489+ else:
490+ ep_map[plugin_group][name] = _FakeEntryPoint(name, klass)
491+ return True
492
493=== removed file 'pybtex/plugin/make_registry.py'
494--- pybtex/plugin/make_registry.py 2011-02-27 17:25:55 +0000
495+++ pybtex/plugin/make_registry.py 1970-01-01 00:00:00 +0000
496@@ -1,63 +0,0 @@
497-#!/usr/bin/env python
498-
499-import os
500-import imp
501-import pkgutil
502-import json
503-
504-
505-plugin_types = (
506- ('pybtex.database.input', 'Parser'),
507- ('pybtex.database.output', 'Writer'),
508- ('pybtex.backends', 'Backend'),
509- ('pybtex.style.labels', 'LabelStyle'),
510- ('pybtex.style.names', 'NameStyle'),
511- ('pybtex.style.sorting', 'SortingStyle'),
512- ('pybtex.style.formatting', 'Style'),
513-)
514-
515-
516-def import_from(module_name, obj_name):
517- module = __import__(module_name, globals(), locals(), [obj_name])
518- return module, getattr(module, obj_name)
519-
520-
521-def iter_plugins(base_module, class_name):
522- plugin_dir = os.path.dirname(base_module.__file__)
523- for loader, name, is_pkg in pkgutil.iter_modules([plugin_dir]):
524- plugin_module = imp.load_module(name, *imp.find_module(name, [plugin_dir]))
525- yield getattr(plugin_module, class_name)
526-
527-
528-def get_plugin_group_info(plugin_group, class_name):
529- base_module, base_plugin = import_from(plugin_group, 'Base' + class_name)
530- info = {
531- 'class_name': class_name,
532- 'default_plugin': base_plugin.default_plugin,
533- 'plugins': [],
534- 'aliases': {},
535- 'suffixes': {},
536- }
537- for plugin in iter_plugins(base_module, class_name):
538- info['plugins'].append(plugin.name)
539- info['aliases'].update(dict.fromkeys(plugin.aliases, plugin.name))
540- info['suffixes'].update(dict.fromkeys(plugin.suffixes, plugin.name))
541- return info
542-
543-
544-def get_plugin_registry():
545- return dict(
546- (plugin_group, get_plugin_group_info(plugin_group, class_name))
547- for plugin_group, class_name in plugin_types
548- )
549-
550-
551-def write_plugin_registry(filename):
552- with open(filename, 'wb') as registry_file:
553- registry_file.write('plugin_registry = ')
554- json.dump(get_plugin_registry(), registry_file, indent=4)
555- registry_file.write('\n')
556-
557-
558-if __name__ == '__main__':
559- write_plugin_registry('registry.py')
560
561=== removed file 'pybtex/plugin/registry.py'
562--- pybtex/plugin/registry.py 2013-08-09 19:38:46 +0000
563+++ pybtex/plugin/registry.py 1970-01-01 00:00:00 +0000
564@@ -1,103 +0,0 @@
565-plugin_registry = {
566- "pybtex.database.output": {
567- "class_name": "Writer",
568- "suffixes": {
569- ".xml": "bibtexml",
570- ".bibtexml": "bibtexml",
571- ".bibyaml": "bibyaml",
572- ".bib": "bibtex",
573- ".yaml": "bibyaml"
574- },
575- "aliases": {
576- "yaml": "bibyaml"
577- },
578- "default_plugin": "bibtex",
579- "plugins": [
580- "bibtex",
581- "bibtexml",
582- "bibyaml"
583- ]
584- },
585- "pybtex.style.formatting": {
586- "class_name": "Style",
587- "suffixes": {},
588- "aliases": {},
589- "default_plugin": "unsrt",
590- "plugins": [
591- "plain",
592- "unsrt",
593- "alpha",
594- "unsrtalpha",
595- ]
596- },
597- "pybtex.style.labels": {
598- "class_name": "LabelStyle",
599- "suffixes": {},
600- "aliases": {},
601- "default_plugin": "number",
602- "plugins": [
603- "number",
604- "alpha",
605- ]
606- },
607- "pybtex.backends": {
608- "class_name": "Backend",
609- "suffixes": {
610- ".html": "html",
611- ".latex": "latex",
612- ".txt": "plaintext",
613- ".bbl": "latex",
614- ".tex": "latex"
615- },
616- "aliases": {
617- "text": "plaintext"
618- },
619- "default_plugin": "latex",
620- "plugins": [
621- "html",
622- "latex",
623- "plaintext"
624- ]
625- },
626- "pybtex.database.input": {
627- "class_name": "Parser",
628- "suffixes": {
629- ".xml": "bibtexml",
630- ".bibtexml": "bibtexml",
631- ".bibyaml": "bibyaml",
632- ".bib": "bibtex",
633- ".yaml": "bibyaml"
634- },
635- "aliases": {
636- "yaml": "bibyaml"
637- },
638- "default_plugin": "bibtex",
639- "plugins": [
640- "bibtex",
641- "bibtexml",
642- "bibyaml"
643- ]
644- },
645- "pybtex.style.names": {
646- "class_name": "NameStyle",
647- "suffixes": {},
648- "aliases": {
649- "last_first": "lastfirst"
650- },
651- "default_plugin": "plain",
652- "plugins": [
653- "lastfirst",
654- "plain"
655- ]
656- },
657- "pybtex.style.sorting": {
658- "class_name": "SortingStyle",
659- "suffixes": {},
660- "aliases": {},
661- "default_plugin": "none",
662- "plugins": [
663- "author_year_title",
664- "none"
665- ]
666- }
667-}
668
669=== modified file 'pybtex/style/formatting/__init__.py'
670--- pybtex/style/formatting/__init__.py 2013-08-07 07:30:30 +0000
671+++ pybtex/style/formatting/__init__.py 2014-02-04 13:09:32 +0000
672@@ -31,7 +31,6 @@
673
674
675 class BaseStyle(Plugin):
676- default_plugin = 'unsrt'
677 default_name_style = None
678 default_label_style = None
679 default_sorting_style = None
680
681=== modified file 'pybtex/style/formatting/alpha.py'
682--- pybtex/style/formatting/alpha.py 2012-08-09 10:25:06 +0000
683+++ pybtex/style/formatting/alpha.py 2014-02-04 13:09:32 +0000
684@@ -23,6 +23,5 @@
685
686
687 class Style(UnsrtStyle):
688- name = 'alpha'
689 default_sorting_style = 'author_year_title'
690 default_label_style = 'alpha'
691
692=== modified file 'pybtex/style/formatting/plain.py'
693--- pybtex/style/formatting/plain.py 2012-02-12 20:44:25 +0000
694+++ pybtex/style/formatting/plain.py 2014-02-04 13:09:32 +0000
695@@ -23,5 +23,4 @@
696
697
698 class Style(UnsrtStyle):
699- name = 'plain'
700 default_sorting_style = 'author_year_title'
701
702=== modified file 'pybtex/style/formatting/unsrt.py'
703--- pybtex/style/formatting/unsrt.py 2012-08-07 13:19:51 +0000
704+++ pybtex/style/formatting/unsrt.py 2014-02-04 13:09:32 +0000
705@@ -37,7 +37,6 @@
706 date = words [optional_field('month'), field('year')]
707
708 class Style(BaseStyle):
709- name = 'unsrt'
710
711 def format_names(self, role, as_sentence=True):
712 formatted_names = names(role, sep=', ', sep2 = ' and ', last_sep=', and ')
713
714=== modified file 'pybtex/style/formatting/unsrtalpha.py'
715--- pybtex/style/formatting/unsrtalpha.py 2012-08-09 10:25:06 +0000
716+++ pybtex/style/formatting/unsrtalpha.py 2014-02-04 13:09:32 +0000
717@@ -23,5 +23,4 @@
718
719
720 class Style(UnsrtStyle):
721- name = 'unsrtalpha'
722 default_label_style = 'alpha'
723
724=== modified file 'pybtex/style/labels/__init__.py'
725--- pybtex/style/labels/__init__.py 2013-08-02 08:12:50 +0000
726+++ pybtex/style/labels/__init__.py 2014-02-04 13:09:32 +0000
727@@ -22,12 +22,8 @@
728 from pybtex.plugin import Plugin
729 from pybtex.textutils import width
730
731-available_plugins = ('number', 'alpha')
732-
733
734 class BaseLabelStyle(Plugin):
735- default_plugin = 'number'
736-
737 def get_longest_label(self, formatted_entries):
738 labels = (entry.label for entry in formatted_entries)
739 return max(labels, key=width)
740
741=== modified file 'pybtex/style/labels/alpha.py'
742--- pybtex/style/labels/alpha.py 2013-08-05 12:55:10 +0000
743+++ pybtex/style/labels/alpha.py 2014-02-04 13:09:32 +0000
744@@ -51,7 +51,6 @@
745 return _nonalnum_pattern.sub(u'', _strip_accents(s))
746
747 class LabelStyle(BaseLabelStyle):
748- name = 'alpha'
749
750 def format_labels(self, sorted_entries):
751 labels = [self.format_label(entry) for entry in sorted_entries]
752
753=== modified file 'pybtex/style/labels/number.py'
754--- pybtex/style/labels/number.py 2013-08-01 16:52:31 +0000
755+++ pybtex/style/labels/number.py 2014-02-04 13:09:32 +0000
756@@ -24,7 +24,6 @@
757
758
759 class LabelStyle(BaseLabelStyle):
760- name = 'number'
761
762 def format_labels(self, sorted_entries):
763 for number, entry in enumerate(sorted_entries):
764
765=== modified file 'pybtex/style/names/__init__.py'
766--- pybtex/style/names/__init__.py 2012-02-12 20:44:25 +0000
767+++ pybtex/style/names/__init__.py 2014-02-04 13:09:32 +0000
768@@ -27,12 +27,7 @@
769 from pybtex.style.template import join, together, node, _format_list
770
771
772-available_plugins = ('plain', 'lastfirst')
773-
774-
775 class BaseNameStyle(Plugin):
776- default_plugin = 'plain'
777-
778 def format(self, person, abbr=False):
779 raise NotImplementedError
780
781
782=== modified file 'pybtex/style/names/lastfirst.py'
783--- pybtex/style/names/lastfirst.py 2012-02-12 20:44:25 +0000
784+++ pybtex/style/names/lastfirst.py 2014-02-04 13:09:32 +0000
785@@ -25,8 +25,6 @@
786
787
788 class NameStyle(BaseNameStyle):
789- name = 'lastfirst'
790- aliases = 'last_first',
791
792 def format(self, person, abbr=False):
793 r"""
794
795=== modified file 'pybtex/style/names/plain.py'
796--- pybtex/style/names/plain.py 2012-02-12 20:44:25 +0000
797+++ pybtex/style/names/plain.py 2014-02-04 13:09:32 +0000
798@@ -25,7 +25,6 @@
799
800
801 class NameStyle(BaseNameStyle):
802- name = 'plain'
803
804 def format(self, person, abbr=False):
805 r"""
806
807=== modified file 'pybtex/style/sorting/__init__.py'
808--- pybtex/style/sorting/__init__.py 2012-02-12 20:44:25 +0000
809+++ pybtex/style/sorting/__init__.py 2014-02-04 13:09:32 +0000
810@@ -23,8 +23,6 @@
811
812
813 class BaseSortingStyle(Plugin):
814- default_plugin = 'none'
815-
816 def sorting_key(self, entry):
817 raise NotImplementedError
818
819
820=== modified file 'pybtex/style/sorting/author_year_title.py'
821--- pybtex/style/sorting/author_year_title.py 2012-08-09 13:05:30 +0000
822+++ pybtex/style/sorting/author_year_title.py 2014-02-04 13:09:32 +0000
823@@ -23,7 +23,6 @@
824
825
826 class SortingStyle(BaseSortingStyle):
827- name = 'author_year_title'
828
829 def sorting_key(self, entry):
830 if entry.type in ('book', 'inbook'):
831
832=== modified file 'pybtex/style/sorting/none.py'
833--- pybtex/style/sorting/none.py 2012-02-12 20:44:25 +0000
834+++ pybtex/style/sorting/none.py 2014-02-04 13:09:32 +0000
835@@ -23,7 +23,6 @@
836
837
838 class SortingStyle(BaseSortingStyle):
839- name = 'none'
840
841 def sort(self, entries):
842 return entries
843
844=== added file 'pybtex/tests/plugin_test.py'
845--- pybtex/tests/plugin_test.py 1970-01-01 00:00:00 +0000
846+++ pybtex/tests/plugin_test.py 2014-02-04 13:09:32 +0000
847@@ -0,0 +1,119 @@
848+# Copyright (c) 2014 Matthias C. M. Troffaes
849+#
850+# Permission is hereby granted, free of charge, to any person obtaining
851+# a copy of this software and associated documentation files (the
852+# "Software"), to deal in the Software without restriction, including
853+# without limitation the rights to use, copy, modify, merge, publish,
854+# distribute, sublicense, and/or sell copies of the Software, and to
855+# permit persons to whom the Software is furnished to do so, subject to
856+# the following conditions:
857+#
858+# The above copyright notice and this permission notice shall be
859+# included in all copies or substantial portions of the Software.
860+#
861+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
862+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
863+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
864+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
865+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
866+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
867+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
868+
869+import nose.tools
870+
871+import pybtex.database.input.bibtex
872+import pybtex.plugin
873+import pybtex.style.formatting.plain
874+
875+
876+def test_plugin_loader():
877+ """Check that all enumerated plugins can be imported."""
878+ for group in pybtex.plugin._DEFAULT_PLUGINS:
879+ for name in pybtex.plugin.enumerate_plugin_names(group):
880+ pybtex.plugin.find_plugin(group, name)
881+
882+
883+class TestPlugin1(pybtex.plugin.Plugin):
884+ pass
885+
886+
887+class TestPlugin2(pybtex.plugin.Plugin):
888+ pass
889+
890+
891+class TestPlugin3(pybtex.plugin.Plugin):
892+ pass
893+
894+
895+def test_register_plugin_1():
896+ nose.tools.assert_true(
897+ pybtex.plugin.register_plugin(
898+ 'pybtex.style.formatting', 'yippikayee', TestPlugin1))
899+ nose.tools.assert_is(
900+ TestPlugin1, pybtex.plugin.find_plugin(
901+ 'pybtex.style.formatting', 'yippikayee'))
902+ nose.tools.assert_false(
903+ pybtex.plugin.register_plugin(
904+ 'pybtex.style.formatting', 'yippikayee', TestPlugin2))
905+ nose.tools.assert_is(
906+ TestPlugin1, pybtex.plugin.find_plugin(
907+ 'pybtex.style.formatting', 'yippikayee'))
908+ nose.tools.assert_true(
909+ pybtex.plugin.register_plugin(
910+ 'pybtex.style.formatting', 'yippikayee', TestPlugin2, force=True))
911+ nose.tools.assert_is(
912+ TestPlugin2, pybtex.plugin.find_plugin(
913+ 'pybtex.style.formatting', 'yippikayee'))
914+
915+
916+def test_register_plugin_2():
917+ nose.tools.assert_false(
918+ pybtex.plugin.register_plugin(
919+ 'pybtex.style.formatting', 'plain', TestPlugin2))
920+ plugin = pybtex.plugin.find_plugin('pybtex.style.formatting', 'plain')
921+ nose.tools.assert_is_not(plugin, TestPlugin2)
922+ nose.tools.assert_is(plugin, pybtex.style.formatting.plain.Style)
923+
924+
925+def test_register_plugin_3():
926+ nose.tools.assert_true(
927+ pybtex.plugin.register_plugin(
928+ 'pybtex.style.formatting.suffixes', '.woo', TestPlugin3))
929+ plugin = pybtex.plugin.find_plugin(
930+ 'pybtex.style.formatting', filename='test.woo')
931+ nose.tools.assert_is(plugin, TestPlugin3)
932+
933+
934+def test_bad_find_plugin():
935+ nose.tools.assert_raises(
936+ pybtex.plugin.PluginGroupNotFound,
937+ lambda: pybtex.plugin.find_plugin("pybtex.invalid.group", "__oops"))
938+ nose.tools.assert_raises(
939+ pybtex.plugin.PluginNotFound,
940+ lambda: pybtex.plugin.find_plugin("pybtex.style.formatting", "__oops"))
941+ nose.tools.assert_raises(
942+ pybtex.plugin.PluginNotFound,
943+ lambda: pybtex.plugin.find_plugin("pybtex.style.formatting",
944+ filename="oh.__oops"))
945+
946+
947+def test_bad_register_plugin():
948+ nose.tools.assert_raises(
949+ pybtex.plugin.PluginGroupNotFound,
950+ lambda: pybtex.plugin.register_plugin(
951+ "pybtex.invalid.group", "__oops", TestPlugin1))
952+ nose.tools.assert_raises(
953+ pybtex.plugin.PluginGroupNotFound,
954+ lambda: pybtex.plugin.register_plugin(
955+ "pybtex.invalid.group.suffixes", ".__oops", TestPlugin1))
956+ # suffixes must start with a dot
957+ nose.tools.assert_raises(
958+ ValueError,
959+ lambda: pybtex.plugin.register_plugin(
960+ "pybtex.style.formatting.suffixes", "notasuffix", TestPlugin1))
961+
962+
963+def test_plugin_suffix():
964+ plugin = pybtex.plugin.find_plugin(
965+ "pybtex.database.input", filename="test.bib")
966+ nose.tools.assert_is(plugin, pybtex.database.input.bibtex.Parser)
967
968=== modified file 'setup.py'
969--- setup.py 2013-08-07 07:36:40 +0000
970+++ setup.py 2014-02-04 13:09:32 +0000
971@@ -92,6 +92,66 @@
972 ],
973 include_package_data=True,
974 cmdclass={'sdist' : Sdist},
975+ entry_points={
976+ 'pybtex.database.input': [
977+ 'bibtex = pybtex.database.input.bibtex:Parser',
978+ 'bibtexml = pybtex.database.input.bibtexml:Parser',
979+ 'bibyaml = pybtex.database.input.bibyaml:Parser',
980+ 'yaml = pybtex.database.input.bibyaml:Parser',
981+ ],
982+ 'pybtex.database.input.suffixes': [
983+ '.bib = pybtex.database.input.bibtex:Parser',
984+ '.xml = pybtex.database.input.bibtexml:Parser',
985+ '.bibtexml = pybtex.database.input.bibtexml:Parser',
986+ '.bibyaml = pybtex.database.input.bibyaml:Parser',
987+ '.yaml = pybtex.database.input.bibyaml:Parser',
988+ ],
989+ 'pybtex.database.output': [
990+ 'bibtex = pybtex.database.output.bibtex:Writer',
991+ 'bibtexml = pybtex.database.output.bibtexml:Writer',
992+ 'bibyaml = pybtex.database.output.bibyaml:Writer',
993+ 'yaml = pybtex.database.output.bibyaml:Writer',
994+ ],
995+ 'pybtex.database.output.suffixes': [
996+ '.bib = pybtex.database.output.bibtex:Writer',
997+ '.xml = pybtex.database.output.bibtexml:Writer',
998+ '.bibtexml = pybtex.database.output.bibtexml:Writer',
999+ '.bibyaml = pybtex.database.output.bibyaml:Writer',
1000+ '.yaml = pybtex.database.output.bibyaml:Writer',
1001+ ],
1002+ 'pybtex.backends': [
1003+ 'latex = pybtex.backends.latex:Backend',
1004+ 'html = pybtex.backends.html:Backend',
1005+ 'plaintext = pybtex.backends.plaintext:Backend',
1006+ 'text = pybtex.backends.plaintext:Backend',
1007+ ],
1008+ 'pybtex.backends.suffixes': [
1009+ '.bbl = pybtex.backends.latex:Backend',
1010+ '.tex = pybtex.backends.latex:Backend',
1011+ '.latex = pybtex.backends.latex:Backend',
1012+ '.html = pybtex.backends.html:Backend',
1013+ '.txt = pybtex.backends.plaintext:Backend',
1014+ ],
1015+ 'pybtex.style.labels': [
1016+ 'number = pybtex.style.labels.number:LabelStyle',
1017+ 'alpha = pybtex.style.labels.alpha:LabelStyle',
1018+ ],
1019+ 'pybtex.style.names': [
1020+ 'plain = pybtex.style.names.plain:NameStyle',
1021+ 'lastfirst = pybtex.style.names.lastfirst:NameStyle',
1022+ 'last_first = pybtex.style.names.lastfirst:NameStyle',
1023+ ],
1024+ 'pybtex.style.sorting': [
1025+ 'none = pybtex.style.sorting.none:SortingStyle',
1026+ 'author_year_title = pybtex.style.sorting.author_year_title:SortingStyle',
1027+ ],
1028+ 'pybtex.style.formatting': [
1029+ 'plain = pybtex.style.formatting.plain:Style',
1030+ 'unsrt = pybtex.style.formatting.unsrt:Style',
1031+ 'alpha = pybtex.style.formatting.alpha:Style',
1032+ 'unsrtalpha = pybtex.style.formatting.unsrtalpha:Style',
1033+ ],
1034+ },
1035 zip_safe=True,
1036 **extra
1037 )

Subscribers

People subscribed via source and target branches