1import functools
2import itertools
3import operator
4
5from jaraco.functools import pass_none
6from jaraco.text import yield_lines
7from more_itertools import consume
8
9from ._importlib import metadata
10from ._itertools import ensure_unique
11from .errors import OptionError
12
13
14def ensure_valid(ep):
15 """
16 Exercise one of the dynamic properties to trigger
17 the pattern match.
18 """
19 try:
20 ep.extras
21 except (AttributeError, AssertionError) as ex:
22 # Why both? See https://github.com/python/importlib_metadata/issues/488
23 msg = (
24 f"Problems to parse {ep}.\nPlease ensure entry-point follows the spec: "
25 "https://packaging.python.org/en/latest/specifications/entry-points/"
26 )
27 raise OptionError(msg) from ex
28
29
30def load_group(value, group):
31 """
32 Given a value of an entry point or series of entry points,
33 return each as an EntryPoint.
34 """
35 # normalize to a single sequence of lines
36 lines = yield_lines(value)
37 text = f'[{group}]\n' + '\n'.join(lines)
38 return metadata.EntryPoints._from_text(text)
39
40
41def by_group_and_name(ep):
42 return ep.group, ep.name
43
44
45def validate(eps: metadata.EntryPoints):
46 """
47 Ensure entry points are unique by group and name and validate each.
48 """
49 consume(map(ensure_valid, ensure_unique(eps, key=by_group_and_name)))
50 return eps
51
52
53@functools.singledispatch
54def load(eps):
55 """
56 Given a Distribution.entry_points, produce EntryPoints.
57 """
58 groups = itertools.chain.from_iterable(
59 load_group(value, group) for group, value in eps.items()
60 )
61 return validate(metadata.EntryPoints(groups))
62
63
64@load.register(str)
65def _(eps):
66 r"""
67 >>> ep, = load('[console_scripts]\nfoo=bar')
68 >>> ep.group
69 'console_scripts'
70 >>> ep.name
71 'foo'
72 >>> ep.value
73 'bar'
74 """
75 return validate(metadata.EntryPoints(metadata.EntryPoints._from_text(eps)))
76
77
78load.register(type(None), lambda x: x)
79
80
81@pass_none
82def render(eps: metadata.EntryPoints):
83 by_group = operator.attrgetter('group')
84 groups = itertools.groupby(sorted(eps, key=by_group), by_group)
85
86 return '\n'.join(f'[{group}]\n{render_items(items)}\n' for group, items in groups)
87
88
89def render_items(eps):
90 return '\n'.join(f'{ep.name} = {ep.value}' for ep in sorted(eps))