varar 0.5.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- varar/__init__.py +7 -0
- varar/internal.py +170 -0
- varar/registry.py +6 -0
- varar-0.5.2.dist-info/METADATA +8 -0
- varar-0.5.2.dist-info/RECORD +6 -0
- varar-0.5.2.dist-info/WHEEL +4 -0
varar/__init__.py
ADDED
varar/internal.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Author-facing API for declaring step state — port of var/src/internal.ts (steps)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from re import Pattern
|
|
6
|
+
from typing import Any, Callable, Optional
|
|
7
|
+
|
|
8
|
+
from varar_core.registry import Registry, add_step, create_registry, define_parameter_type
|
|
9
|
+
from varar_core.step_role import StepKind
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Module-level mutable builder state (mirrors the module-scope vars in internal.ts)
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
_steps: list[dict[str, Any]] = []
|
|
16
|
+
# One factory per step-file; keyed by the FACTORY's __code__.co_filename
|
|
17
|
+
_context_factories_by_file: dict[str, Callable[[], Any]] = {}
|
|
18
|
+
_custom_types: list[dict[str, Any]] = []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Public API
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def steps(
|
|
27
|
+
factory: Optional[Callable[[], Any]] = None,
|
|
28
|
+
) -> tuple[
|
|
29
|
+
Callable[..., None],
|
|
30
|
+
Callable[[str], Callable[[Callable], Callable]],
|
|
31
|
+
Callable[[str], Callable[[Callable], Callable]],
|
|
32
|
+
]:
|
|
33
|
+
"""Register *factory* as the context-state constructor for its step file.
|
|
34
|
+
|
|
35
|
+
*factory* is optional: a step file whose steps are pure (nothing to
|
|
36
|
+
arrange, nothing to evolve) calls ``steps()`` bare and its handlers
|
|
37
|
+
receive an empty ``dict`` as state.
|
|
38
|
+
|
|
39
|
+
Returns ``(param, stimulus, sensor)``:
|
|
40
|
+
|
|
41
|
+
- ``param(name, regexp, parse=None, format=None)`` declares a custom
|
|
42
|
+
cucumber-expression parameter type. ``parse`` is a varargs function over
|
|
43
|
+
the capture groups (``parse(*groups)``); omitted, the parameter stays the
|
|
44
|
+
matched text. ``format`` is the display-only inverse.
|
|
45
|
+
- ``stimulus`` / ``sensor`` are decorator factories:
|
|
46
|
+
``@stimulus("expression")`` registers the decorated function as a step.
|
|
47
|
+
|
|
48
|
+
Source location is captured from the decorated function's ``__code__``
|
|
49
|
+
attributes (``co_filename`` / ``co_firstlineno``).
|
|
50
|
+
|
|
51
|
+
Raises ``RuntimeError`` if called more than once for the same source file
|
|
52
|
+
(keyed by *factory*'s ``co_filename``, or the caller's file when *factory*
|
|
53
|
+
is omitted), mirroring ``internal.ts``.
|
|
54
|
+
"""
|
|
55
|
+
if factory is None:
|
|
56
|
+
# No factory code object to key by — use the calling step file itself,
|
|
57
|
+
# which is where an inline factory would have been defined anyway.
|
|
58
|
+
source_file: str = sys._getframe(1).f_code.co_filename
|
|
59
|
+
factory = dict
|
|
60
|
+
else:
|
|
61
|
+
source_file = factory.__code__.co_filename
|
|
62
|
+
if source_file in _context_factories_by_file:
|
|
63
|
+
raise RuntimeError(
|
|
64
|
+
f"steps() called more than once in {source_file}"
|
|
65
|
+
)
|
|
66
|
+
_context_factories_by_file[source_file] = factory
|
|
67
|
+
|
|
68
|
+
def param(
|
|
69
|
+
name: str,
|
|
70
|
+
regexp: Any,
|
|
71
|
+
parse: Optional[Callable[..., Any]] = None,
|
|
72
|
+
format: Optional[Callable[[Any], str]] = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
_custom_types.append(
|
|
75
|
+
{"name": name, "regexp": regexp, "parse": parse, "format": format}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def _make_decorator(kind: StepKind) -> Callable[[str], Callable[[Callable], Callable]]:
|
|
79
|
+
def decorator_factory(expression: str) -> Callable[[Callable], Callable]:
|
|
80
|
+
def decorator(fn: Callable) -> Callable:
|
|
81
|
+
_steps.append(
|
|
82
|
+
{
|
|
83
|
+
"expression": expression,
|
|
84
|
+
"source_file": fn.__code__.co_filename,
|
|
85
|
+
"source_line": fn.__code__.co_firstlineno,
|
|
86
|
+
"handler": fn,
|
|
87
|
+
"kind": kind,
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
return fn
|
|
91
|
+
|
|
92
|
+
return decorator
|
|
93
|
+
|
|
94
|
+
return decorator_factory
|
|
95
|
+
|
|
96
|
+
return param, _make_decorator("stimulus"), _make_decorator("sensor")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def context_factory() -> Callable[[str], Any]:
|
|
100
|
+
"""Return a callable ``(step_file: str) -> state`` that invokes the
|
|
101
|
+
registered factory for *step_file*, or ``{}`` if none is registered.
|
|
102
|
+
|
|
103
|
+
Mirrors ``contextFactory()`` in ``internal.ts``.
|
|
104
|
+
"""
|
|
105
|
+
# Snapshot at call time (mirrors TS semantics where the map is closed over)
|
|
106
|
+
factories = dict(_context_factories_by_file)
|
|
107
|
+
|
|
108
|
+
def _invoke(step_file: str) -> Any:
|
|
109
|
+
f = factories.get(step_file)
|
|
110
|
+
return f() if f is not None else {}
|
|
111
|
+
|
|
112
|
+
return _invoke
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_registry() -> Registry:
|
|
116
|
+
"""Build and return a ``Registry`` from the accumulated steps and custom types.
|
|
117
|
+
|
|
118
|
+
Mirrors ``buildRegistry()`` in ``internal.ts``: custom parameter types are
|
|
119
|
+
registered first, then steps are compiled in registration order.
|
|
120
|
+
"""
|
|
121
|
+
r = create_registry()
|
|
122
|
+
for t in _custom_types:
|
|
123
|
+
kwargs: dict[str, Any] = {"name": t["name"], "regexp": t["regexp"]}
|
|
124
|
+
if t.get("parse") is not None:
|
|
125
|
+
kwargs["parse"] = t["parse"]
|
|
126
|
+
if t.get("format") is not None:
|
|
127
|
+
kwargs["format"] = t["format"]
|
|
128
|
+
r = define_parameter_type(r, **kwargs)
|
|
129
|
+
for e in _steps:
|
|
130
|
+
r = add_step(
|
|
131
|
+
r,
|
|
132
|
+
expression=e["expression"],
|
|
133
|
+
expression_source_file=e["source_file"],
|
|
134
|
+
expression_source_line=e["source_line"],
|
|
135
|
+
handler=e["handler"],
|
|
136
|
+
kind=e["kind"],
|
|
137
|
+
)
|
|
138
|
+
return r
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _reset_builder() -> None:
|
|
142
|
+
"""Clear all accumulated state. Use in tests between isolated scenarios."""
|
|
143
|
+
global _steps, _custom_types
|
|
144
|
+
_steps = []
|
|
145
|
+
_context_factories_by_file.clear()
|
|
146
|
+
_custom_types = []
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _custom_parameter_types() -> list[dict[str, str]]:
|
|
150
|
+
"""Conformance-harness accessor: the custom parameter types accumulated by
|
|
151
|
+
``param`` since the last ``_reset_builder``, projected to the
|
|
152
|
+
``{"name", "regexp"}`` wire shape ``to_registry_artifact`` serializes.
|
|
153
|
+
|
|
154
|
+
``regexp`` is the bare pattern source (``re.Pattern.pattern`` or the string
|
|
155
|
+
as authored — no flags/delimiters), the cross-port convention every
|
|
156
|
+
language's registry golden uses. Internal-only, mirrors the TS
|
|
157
|
+
``_customParameterTypes``.
|
|
158
|
+
"""
|
|
159
|
+
out: list[dict[str, str]] = []
|
|
160
|
+
for t in _custom_types:
|
|
161
|
+
rx = t["regexp"]
|
|
162
|
+
if isinstance(rx, Pattern):
|
|
163
|
+
rx = rx.pattern
|
|
164
|
+
elif not isinstance(rx, str):
|
|
165
|
+
raise TypeError(
|
|
166
|
+
f"parameter type {t['name']!r}: regexp lists are not supported by the "
|
|
167
|
+
"conformance projection yet"
|
|
168
|
+
)
|
|
169
|
+
out.append({"name": t["name"], "regexp": rx})
|
|
170
|
+
return out
|
varar/registry.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Adapter-only glue (mirrors @varar/varar/registry): build the registry and
|
|
2
|
+
context factory from the module-scope accumulator, and reset it between runs."""
|
|
3
|
+
|
|
4
|
+
from varar.internal import _custom_parameter_types, _reset_builder, build_registry, context_factory
|
|
5
|
+
|
|
6
|
+
__all__ = ["build_registry", "context_factory", "_reset_builder", "_custom_parameter_types"]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
varar/__init__.py,sha256=fhSJwv76-l-UjCkvxPK8pxPc9aaZKLmx9LBRAhava6s,144
|
|
2
|
+
varar/internal.py,sha256=xHgtA0_bDLfdOrYnAg7653zGt-ToqrO08vb-vYFct9Y,6445
|
|
3
|
+
varar/registry.py,sha256=vwMy4YjAlm652bGlulZm5bu5wt3hTv2-MbeBqdwjXFc,353
|
|
4
|
+
varar-0.5.2.dist-info/METADATA,sha256=76QT7W4jdwgoL9P7u9w7lsl77McHPYW4VQzekQ-AdL8,247
|
|
5
|
+
varar-0.5.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
6
|
+
varar-0.5.2.dist-info/RECORD,,
|