Skip to content

Commit e35e68e

Browse files
committed
Add serializer based on ujson5
1 parent 8740497 commit e35e68e

4 files changed

Lines changed: 329 additions & 205 deletions

File tree

dict2css/__init__.py

Lines changed: 24 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131

3232
# stdlib
3333
from io import TextIOBase
34-
from typing import IO, Any, Dict, Iterable, List, Mapping, MutableMapping, Sequence, Union, cast
34+
from typing import IO, Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Sequence, Union
3535

3636
# 3rd party
37-
import tinycss2
38-
import tinycss2.ast
37+
import tinycss2 # type: ignore[import-untyped]
38+
import tinycss2.ast # type: ignore[import-untyped]
3939
from domdf_python_tools.paths import PathPlus
4040
from domdf_python_tools.typing import PathLike
4141
from domdf_python_tools.words import TAB
@@ -45,7 +45,7 @@
4545
from dict2css.serializer import CSSSerializer
4646

4747
__author__: str = "Dominic Davis-Foster"
48-
__copyright__: str = "2020-2021 Dominic Davis-Foster"
48+
__copyright__: str = "2020-2026 Dominic Davis-Foster"
4949
__license__: str = "MIT License"
5050
__version__: str = "0.3.0.post1"
5151
__email__: str = "dominic@davis-foster.co.uk"
@@ -57,10 +57,10 @@
5757
"dump",
5858
"loads",
5959
"load",
60-
"StyleSheet",
61-
"make_style",
6260
]
6361

