Skip to content

Commit 51360aa

Browse files
committed
feat: introduce _Placeholder class and update TpptSlideLayout to use placeholders for field definitions
1 parent 7e96f9e commit 51360aa

File tree

1 file changed

+155
-174
lines changed

1 file changed

+155
-174
lines changed

src/tppt/slide_layout.py

Lines changed: 155 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,114 @@
11
import datetime
2-
from typing import TYPE_CHECKING, Any, Self, overload
2+
from inspect import isclass
3+
from typing import (
4+
TYPE_CHECKING,
5+
Annotated,
6+
Any,
7+
ClassVar,
8+
Self,
9+
TypeVar,
10+
dataclass_transform,
11+
get_args,
12+
get_origin,
13+
get_type_hints,
14+
overload,
15+
)
16+
17+
from tppt.types import FilePath
318

419
if TYPE_CHECKING:
520
from tppt._pptx.slide import SlideBuilder
621

7-
8-
class TpptSlideLayout:
9-
"""Base class for all slide layouts."""
22+
_AnyType = TypeVar("_AnyType")
23+
24+
25+
class _Placeholder:
26+
def __init__(self, description: str = ""):
27+
self.description = description
28+
self.value = None
29+
30+
def __repr__(self) -> str:
31+
return f"Placeholder({self.description!r})"
32+
33+
@classmethod
34+
def __class_getitem__(cls, item: Any) -> Any:
35+
return Annotated[item, cls()]
36+
37+
38+
Placeholder = Annotated[_AnyType, _Placeholder]
39+
40+
41+
class TpptSlideLayoutMeta(type):
42+
"""TpptSlideLayoutのメタクラス
43+
44+
プレースホルダーとして注釈されたフィールドを追跡します。
45+
"""
46+
47+
def __new__(
48+
mcs, name: str, bases: tuple[type, ...], namespace: dict[str, Any]
49+
) -> type:
50+
cls = super().__new__(mcs, name, bases, namespace)
51+
52+
# プレースホルダーフィールドを収集
53+
annotations = get_type_hints(cls, include_extras=True)
54+
placeholders = {}
55+
56+
for field_name, field_type in annotations.items():
57+
# Annotatedフィールドを検索
58+
if get_origin(field_type) is Annotated:
59+
args = get_args(field_type)
60+
# メタデータのチェック
61+
metadata_args = args[1:]
62+
63+
# Placeholderとして直接マークされたフィールドを検索
64+
if any(
65+
arg is _Placeholder
66+
or (isclass(arg) and arg.__name__ == "_Placeholder")
67+
for arg in metadata_args
68+
):
69+
placeholders[field_name] = args[0] # 実際の型
70+
continue
71+
72+
# ネストされたAnnotatedのチェック (Placeholder[T]パターン)
73+
# Placeholder[T] = Annotated[T, _Placeholder]のパターンを検出
74+
base_type = args[0]
75+
if get_origin(base_type) is Annotated:
76+
nested_args = get_args(base_type)
77+
if any(
78+
arg is _Placeholder
79+
or (isclass(arg) and arg.__name__ == "_Placeholder")
80+
for arg in nested_args[1:]
81+
):
82+
placeholders[field_name] = nested_args[0] # 実際の型
83+
continue
84+
85+
# プレースホルダー情報をクラスに保存
86+
setattr(cls, "__placeholders__", placeholders)
87+
return cls
88+
89+
90+
@dataclass_transform(
91+
eq_default=True,
92+
order_default=False,
93+
field_specifiers=(),
94+
)
95+
class TpptSlideLayout(metaclass=TpptSlideLayoutMeta):
96+
"""スライドレイアウトのベースクラス"""
97+
98+
__placeholders__: ClassVar[dict[str, Any]] = {}
99+
100+
def __init__(self, **kwargs) -> None:
101+
# すべてのフィールドに値を設定
102+
for field_name, field_value in kwargs.items():
103+
if field_name in self.__class__.__placeholders__:
104+
# プレースホルダーフィールドの場合
105+
setattr(self, field_name, field_value)
106+
elif field_name in self.__class__.__annotations__:
107+
setattr(self, field_name, field_value)
108+
else:
109+
raise TypeError(
110+
f"'{self.__class__.__name__}' got an unexpected keyword argument '{field_name}'"
111+
)
10112

