Skip to content

Commit f41fd81

Browse files
committed
Swift: add docstring parsing
1 parent 73f977c commit f41fd81

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed

swift/codegen/lib/schema/defs.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from functools import singledispatch as _singledispatch
33
from swift.codegen.lib import schema as _schema
44
import inspect as _inspect
5+
from dataclasses import dataclass as _dataclass
56

67

78
class _ChildModifier(_schema.PropertyModifier):
@@ -11,6 +12,14 @@ def modify(self, prop: _schema.Property):
1112
prop.is_child = True
1213

1314

15+
@_dataclass
16+
class _DescModifier(_schema.PropertyModifier):
17+
description: str
18+
19+
def modify(self, prop: _schema.Property):
20+
prop.doc = _schema.split_doc(self.description)
21+
22+
1423
def include(source: str):
1524
# add to `includes` variable in calling context
1625
_inspect.currentframe().f_back.f_locals.setdefault(
@@ -97,6 +106,7 @@ def f(cls: type) -> type:
97106
list = _TypeModifier(_Listifier())
98107

99108
child = _ChildModifier()
109+
desc = _DescModifier
100110

101111
qltest = _Namespace(
102112
skip=_Pragma("qltest_skip"),

swift/codegen/lib/schema/schema.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" schema.yml format representation """
22
import pathlib
3+
import re
34
import types
45
import typing
56
from dataclasses import dataclass, field
@@ -35,6 +36,8 @@ class Kind(Enum):
3536
type: Optional[str] = None
3637
is_child: bool = False
3738
pragmas: List[str] = field(default_factory=list)
39+
doc_name: Optional[str] = None
40+
doc: List[str] = field(default_factory=list)
3841

3942
@property
4043
def is_single(self) -> bool:
@@ -76,6 +79,7 @@ class Class:
7679
group: str = ""
7780
pragmas: List[str] = field(default_factory=list)
7881
ipa: Optional[IpaInfo] = None
82+
doc: List[str] = field(default_factory=list)
7983

8084
@property
8185
def final(self):
@@ -154,6 +158,27 @@ def modify(self, prop: Property):
154158
raise NotImplementedError
155159

156160

161+
def split_doc(doc):
162+
# implementation inspired from https://peps.python.org/pep-0257/
163+
if not doc:
164+
return []
165+
lines = doc.splitlines()
166+
# Determine minimum indentation (first line doesn't count):
167+
strippedlines = (line.lstrip() for line in lines[1:])
168+
indents = [len(line) - len(stripped) for line, stripped in zip(lines[1:], strippedlines) if stripped]
169+
# Remove indentation (first line is special):
170+
trimmed = [lines[0].strip()]
171+
if indents:
172+
indent = min(indents)
173+
trimmed.extend(line[indent:].rstrip() for line in lines[1:])
174+
# Strip off trailing and leading blank lines:
175+
while trimmed and not trimmed[-1]:
176+
trimmed.pop()
177+
while trimmed and not trimmed[0]:
178+
trimmed.pop(0)
179+
return trimmed
180+
181+
157182
@dataclass
158183
class _PropertyNamer(PropertyModifier):
159184
name: str
@@ -181,6 +206,7 @@ def _get_class(cls: type) -> Class:
181206
a | _PropertyNamer(n)
182207
for n, a in cls.__dict__.get("__annotations__", {}).items()
183208
],
209+
doc=split_doc(cls.__doc__),
184210
)
185211

186212

swift/codegen/test/test_schema.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class C(A):
141141
class D(B, C):
142142
pass
143143

144+
144145
#
145146

146147

@@ -409,5 +410,137 @@ class B:
409410
pass
410411

411412

413+
def test_class_docstring():
414+
@schema.load
415+
class data:
416+
class A:
417+
"""Very important class."""
418+
419+
assert data.classes == {
420+
'A': schema.Class('A', doc=["Very important class."])
421+
}
422+
423+
424+
def test_property_docstring():
425+
@schema.load
426+
class data:
427+
class A:
428+
x: int | defs.desc("very important property.")
429+
430+
assert data.classes == {
431+
'A': schema.Class('A', properties=[schema.SingleProperty('x', 'int', doc=["very important property."])])
432+
}
433+
434+
435+
def test_class_docstring_newline():
436+
@schema.load
437+
class data:
438+
class A:
439+
"""Very important
440+
class."""
441+
442+
assert data.classes == {
443+
'A': schema.Class('A', doc=["Very important", "class."])
444+
}
445+
446+
447+
def test_property_docstring_newline():
448+
@schema.load
449+
class data:
450+
class A:
451+
x: int | defs.desc("""very important
452+
property.""")
453+
454+
assert data.classes == {
455+
'A': schema.Class('A', properties=[schema.SingleProperty('x', 'int', doc=["very important", "property."])])
456+
}
457+
458+
459+
def test_class_docstring_stripped():
460+
@schema.load
461+
class data:
462+
class A:
463+
"""
464+
465+
Very important class.
466+
467+
"""
468+
469+
assert data.classes == {
470+
'A': schema.Class('A', doc=["Very important class."])
471+
}
472+
473+
474+
def test_property_docstring_stripped():
475+
@schema.load
476+
class data:
477+
class A:
478+
x: int | defs.desc("""
479+
480+
very important property.
481+
482+
""")
483+
484+
assert data.classes == {
485+
'A': schema.Class('A', properties=[schema.SingleProperty('x', 'int', doc=["very important property."])])
486+
}
487+
488+
489+
def test_class_docstring_split():
490+
@schema.load
491+
class data:
492+
class A:
493+
"""Very important class.
494+
495+
As said, very important."""
496+
497+
assert data.classes == {
498+
'A': schema.Class('A', doc=["Very important class.", "", "As said, very important."])
499+
}
500+
501+
502+
def test_property_docstring_split():
503+
@schema.load
504+
class data:
505+
class A:
506+
x: int | defs.desc("""very important property.
507+
508+
Very very important.""")
509+
510+
assert data.classes == {
511+
'A': schema.Class('A', properties=[
512+
schema.SingleProperty('x', 'int', doc=["very important property.", "", "Very very important."])])
513+
}
514+
515+
516+
def test_class_docstring_indent():
517+
@schema.load
518+
class data:
519+
class A:
520+
"""
521+
Very important class.
522+
As said, very important.
523+
"""
524+
525+
assert data.classes == {
526+
'A': schema.Class('A', doc=["Very important class.", " As said, very important."])
527+
}
528+
529+
530+
def test_property_docstring_indent():
531+
@schema.load
532+
class data:
533+
class A:
534+
x: int | defs.desc("""
535+
very important property.
536+
Very very important.
537+
""")
538+
539+
assert data.classes == {
540+
'A': schema.Class('A', properties=[
541+
schema.SingleProperty('x', 'int', doc=["very important property.", " Very very important."])])
542+
}
543+
544+
412545
if __name__ == '__main__':
413546
sys.exit(pytest.main([__file__] + sys.argv[1:]))

0 commit comments

Comments
 (0)