webwidgets 0.2.0__py3-none-any.whl → 0.2.1__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.
- webwidgets/__init__.py +1 -1
- webwidgets/compilation/css/__init__.py +2 -1
- webwidgets/compilation/css/css.py +86 -28
- webwidgets/compilation/html/html_node.py +3 -1
- webwidgets/utility/__init__.py +1 -0
- webwidgets/utility/representation.py +34 -0
- {webwidgets-0.2.0.dist-info → webwidgets-0.2.1.dist-info}/METADATA +1 -1
- webwidgets-0.2.1.dist-info/RECORD +15 -0
- webwidgets-0.2.0.dist-info/RECORD +0 -14
- {webwidgets-0.2.0.dist-info → webwidgets-0.2.1.dist-info}/WHEEL +0 -0
- {webwidgets-0.2.0.dist-info → webwidgets-0.2.1.dist-info}/licenses/LICENSE +0 -0
webwidgets/__init__.py
CHANGED
@@ -11,31 +11,49 @@
|
|
11
11
|
# =======================================================================
|
12
12
|
|
13
13
|
import itertools
|
14
|
-
from typing import Dict, List, Union
|
14
|
+
from typing import Callable, Dict, List, Union
|
15
15
|
from webwidgets.compilation.html.html_node import HTMLNode
|
16
|
+
from webwidgets.utility.representation import ReprMixin
|
16
17
|
from webwidgets.utility.validation import validate_css_identifier
|
17
18
|
|
18
19
|
|
19
|
-
class
|
20
|
+
class CSSRule(ReprMixin):
|
21
|
+
"""A rule in a style sheet.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, name: str, declarations: Dict[str, str]):
|
25
|
+
"""Stores the name and declarations of the rule.
|
26
|
+
|
27
|
+
:param name: The name of the rule.
|
28
|
+
:type name: str
|
29
|
+
:param declarations: The CSS declarations for the rule, specified as a
|
30
|
+
dictionary where keys are property names and values are their
|
31
|
+
corresponding values. For example: `{'color': 'red'}`
|
32
|
+
:type declarations: Dict[str, str]
|
33
|
+
"""
|
34
|
+
super().__init__()
|
35
|
+
self.name = name
|
36
|
+
self.declarations = declarations
|
37
|
+
|
38
|
+
|
39
|
+
class CompiledCSS(ReprMixin):
|
20
40
|
"""A utility class to hold compiled CSS rules.
|
21
41
|
"""
|
22
42
|
|
23
|
-
def __init__(self, trees: List[HTMLNode], rules:
|
24
|
-
mapping: Dict[int, List[
|
43
|
+
def __init__(self, trees: List[HTMLNode], rules: List[CSSRule],
|
44
|
+
mapping: Dict[int, List[CSSRule]]):
|
25
45
|
"""Stores compiled CSS rules.
|
26
46
|
|
27
47
|
:param trees: The HTML trees at the origin of the compilation. These
|
28
48
|
are the elements that have been styled with CSS properties.
|
29
49
|
:type trees: List[HTMLNode]
|
30
|
-
:param rules: The compiled CSS rules
|
31
|
-
|
32
|
-
`{'r0': {'color': 'red'}}`.
|
33
|
-
:type rules: Dict[str, Dict[str, str]]
|
50
|
+
:param rules: The compiled CSS rules.
|
51
|
+
:type rules: List[CSSRule]
|
34
52
|
:param mapping: A dictionary mapping each node ID to a list of rules
|
35
|
-
that achieve the same style.
|
36
|
-
|
37
|
-
:type mapping: Dict[int, List[str]]
|
53
|
+
that achieve the same style.
|
54
|
+
:type mapping: Dict[int, List[CSSRule]]
|
38
55
|
"""
|
56
|
+
super().__init__()
|
39
57
|
self.trees = trees
|
40
58
|
self.rules = rules
|
41
59
|
self.mapping = mapping
|
@@ -44,9 +62,9 @@ class CompiledCSS:
|
|
44
62
|
"""Converts the `rules` dictionary of the :py:class:`CompiledCSS`
|
45
63
|
object into CSS code.
|
46
64
|
|
47
|
-
|
48
|
-
is validated with :py:func:`validate_css_identifier`
|
49
|
-
converted.
|
65
|
+
Rule names are converted to class selectors. Note that each rule and
|
66
|
+
property name is validated with :py:func:`validate_css_identifier`
|
67
|
+
before being converted.
|
50
68
|
|
51
69
|
:param indent_size: The number of spaces to use for indentation in the
|
52
70
|
CSS code. Defaults to 4.
|
@@ -58,10 +76,11 @@ class CompiledCSS:
|
|
58
76
|
css_code = ""
|
59
77
|
indentation = ' ' * indent_size
|
60
78
|
|
61
|
-
# Writing down each rule
|
62
|
-
for i,
|
63
|
-
|
64
|
-
|
79
|
+
# Writing down each rule
|
80
|
+
for i, rule in enumerate(self.rules):
|
81
|
+
validate_css_identifier(rule.name)
|
82
|
+
css_code += f".{rule.name}" + " {\n"
|
83
|
+
for property_name, value in rule.declarations.items():
|
65
84
|
validate_css_identifier(property_name)
|
66
85
|
css_code += f"{indentation}{property_name}: {value};\n"
|
67
86
|
css_code += "}" + ('\n\n' if i < len(self.rules) - 1 else '')
|
@@ -69,7 +88,9 @@ class CompiledCSS:
|
|
69
88
|
return css_code
|
70
89
|
|
71
90
|
|
72
|
-
def compile_css(trees: Union[HTMLNode, List[HTMLNode]]
|
91
|
+
def compile_css(trees: Union[HTMLNode, List[HTMLNode]],
|
92
|
+
rule_namer: Callable[[List[CSSRule], int],
|
93
|
+
str] = None) -> CompiledCSS:
|
73
94
|
"""Computes optimized CSS rules from the given HTML trees.
|
74
95
|
|
75
96
|
The main purpose of this function is to reduce the number of CSS rules
|
@@ -97,15 +118,31 @@ def compile_css(trees: Union[HTMLNode, List[HTMLNode]]) -> CompiledCSS:
|
|
97
118
|
|
98
119
|
>>> compiled_css = compile_css(tree)
|
99
120
|
>>> print(compiled_css.rules)
|
100
|
-
|
101
|
-
'r0'
|
102
|
-
'r1'
|
103
|
-
'r2'
|
104
|
-
|
121
|
+
[
|
122
|
+
CSSRule(name='r0', declarations={'color': 'blue'}),
|
123
|
+
CSSRule(name='r1', declarations={'margin': '0'}),
|
124
|
+
CSSRule(name='r2', declarations={'padding': '0'})
|
125
|
+
]
|
105
126
|
|
106
127
|
:param trees: A single tree or a list of trees to optimize over. All
|
107
128
|
children are recursively included in the compilation.
|
108
129
|
:type trees: Union[HTMLNode, List[HTMLNode]]
|
130
|
+
:param rule_namer: A callable that takes two arguments, which are the list
|
131
|
+
of all compiled rules and an index within that list, and returns a
|
132
|
+
unique name for the rule at the given index.
|
133
|
+
|
134
|
+
This argument allows to customize the rule naming process and use names
|
135
|
+
other than the default `"r0"`, `"r1"`, etc. For example, it can be used
|
136
|
+
to achieve something similar to Tailwind CSS and name rules according
|
137
|
+
to what they achieve, e.g. by prefixing their name with `"m"` for
|
138
|
+
margin rules or `"p"` for padding rules. Note that all rule names will
|
139
|
+
be validated with the :py:func:`validate_css_identifier` function
|
140
|
+
before being written into CSS code.
|
141
|
+
|
142
|
+
Defaults to the :py:func:`default_rule_namer` function which implements
|
143
|
+
a default naming strategy where each rule is named `"r{i}"` where `i`
|
144
|
+
is the index of the rule in the list.
|
145
|
+
:type rule_namer: Callable[[List[CSSRule], int], str]
|
109
146
|
:return: The :py:class:`CompiledCSS` object containing the optimized rules.
|
110
147
|
Every HTML node present in one or more of the input trees is included
|
111
148
|
in the :py:attr:`CompiledCSS.mapping` attribute, even if the node does
|
@@ -117,14 +154,21 @@ def compile_css(trees: Union[HTMLNode, List[HTMLNode]]) -> CompiledCSS:
|
|
117
154
|
if isinstance(trees, HTMLNode):
|
118
155
|
trees = [trees]
|
119
156
|
|
157
|
+
# Handling default rule_namer
|
158
|
+
rule_namer = default_rule_namer if rule_namer is None else rule_namer
|
159
|
+
|
120
160
|
# For now, we just return a simple mapping where each CSS property defines
|
121
161
|
# its own ruleset
|
122
162
|
styles = {k: v for tree in trees for k, v in tree.get_styles().items()}
|
123
163
|
properties = set(itertools.chain.from_iterable(s.items()
|
124
164
|
for s in styles.values()))
|
125
|
-
rules =
|
126
|
-
|
127
|
-
|
165
|
+
rules = [CSSRule(None, dict([p])) # Initializing with no name
|
166
|
+
for p in sorted(properties)]
|
167
|
+
for i, rule in enumerate(rules): # Assigning name from callback
|
168
|
+
rule.name = rule_namer(rules, i)
|
169
|
+
rules = sorted(rules, key=lambda r: r.name) # Sorting by name
|
170
|
+
mapping = {node_id: [r for r in rules if
|
171
|
+
set(r.declarations.items()).issubset(style.items())]
|
128
172
|
for node_id, style in styles.items()}
|
129
173
|
return CompiledCSS(trees, rules, mapping)
|
130
174
|
|
@@ -156,7 +200,7 @@ def apply_css(css: CompiledCSS, tree: HTMLNode) -> None:
|
|
156
200
|
|
157
201
|
# Listing rules to add as classes. We do not add rules that are already
|
158
202
|
# there.
|
159
|
-
rules_to_add = [r for r in css.mapping[id(tree)] if r
|
203
|
+
rules_to_add = [r.name for r in css.mapping[id(tree)] if r.name not in
|
160
204
|
tree.attributes.get('class', '').split(' ')]
|
161
205
|
|
162
206
|
# Updating the class attribute. If it already exists and is not empty,
|
@@ -169,3 +213,17 @@ def apply_css(css: CompiledCSS, tree: HTMLNode) -> None:
|
|
169
213
|
# Recursively applying the CSS rules to all child nodes of the tree
|
170
214
|
for child in tree.children:
|
171
215
|
apply_css(css, child)
|
216
|
+
|
217
|
+
|
218
|
+
def default_rule_namer(rules: List[CSSRule], index: int) -> str:
|
219
|
+
"""Default rule naming function. Returns a string like "r{i}" where {i} is
|
220
|
+
the index of the rule.
|
221
|
+
|
222
|
+
:param rules: List of all compiled CSSRule objects. This argument is not
|
223
|
+
used in this function, but it can be used in other naming strategies.
|
224
|
+
:type rules: List[CSSRule]
|
225
|
+
:param index: Index of the rule being named.
|
226
|
+
:type index: int
|
227
|
+
:return: A string like `"r{i}"` where `i` is the index of the rule.
|
228
|
+
"""
|
229
|
+
return f'r{index}'
|
@@ -13,11 +13,12 @@
|
|
13
13
|
import copy
|
14
14
|
import itertools
|
15
15
|
from typing import Any, Dict, List, Union
|
16
|
+
from webwidgets.utility.representation import ReprMixin
|
16
17
|
from webwidgets.utility.sanitizing import sanitize_html_text
|
17
18
|
from webwidgets.utility.validation import validate_html_class
|
18
19
|
|
19
20
|
|
20
|
-
class HTMLNode:
|
21
|
+
class HTMLNode(ReprMixin):
|
21
22
|
"""Represents an HTML node (for example, a div or a span).
|
22
23
|
"""
|
23
24
|
|
@@ -34,6 +35,7 @@ class HTMLNode:
|
|
34
35
|
:param style: Dictionary of CSS properties for the node. Defaults to an empty dictionary.
|
35
36
|
:type style: Dict[str, str]
|
36
37
|
"""
|
38
|
+
super().__init__()
|
37
39
|
self.children = [] if children is None else children
|
38
40
|
self.attributes = {} if attributes is None else attributes
|
39
41
|
self.style = {} if style is None else style
|
webwidgets/utility/__init__.py
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
# =======================================================================
|
2
|
+
#
|
3
|
+
# This file is part of WebWidgets, a Python package for designing web
|
4
|
+
# UIs.
|
5
|
+
#
|
6
|
+
# You should have received a copy of the MIT License along with
|
7
|
+
# WebWidgets. If not, see <https://opensource.org/license/mit>.
|
8
|
+
#
|
9
|
+
# Copyright(C) 2025, mlaasri
|
10
|
+
#
|
11
|
+
# =======================================================================
|
12
|
+
|
13
|
+
class ReprMixin:
|
14
|
+
"""A mixin class that is represented with its variables when printed.
|
15
|
+
|
16
|
+
For example:
|
17
|
+
|
18
|
+
>>> class MyClass(RepresentedWithVars):
|
19
|
+
... def __init__(self, a, b):
|
20
|
+
... self.a = a
|
21
|
+
... self.b = b
|
22
|
+
>>> obj = MyClass(1, 2)
|
23
|
+
>>> print(obj)
|
24
|
+
MyClass(a=1, b=2)
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __repr__(self) -> str:
|
28
|
+
"""Returns a string exposing all member variables of the class.
|
29
|
+
|
30
|
+
:return: A string representing the class with its variables.
|
31
|
+
:rtype: str
|
32
|
+
"""
|
33
|
+
variables = ', '.join(f'{k}={repr(v)}' for k, v in vars(self).items())
|
34
|
+
return f"{self.__class__.__name__}({variables})"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
webwidgets/__init__.py,sha256=lWweaTCcPhSUa2zi5l7UnoDSgup_q81dh0pn_kGYYT0,481
|
2
|
+
webwidgets/compilation/__init__.py,sha256=hb61nhmPTghIzuA_hun98xT5Ngv7QFAgMHD44g-9uOo,433
|
3
|
+
webwidgets/compilation/css/__init__.py,sha256=ZisiFaw9RqNuVqewOjnEOLLLZwgjfW95yruMXGRGp_I,484
|
4
|
+
webwidgets/compilation/css/css.py,sha256=msFTyuIjEvLTKk4H-tvvr9HK1K6N-_hcF1pmOJWT1S8,9654
|
5
|
+
webwidgets/compilation/html/__init__.py,sha256=NRccbCUKdhmK51MnYFO8q1sS8fWhAggRnMiGDG7lQMQ,505
|
6
|
+
webwidgets/compilation/html/html_node.py,sha256=QptDwSD2ljzR5S-r5rXuRBeOz8EF55OlZlWTXgQzI4Y,10189
|
7
|
+
webwidgets/compilation/html/html_tags.py,sha256=U2HmhLkV6BOqTmgvIMlmMCQysQ7i2nEAVWJzZe74ucA,1388
|
8
|
+
webwidgets/utility/__init__.py,sha256=nY-j5tu45qGl09lRyMyxkT4zsUf2jCfeXWcx954yIvM,478
|
9
|
+
webwidgets/utility/representation.py,sha256=lQ15v_DZOHBQKLM8pzRE1tuJkU_modhPTpWpSJ2lBCE,1061
|
10
|
+
webwidgets/utility/sanitizing.py,sha256=OKJRDqk-OXYCWeK6ie3GdfQvb49wTs93kd971mg5oK0,5770
|
11
|
+
webwidgets/utility/validation.py,sha256=bUjpiGP59GW3DPvQ1hwR5ezBMmcSd6v4xlDLwTHZv_A,4261
|
12
|
+
webwidgets-0.2.1.dist-info/METADATA,sha256=Adl61Eg47Lw7AuZ5sJN_9-MMumjgsEVFZ33glOceGUk,550
|
13
|
+
webwidgets-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
14
|
+
webwidgets-0.2.1.dist-info/licenses/LICENSE,sha256=LISw1mw5eK6i8adFSlx6zltZxrJFwurngVdZAEU8g_I,1064
|
15
|
+
webwidgets-0.2.1.dist-info/RECORD,,
|
@@ -1,14 +0,0 @@
|
|
1
|
-
webwidgets/__init__.py,sha256=jkCnUIqDE1LjArrZKtl1Bpm9bf-ZmLUP5JBRTh3gayA,481
|
2
|
-
webwidgets/compilation/__init__.py,sha256=hb61nhmPTghIzuA_hun98xT5Ngv7QFAgMHD44g-9uOo,433
|
3
|
-
webwidgets/compilation/css/__init__.py,sha256=Yzk_Bq1ey8Bf4dcKNd_priwj6_DA9CwG9wFs-BbZRGE,449
|
4
|
-
webwidgets/compilation/css/css.py,sha256=8VjlXQtjY2fuhg49VtUi1RZF__Z7-qtj9-FAw3ZAtZA,7160
|
5
|
-
webwidgets/compilation/html/__init__.py,sha256=NRccbCUKdhmK51MnYFO8q1sS8fWhAggRnMiGDG7lQMQ,505
|
6
|
-
webwidgets/compilation/html/html_node.py,sha256=IEkb_zfIexU59cEk2Gf7nBo9Tm13DN_siLF7TBIGF1k,10095
|
7
|
-
webwidgets/compilation/html/html_tags.py,sha256=U2HmhLkV6BOqTmgvIMlmMCQysQ7i2nEAVWJzZe74ucA,1388
|
8
|
-
webwidgets/utility/__init__.py,sha256=_L0RxTAzAhjgZqg0eKEqpCJJeN_W2P9p5Clu7PETqCQ,448
|
9
|
-
webwidgets/utility/sanitizing.py,sha256=OKJRDqk-OXYCWeK6ie3GdfQvb49wTs93kd971mg5oK0,5770
|
10
|
-
webwidgets/utility/validation.py,sha256=bUjpiGP59GW3DPvQ1hwR5ezBMmcSd6v4xlDLwTHZv_A,4261
|
11
|
-
webwidgets-0.2.0.dist-info/METADATA,sha256=WDQK0YqoVt7imhRbXDw3QjrG9UgCflSvbHYDiNRM1mU,550
|
12
|
-
webwidgets-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
13
|
-
webwidgets-0.2.0.dist-info/licenses/LICENSE,sha256=LISw1mw5eK6i8adFSlx6zltZxrJFwurngVdZAEU8g_I,1064
|
14
|
-
webwidgets-0.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|