11113
@overload
12114
def __get__(self, instance: None, objtype: type[Any]) -> type[Self]: ...
@@ -30,228 +132,107 @@ def builder(self) -> "SlideBuilder":
30132
class DefaultMasterSlide(TpptSlideLayout):
31133
"""Default master slide layout."""
32134

33-
def __init__(
34-
self,
35-
*,
36-
title: str,
37-
text: str,
38-
date: datetime.date | None = None,
39-
footer: str | None = None,
40-
slide_number: bool = True,
41-
) -> None:
42-
self.title = title
43-
self.text = text
44-
self.date = date
45-
self.footer = footer
46-
self.slide_number = slide_number
135+
title: Placeholder[str]
136+
text: Placeholder[str]
137+
date: Placeholder[datetime.date | None] = None
138+
footer: Placeholder[str | None] = None
47139

48140

49141
class DefaultTitleSlide(TpptSlideLayout):
50142
"""Title slide layout."""
51143

52-
def __init__(
53-
self,
54-
*,
55-
title: str,
56-
subtitle: str | None = None,
57-
date: datetime.date | None = None,
58-
footer: str | None = None,
59-
slide_number: bool = True,
60-
) -> None:
61-
self.title = title
62-
self.subtitle = subtitle
63-
self.date = date
64-
self.footer = footer
65-
self.slide_number = slide_number
144+
title: Placeholder[str]
145+
subtitle: Placeholder[str | None] = None
146+
date: Placeholder[datetime.date | None] = None
147+
footer: Placeholder[str | None] = None
66148

67149

68150
class DefaultTitleAndContentSlide(TpptSlideLayout):
69151
"""Title and content slide layout."""
70152

71-
def __init__(
72-
self,
73-
*,
74-
title: str,
75-
content: str | None = None,
76-
date: datetime.date | None = None,
77-
footer: str | None = None,
78-
slide_number: bool = True,
79-
) -> None:
80-
self.title = title
81-
self.content = content
82-
self.date = date
83-
self.footer = footer
84-
self.slide_number = slide_number
153+
title: Placeholder[str]
154+
content: Placeholder[str]
155+
date: Placeholder[datetime.date | None] = None
156+
footer: Placeholder[str | None] = None
85157

86158

87159
class DefaultSectionHeaderSlide(TpptSlideLayout):
88160
"""Section header slide layout."""
89161

90-
def __init__(
91-
self,
92-
*,
93-
title: str,
94-
text: str | None = None,
95-
date: datetime.date | None = None,
96-
footer: str | None = None,
97-
slide_number: bool = True,
98-
) -> None:
99-
self.title = title
100-
self.text = text
101-
self.date = date
102-
self.footer = footer
103-
self.slide_number = slide_number
162+
title: Placeholder[str]
163+
text: Placeholder[str]
164+
date: Placeholder[datetime.date | None] = None
165+
footer: Placeholder[str | None] = None
104166

105167

106168
class DefaultTwoContentSlide(TpptSlideLayout):
107169
"""Two content slide layout."""
108170

109-
def __init__(
110-
self,
111-
*,
112-
title: str,
113-
left_content: str | None = None,
114-
right_content: str | None = None,
115-
date: datetime.date | None = None,
116-
footer: str | None = None,
117-
slide_number: bool = True,
118-
) -> None:
119-
self.title = title
120-
self.left_content = left_content
121-
self.right_content = right_content
122-
self.date = date
123-
self.footer = footer
124-
self.slide_number = slide_number
171+
title: Placeholder[str]
172+
left_content: Placeholder[str]
173+
right_content: Placeholder[str]
174+
date: Placeholder[datetime.date | None] = None
175+
footer: Placeholder[str | None] = None
125176

126177

127178
class DefaultComparisonSlide(TpptSlideLayout):
128179
"""Comparison slide layout."""
129180

130-
def __init__(
131-
self,
132-
*,
133-
title: str,
134-
left_title: str | None = None,
135-
left_content: str | None = None,
136-
right_title: str | None = None,
137-
right_content: str | None = None,
138-
date: datetime.date | None = None,
139-
footer: str | None = None,
140-
slide_number: bool = True,
141-
) -> None:
142-
self.title = title
143-
self.left_title = left_title
144-
self.left_content = left_content
145-
self.right_title = right_title
146-
self.right_content = right_content
147-
self.date = date
148-
self.footer = footer
149-
self.slide_number = slide_number
181+
title: Placeholder[str]
182+
left_title: Placeholder[str]
183+
left_content: Placeholder[str]
184+
right_title: Placeholder[str]
185+
right_content: Placeholder[str]
186+
date: Placeholder[datetime.date | None] = None
187+
footer: Placeholder[str | None] = None
150188

