Skip to content

Commit 9230403

Browse files
committed
feat: collect Pin.__call__ from the docstub of the same version
Signed-off-by: Jos Verlinde <[email protected]>
1 parent e7c29af commit 9230403

File tree

3 files changed

+48
-18
lines changed

3 files changed

+48
-18
lines changed

src/stubber/codemod/add_method.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,38 @@
55
"""
66

77

8+
from typing import Optional
89
import libcst as cst
910
from libcst import matchers as m
1011

11-
# todo: extract the call method from the docstubs
12-
FNDEF_PIN_CALL = '''
13-
def __call__(self, x: Optional[Any] = None) -> Any:
14-
"""
15-
Pin objects are callable. The call method provides a (fast) shortcut to set
16-
and get the value of the pin. It is equivalent to Pin.value([x]).
17-
See :meth:`Pin.value` for more details.
18-
"""
19-
...
20-
'''
2112

22-
# print(call_meth)
13+
# there is no simple way to re-use the code from multiple classes / methods
14+
# but could be added https://stackoverflow.com/questions/17522706/how-to-pass-an-instance-variable-to-a-decorator-inside-class-definition?noredirect=1&lq=1
15+
# so for now, just copy the code, or use a module scoped variable - but that is not thread safe
16+
17+
class CallFinder(m.MatcherDecoratableTransformer):
18+
"""Find the Pin.__call__ method and extract it from a (machine) module."""
19+
class_name: str = "Pin" # class name
20+
method_name: str = "__call__" # method name
21+
def __init__(self):
22+
super().__init__()
23+
self.call_meth: Optional[cst.FunctionDef] = None
24+
25+
@m.call_if_inside(m.ClassDef(name=m.Name(class_name)))
26+
@m.visit(m.FunctionDef(name=m.Name(method_name)))
27+
def detect_call(self, node: cst.FunctionDef) -> None:
28+
"""find the __call__ method and store it."""
29+
self.call_meth = node
2330

2431

2532
class CallAdder(m.MatcherDecoratableTransformer):
2633
"""Add a __call__ method to a class if it is missing."""
2734
class_name = "Pin" # class name
2835
has_call = 0 # number of __call__ methods found
2936

30-
def __init__(self) -> None:
37+
def __init__(self, call_meth :cst.FunctionDef) -> None:
3138
super().__init__()
32-
# parse the (default) call method once
33-
self.call_meth = cst.parse_statement(FNDEF_PIN_CALL)
39+
self.call_meth = call_meth
3440

3541

3642
@m.call_if_inside(m.ClassDef(name=m.Name(class_name)))

src/stubber/publish/merge_docstubs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def merge_all_docstubs(
100100
log.info(f"Merge docstubs for {merged_path.name} {candidate['version']}")
101101
result = copy_and_merge_docstubs(board_path, merged_path, doc_path)
102102
# Add methods from docstubs to the firmware stubs that do not exist in the firmware stubs
103-
add_machine_pin_call(merged_path)
103+
add_machine_pin_call(merged_path, candidate['version'])
104104
if result:
105105
merged += 1
106106
log.info(f"merged {merged} of {len(candidates)} candidates")

src/stubber/publish/missing_class_methods.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,44 @@
55
from pathlib import Path
66

77
import libcst as cst
8+
from loguru import logger as log
89

9-
from stubber.codemod.add_method import CallAdder
10+
from stubber.codemod.add_method import CallAdder, CallFinder
11+
from stubber.utils.config import CONFIG
1012
from stubber.utils.post import run_black
13+
from stubber.utils.versions import clean_version
1114

1215

13-
def add_machine_pin_call(merged_path: Path):
16+
def add_machine_pin_call(merged_path: Path, version: str):
1417
"""
1518
Add the __call__ method to the machine.Pin and pyb.Pin class
1619
in all pyb and machine/umachine stubs
1720
"""
21+
# TODO: this should be done in the merge_docstubs.py to avoid needing to run black twice
22+
# and to avoid having to parse the file twice
23+
24+
# first find the __call__ method in the default stubs
25+
mod_path = CONFIG.stub_path / f"micropython-{clean_version(version, flat=True)}-docstubs/machine.pyi"
26+
if not mod_path.exists():
27+
log.error(f"no docstubs found for {version}")
28+
return False
29+
log.trace(f"Parsing {mod_path} for __call__ method")
30+
source = mod_path.read_text()
31+
module = cst.parse_module(source)
32+
33+
call_finder = CallFinder()
34+
module.visit(call_finder)
35+
36+
if call_finder.call_meth is None:
37+
log.error("no __call__ method found")
38+
return False
39+
40+
# then use the CallAdder to add the __call__ method to all machine and pyb stubs
1841
mod_paths = [f for f in merged_path.rglob("*.*") if f.stem in {"machine", "umachine", "pyb"}]
1942
for mod_path in mod_paths:
2043
source = mod_path.read_text()
2144
machine_module = cst.parse_module(source)
22-
new_module = machine_module.visit(CallAdder())
45+
new_module = machine_module.visit(CallAdder(call_finder.call_meth))
2346
mod_path.write_text(new_module.code)
2447
run_black(mod_path)
48+
return True

0 commit comments

Comments
 (0)