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 CHANGED
@@ -10,6 +10,6 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
- __version__ = "0.2.0" # Dynamically set by build backend
13
+ __version__ = "0.2.1" # Dynamically set by build backend
14
14
 
15
15
  from . import compilation
@@ -10,4 +10,5 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
- from .css import compile_css, CompiledCSS, apply_css
13
+ from .css import compile_css, CSSRule, CompiledCSS, apply_css, \
14
+ default_rule_namer
@@ -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 CompiledCSS:
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: Dict[str, Dict[str, str]],
24
- mapping: Dict[int, List[str]]):
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, specified as a dictionary mapping
31
- the rule's name to its corresponding CSS declarations. For example:
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. Rules must be specified by their name.
36
- For example: `{123: ['r0', 'r2'], 456: ['r1']}`.
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
- Each rule name is converted to a class selector and each property name
48
- is validated with :py:func:`validate_css_identifier` before being
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 from the rules dictionary
62
- for i, (name, declarations) in enumerate(self.rules.items()):
63
- css_code += f".{name}" + " {\n"
64
- for property_name, value in declarations.items():
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]]) -> CompiledCSS:
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': {'color': 'blue'},
102
- 'r1': {'margin': '0'},
103
- 'r2': {'padding': '0'}
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 = {f"r{i}": dict([p]) for i, p in enumerate(sorted(properties))}
126
- mapping = {node_id: sorted([n for n, r in rules.items() if
127
- set(r.items()).issubset(style.items())])
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 and r not in
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
@@ -10,5 +10,6 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
+ from .representation import *
13
14
  from .sanitizing import *
14
15
  from .validation import *
@@ -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})"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webwidgets
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A Python package for designing web UIs.
5
5
  Project-URL: Source code, https://github.com/mlaasri/WebWidgets
6
6
  Author: mlaasri
@@ -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,,