151189

152190
class DefaultTitleOnlySlide(TpptSlideLayout):
153191
"""Title only slide layout."""
154192

155-
def __init__(
156-
self,
157-
*,
158-
title: str,
159-
date: datetime.date | None = None,
160-
footer: str | None = None,
161-
slide_number: bool = True,
162-
) -> None:
163-
self.title = title
164-
self.date = date
165-
self.footer = footer
166-
self.slide_number = slide_number
193+
title: Placeholder[str]
194+
date: Placeholder[datetime.date | None] = None
195+
footer: Placeholder[str | None] = None
167196

168197

169198
class DefaultBlankSlide(TpptSlideLayout):
170199
"""Blank slide layout."""
171200

172-
def __init__(
173-
self,
174-
*,
175-
date: datetime.date | None = None,
176-
footer: str | None = None,
177-
slide_number: bool = True,
178-
) -> None:
179-
self.date = date
180-
self.footer = footer
181-
self.slide_number = slide_number
201+
date: Placeholder[datetime.date | None] = None
202+
footer: Placeholder[str | None] = None
182203

183204

184205
class DefaultContentWithCaptionSlide(TpptSlideLayout):
185206
"""Content with caption slide layout."""
186207

187-
def __init__(
188-
self,
189-
*,
190-
title: str,
191-
content: str | None = None,
192-
date: datetime.date | None = None,
193-
footer: str | None = None,
194-
slide_number: bool = True,
195-
) -> None:
196-
self.title = title
197-
self.content = content
198-
self.date = date
199-
self.footer = footer
200-
self.slide_number = slide_number
208+
title: Placeholder[str]
209+
content: Placeholder[str]
210+
date: Placeholder[datetime.date | None] = None
211+
footer: Placeholder[str | None] = None
201212

202213

203214
class DefaultPictureWithCaptionSlide(TpptSlideLayout):
204215
"""Picture with caption slide layout."""
205216

206-
def __init__(
207-
self,
208-
*,
209-
title: str,
210-
picture_path: str | None = None,
211-
date: datetime.date | None = None,
212-
footer: str | None = None,
213-
slide_number: bool = True,
214-
) -> None:
215-
self.title = title
216-
self.picture_path = picture_path
217-
self.date = date
218-
self.footer = footer
219-
self.slide_number = slide_number
217+
title: Placeholder[str]
218+
picture_path: Placeholder[FilePath]
219+
date: Placeholder[datetime.date | None] = None
220+
footer: Placeholder[str | None] = None
220221

221222

222223
class DefaultTitleAndVerticalTextSlide(TpptSlideLayout):
223224
"""Title and vertical text slide layout."""
224225

225-
def __init__(
226-
self,
227-
*,
228-
title: str,
229-
vertical_text: str | None = None,
230-
date: datetime.date | None = None,
231-
footer: str | None = None,
232-
slide_number: bool = True,
233-
) -> None:
234-
self.title = title
235-
self.vertical_text = vertical_text
236-
self.date = date
237-
self.footer = footer
238-
self.slide_number = slide_number
226+
title: Placeholder[str]
227+
vertical_text: Placeholder[str]
228+
date: Placeholder[datetime.date | None] = None
229+
footer: Placeholder[str | None] = None
239230

240231

241232
class DefaultVerticalTitleAndTextSlide(TpptSlideLayout):
242233
"""Vertical title and text slide layout."""
243234

244-
def __init__(
245-
self,
246-
*,
247-
vertical_title: str,
248-
text: str | None = None,
249-
date: datetime.date | None = None,
250-
footer: str | None = None,
251-
slide_number: bool = True,
252-
) -> None:
253-
self.vertical_title = vertical_title
254-
self.text = text
255-
self.date = date
256-
self.footer = footer
257-
self.slide_number = slide_number
235+
vertical_title: Placeholder[str]
236+
text: Placeholder[str]
237+
date: Placeholder[datetime.date | None]
238+
footer: Placeholder[str | None]

0 commit comments

Comments
 (0)