62+
# TODO: allow int indent like json.dumps etc.
63+
6464
IMPORTANT = "important"
6565
"""
6666
The string ``'important'``.
@@ -73,7 +73,7 @@
7373

7474
Style = Mapping[str, Property]
7575
"""
76-
Type annotation representing a style for :func:`~.make_style` and :func:`~.dumps`.
76+
Type annotation representing a style for :func:`~.dumps` and :func:`~.dump`.
7777
7878
The keys are CSS properties.
7979
@@ -89,9 +89,11 @@ def dumps(
8989
styles: Mapping[str, Union[Style, Mapping]],
9090
*,
9191
indent: str = TAB,
92-
trailing_semicolon: bool = False,
92+
trailing_semicolon: Optional[bool] = None,
9393
indent_closing_brace: bool = False,
9494
minify: bool = False,
95+
sort_keys: bool = False,
96+
check_circular: bool = True,
9597
) -> str:
9698
r"""
9799
Construct a cascading style sheet from a dictionary.
@@ -135,6 +137,8 @@ def dumps(
135137
:param trailing_semicolon: Whether to add a semicolon to the end of the final property.
136138
:param indent_closing_brace:
137139
:param minify: Minify the CSS. Overrides all other options.
140+
:param sort_keys: Sort dictionary keys alphabetically.
141+
:param check_circular: Check for circular references.
138142
139143
:return: The style sheet as a string.
140144
@@ -146,37 +150,23 @@ def dumps(
146150
trailing_semicolon=trailing_semicolon,
147151
indent_closing_brace=indent_closing_brace,
148152
minify=minify,
153+
sort_keys=sort_keys,
154+
check_circular=check_circular,
149155
)
150156

151-
stylesheet: str = ''
152-
153-
with serializer.use():
154-
sheet = StyleSheet()
155-
156-
for selector, style in styles.items():
157-
if selector.startswith("@media"):
158-
sheet.add_media_styles(selector.split("@media")[1].strip(), cast(Mapping[str, Style], style))
159-
elif selector.startswith('@'):
160-
raise NotImplementedError("Only @media at-rules are supported at this time.")
161-
else:
162-
sheet.add_style(selector, cast(Style, style))
163-
164-
stylesheet = sheet.tostring()
165-
166-
if not serializer.minify:
167-
stylesheet = stylesheet.replace('}', "}\n")
168-
169-
return stylesheet
157+
return serializer.encode(styles)
170158

171159

172160
def dump(
173161
styles: Mapping[str, Union[Style, Mapping]],
174162
fp: Union[PathLike, IO],
175163
*,
176164
indent: str = TAB,
177-
trailing_semicolon: bool = False,
165+
trailing_semicolon: Optional[bool] = None,
178166
indent_closing_brace: bool = False,
179167
minify: bool = False,
168+
sort_keys: bool = False,
169+
check_circular: bool = True,
180170
) -> None:
181171
r"""
182172
Construct a style sheet from a dictionary and write it to ``fp``.
@@ -221,6 +211,8 @@ def dump(
221211
:param trailing_semicolon: Whether to add a semicolon to the end of the final property.
222212
:param indent_closing_brace:
223213
:param minify: Minify the CSS. Overrides all other options.
214+
:param sort_keys: Sort dictionary keys alphabetically.
215+
:param check_circular: Check for circular references.
224216
225217
.. versionchanged:: 0.2.0
226218
@@ -234,6 +226,8 @@ def dump(
234226
indent=indent,
235227
trailing_semicolon=trailing_semicolon,
236228
indent_closing_brace=indent_closing_brace,
229+
sort_keys=sort_keys,
230+
check_circular=check_circular,
237231
minify=minify,
238232
)
239233

@@ -279,7 +273,7 @@ def parse_style(style: List[tinycss2.ast.Node]) -> MutableMapping[str, Property]
279273
styles_dict[_serialize(rule.prelude)] = parse_style(rule.content)
280274

281275
elif isinstance(rule, tinycss2.ast.AtRule):
282-
at_rule_styles = styles_dict[f"@{rule.at_keyword} {_serialize(rule.prelude)}"] = at_rule_styles
276+
at_rule_styles = styles_dict[f"@{rule.at_keyword} {_serialize(rule.prelude)}"]
283277

284278
for child in tinycss2.parse_blocks_contents(rule.content, skip_comments=True, skip_whitespace=True):
285279
at_rule_styles[_serialize(child.prelude)] = parse_style(child.content)
@@ -290,7 +284,7 @@ def parse_style(style: List[tinycss2.ast.Node]) -> MutableMapping[str, Property]
290284
return styles_dict
291285

292286

293-
def _serialize(nodes: Iterable[tinycss2.ast.Node]):
287+
def _serialize(nodes: Iterable[tinycss2.ast.Node]) -> str:
294288
return tinycss2.serialize(nodes).strip()
295289

296290

@@ -311,93 +305,3 @@ def load(fp: Union[PathLike, IO]) -> MutableMapping[str, MutableMapping[str, Any
311305
styles = PathPlus(fp).read_text()
312306

313307
return loads(styles)
314-
315-
316-
class StyleSheet(css_parser.css.CSSStyleSheet):
317-
r"""
318-
Represents a CSS style sheet.
319-
320-
.. raw:: latex
321-
322-
\nopagebreak
323-
324-
.. autosummary-widths:: 7/16
325-
326-
"""
327-
328-
def __init__(self):
329-
super().__init__(validating=False)
330-
331-
def add(self, rule: css_parser.css.CSSRule) -> int:
332-
"""
333-
Add the ``rule`` to the style sheet.
334-
335-
:param rule:
336-
:type rule: :class:`css_parser.css.CSSRule`
337-
"""
338-
339-
return super().add(rule)
340-
341-
def add_style(
342-
self,
343-
selector: str,
344-
styles: Style,
345-
) -> None:
346-
"""
347-
Add a style to the style sheet.
348-
349-
:param selector:
350-
:param styles:
351-
"""
352-
353-
self.add(make_style(selector, styles))
354-
355-
def add_media_styles(
356-
self,
357-
media_query: str,
358-
styles: Mapping[str, Style],
359-
) -> None:
360-
"""
361-
Add a set of styles for a media query to the style sheet.
362-
363-
.. versionadded:: 0.2.0
364-
365-
:param media_query:
366-
:param styles:
367-
"""
368-
369-
media = css_parser.css.CSSMediaRule(media_query)
370-
371-
for selector, style in styles.items():
372-
media.add(make_style(selector, style))
373-
374-
self.add(media)
375-
376-
def tostring(self) -> str:
377-
"""
378-
Returns the style sheet as a string.
379-
"""
380-
381-
return self.cssText.decode("UTF-8")
382-
383-
384-
def make_style(selector: str, styles: Style) -> css_parser.css.CSSStyleRule:
385-
"""
386-
Create a CSS Style Rule from a dictionary.
387-
388-
:param selector:
389-
:param styles:
390-
391-
:rtype: :class:`css_parser.css.CSSStyleRule`
392-
"""
393-
394-
style = css_parser.css.CSSStyleDeclaration()
395-
style.validating = False
396-
397-
for name, properties in styles.items():
398-
if isinstance(properties, Sequence) and not isinstance(properties, str):
399-
style[name] = tuple(str(x) for x in properties)
400-
else:
401-
style[name] = str(properties)
402-
403-
return css_parser.css.CSSStyleRule(selectorText=selector, style=style)

0 commit comments

Comments
 (0)