webwidgets 0.2.0__py3-none-any.whl → 1.0.0__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,9 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
- __version__ = "0.2.0" # Dynamically set by build backend
13
+ __version__ = "1.0.0" # Dynamically set by build backend
14
14
 
15
15
  from . import compilation
16
+ from . import utility
17
+ from .website import *
18
+ from .widgets import *
@@ -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,50 @@
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.indentation import get_indentation
17
+ from webwidgets.utility.representation import ReprMixin
16
18
  from webwidgets.utility.validation import validate_css_identifier
17
19
 
18
20
 
19
- class CompiledCSS:
21
+ class CSSRule(ReprMixin):
22
+ """A rule in a style sheet.
23
+ """
24
+
25
+ def __init__(self, name: str, declarations: Dict[str, str]):
26
+ """Stores the name and declarations of the rule.
27
+
28
+ :param name: The name of the rule.
29
+ :type name: str
30
+ :param declarations: The CSS declarations for the rule, specified as a
31
+ dictionary where keys are property names and values are their
32
+ corresponding values. For example: `{'color': 'red'}`
33
+ :type declarations: Dict[str, str]
34
+ """
35
+ super().__init__()
36
+ self.name = name
37
+ self.declarations = declarations
38
+
39
+
40
+ class CompiledCSS(ReprMixin):
20
41
  """A utility class to hold compiled CSS rules.
21
42
  """
22
43
 
23
- def __init__(self, trees: List[HTMLNode], rules: Dict[str, Dict[str, str]],
24
- mapping: Dict[int, List[str]]):
44
+ def __init__(self, trees: List[HTMLNode], rules: List[CSSRule],
45
+ mapping: Dict[int, List[CSSRule]]):
25
46
  """Stores compiled CSS rules.
26
47
 
27
48
  :param trees: The HTML trees at the origin of the compilation. These
28
49
  are the elements that have been styled with CSS properties.
29
50
  :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]]
51
+ :param rules: The compiled CSS rules.
52
+ :type rules: List[CSSRule]
34
53
  :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]]
54
+ that achieve the same style.
55
+ :type mapping: Dict[int, List[CSSRule]]
38
56
  """
57
+ super().__init__()
39
58
  self.trees = trees
40
59
  self.rules = rules
41
60
  self.mapping = mapping
@@ -44,9 +63,9 @@ class CompiledCSS:
44
63
  """Converts the `rules` dictionary of the :py:class:`CompiledCSS`
45
64
  object into CSS code.
46
65
 
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.
66
+ Rule names are converted to class selectors. Note that each rule and
67
+ property name is validated with :py:func:`validate_css_identifier`
68
+ before being converted.
50
69
 
51
70
  :param indent_size: The number of spaces to use for indentation in the
52
71
  CSS code. Defaults to 4.
@@ -56,12 +75,13 @@ class CompiledCSS:
56
75
  """
57
76
  # Initializing code and defining indentation
58
77
  css_code = ""
59
- indentation = ' ' * indent_size
78
+ indentation = get_indentation(level=1, size=indent_size)
60
79
 
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():
80
+ # Writing down each rule
81
+ for i, rule in enumerate(self.rules):
82
+ validate_css_identifier(rule.name)
83
+ css_code += f".{rule.name}" + " {\n"
84
+ for property_name, value in rule.declarations.items():
65
85
  validate_css_identifier(property_name)
66
86
  css_code += f"{indentation}{property_name}: {value};\n"
67
87
  css_code += "}" + ('\n\n' if i < len(self.rules) - 1 else '')
@@ -69,7 +89,9 @@ class CompiledCSS:
69
89
  return css_code
70
90
 
71
91
 
72
- def compile_css(trees: Union[HTMLNode, List[HTMLNode]]) -> CompiledCSS:
92
+ def compile_css(trees: Union[HTMLNode, List[HTMLNode]],
93
+ rule_namer: Callable[[List[CSSRule], int],
94
+ str] = None) -> CompiledCSS:
73
95
  """Computes optimized CSS rules from the given HTML trees.
74
96
 
75
97
  The main purpose of this function is to reduce the number of CSS rules
@@ -97,15 +119,31 @@ def compile_css(trees: Union[HTMLNode, List[HTMLNode]]) -> CompiledCSS:
97
119
 
98
120
  >>> compiled_css = compile_css(tree)
99
121
  >>> print(compiled_css.rules)
100
- {
101
- 'r0': {'color': 'blue'},
102
- 'r1': {'margin': '0'},
103
- 'r2': {'padding': '0'}
104
- }
122
+ [
123
+ CSSRule(name='r0', declarations={'color': 'blue'}),
124
+ CSSRule(name='r1', declarations={'margin': '0'}),
125
+ CSSRule(name='r2', declarations={'padding': '0'})
126
+ ]
105
127
 
106
128
  :param trees: A single tree or a list of trees to optimize over. All
107
129
  children are recursively included in the compilation.
108
130
  :type trees: Union[HTMLNode, List[HTMLNode]]
131
+ :param rule_namer: A callable that takes two arguments, which are the list
132
+ of all compiled rules and an index within that list, and returns a
133
+ unique name for the rule at the given index.
134
+
135
+ This argument allows to customize the rule naming process and use names
136
+ other than the default `"r0"`, `"r1"`, etc. For example, it can be used
137
+ to achieve something similar to Tailwind CSS and name rules according
138
+ to what they achieve, e.g. by prefixing their name with `"m"` for
139
+ margin rules or `"p"` for padding rules. Note that all rule names will
140
+ be validated with the :py:func:`validate_css_identifier` function
141
+ before being written into CSS code.
142
+
143
+ Defaults to the :py:func:`default_rule_namer` function which implements
144
+ a default naming strategy where each rule is named `"r{i}"` where `i`
145
+ is the index of the rule in the list.
146
+ :type rule_namer: Callable[[List[CSSRule], int], str]
109
147
  :return: The :py:class:`CompiledCSS` object containing the optimized rules.
110
148
  Every HTML node present in one or more of the input trees is included
111
149
  in the :py:attr:`CompiledCSS.mapping` attribute, even if the node does
@@ -117,14 +155,21 @@ def compile_css(trees: Union[HTMLNode, List[HTMLNode]]) -> CompiledCSS:
117
155
  if isinstance(trees, HTMLNode):
118
156
  trees = [trees]
119
157
 
120
- # For now, we just return a simple mapping where each CSS property defines
121
- # its own ruleset
158
+ # Handling default rule_namer
159
+ rule_namer = default_rule_namer if rule_namer is None else rule_namer
160
+
161
+ # We compute a simple mapping where each CSS property defines its own
162
+ # ruleset
122
163
  styles = {k: v for tree in trees for k, v in tree.get_styles().items()}
123
164
  properties = set(itertools.chain.from_iterable(s.items()
124
165
  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())])
166
+ rules = [CSSRule(None, dict([p])) # Initializing with no name
167
+ for p in sorted(properties)]
168
+ for i, rule in enumerate(rules): # Assigning name from callback
169
+ rule.name = rule_namer(rules, i)
170
+ rules = sorted(rules, key=lambda r: r.name) # Sorting by name
171
+ mapping = {node_id: [r for r in rules if
172
+ set(r.declarations.items()).issubset(style.items())]
128
173
  for node_id, style in styles.items()}
129
174
  return CompiledCSS(trees, rules, mapping)
130
175
 
@@ -156,7 +201,7 @@ def apply_css(css: CompiledCSS, tree: HTMLNode) -> None:
156
201
 
157
202
  # Listing rules to add as classes. We do not add rules that are already
158
203
  # there.
159
- rules_to_add = [r for r in css.mapping[id(tree)] if r and r not in
204
+ rules_to_add = [r.name for r in css.mapping[id(tree)] if r.name not in
160
205
  tree.attributes.get('class', '').split(' ')]
161
206
 
162
207
  # Updating the class attribute. If it already exists and is not empty,
@@ -169,3 +214,17 @@ def apply_css(css: CompiledCSS, tree: HTMLNode) -> None:
169
214
  # Recursively applying the CSS rules to all child nodes of the tree
170
215
  for child in tree.children:
171
216
  apply_css(css, child)
217
+
218
+
219
+ def default_rule_namer(rules: List[CSSRule], index: int) -> str:
220
+ """Default rule naming function. Returns a string like "r{i}" where {i} is
221
+ the index of the rule.
222
+
223
+ :param rules: List of all compiled CSSRule objects. This argument is not
224
+ used in this function, but it can be used in other naming strategies.
225
+ :type rules: List[CSSRule]
226
+ :param index: Index of the rule being named.
227
+ :type index: int
228
+ :return: A string like `"r{i}"` where `i` is the index of the rule.
229
+ """
230
+ return f'r{index}'
@@ -10,5 +10,6 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
- from .html_node import HTMLNode, no_start_tag, no_end_tag, one_line, RawText
13
+ from .html_node import HTMLNode, no_start_tag, no_end_tag, one_line, RawText, \
14
+ RootNode
14
15
  from .html_tags import TextNode
@@ -13,11 +13,13 @@
13
13
  import copy
14
14
  import itertools
15
15
  from typing import Any, Dict, List, Union
16
+ from webwidgets.utility.indentation import get_indentation
17
+ from webwidgets.utility.representation import ReprMixin
16
18
  from webwidgets.utility.sanitizing import sanitize_html_text
17
19
  from webwidgets.utility.validation import validate_html_class
18
20
 
19
21
 
20
- class HTMLNode:
22
+ class HTMLNode(ReprMixin):
21
23
  """Represents an HTML node (for example, a div or a span).
22
24
  """
23
25
 
@@ -34,6 +36,7 @@ class HTMLNode:
34
36
  :param style: Dictionary of CSS properties for the node. Defaults to an empty dictionary.
35
37
  :type style: Dict[str, str]
36
38
  """
39
+ super().__init__()
37
40
  self.children = [] if children is None else children
38
41
  self.attributes = {} if attributes is None else attributes
39
42
  self.style = {} if style is None else style
@@ -51,11 +54,13 @@ class HTMLNode:
51
54
  def _render_attributes(self) -> str:
52
55
  """Renders the attributes of the HTML node into a string that can be added to the start tag.
53
56
 
57
+ Attributes are sorted alphabetically by name.
58
+
54
59
  :return: A string containing all attribute key-value pairs separated by spaces.
55
60
  :rtype: str
56
61
  """
57
62
  return ' '.join(
58
- f'{key}="{value}"' for key, value in self.attributes.items()
63
+ f'{k}="{v}"' for k, v in sorted(self.attributes.items())
59
64
  )
60
65
 
61
66
  def add(self, child: 'HTMLNode') -> None:
@@ -134,7 +139,14 @@ class HTMLNode:
134
139
  :type collapse_empty: bool
135
140
  :param indent_size: The number of spaces to use for each indentation level.
136
141
  :type indent_size: int
137
- :param indent_level: The current level of indentation in the HTML output.
142
+ :param indent_level: The current level of indentation in the HTML
143
+ output.
144
+
145
+ This argument supports negative values as a way to flatten the HTML
146
+ output down to a certain depth with indentation resuming as normal
147
+ afterwards. If negative, `indent_level` is construed as an offset
148
+ on the depth in the HTML tree represented by the node, in which
149
+ case the node will wait for that depth before starting indentation.
138
150
  :type indent_level: int
139
151
  :param force_one_line: If True, forces all child elements to be rendered on a single line without additional
140
152
  indentation. Defaults to False.
@@ -149,7 +161,8 @@ class HTMLNode:
149
161
  :rtype: str or List[str]
150
162
  """
151
163
  # Opening the element
152
- indentation = "" if force_one_line else ' ' * indent_size * indent_level
164
+ indentation = "" if force_one_line else get_indentation(
165
+ indent_level, indent_size)
153
166
  html_lines = [indentation + self.start_tag]
154
167
 
155
168
  # If content must be in one line
@@ -262,7 +275,34 @@ class RawText(HTMLNode):
262
275
  """
263
276
  sanitized = sanitize_html_text(
264
277
  self.text, replace_all_entities=replace_all_entities)
265
- line = ' ' * indent_size * indent_level + sanitized
278
+ line = get_indentation(indent_level, indent_size) + sanitized
266
279
  if return_lines:
267
280
  return [line]
268
281
  return line
282
+
283
+
284
+ @no_start_tag
285
+ @no_end_tag
286
+ class RootNode(HTMLNode):
287
+ """The root node of an HTML document.
288
+
289
+ This is the top-level node that contains all other nodes.
290
+ """
291
+
292
+ def to_html(self, indent_level: int = 0, **kwargs: Any) -> Union[str, List[str]]:
293
+ """Converts the root node to HTML code.
294
+
295
+ This method overrides :py:meth:`HTMLNode.to_html`. The only difference
296
+ between this method and that of the base class is that the indentation
297
+ level is adjusted by one level, so the root node acts as an array of
298
+ elements.
299
+
300
+ :param indent_level: See :py:meth:`HTMLNode.to_html`.
301
+ :type indent_level: int
302
+ :param kwargs: Other keyword arguments. These are passed to
303
+ :py:meth:`HTMLNode.to_html`.
304
+ :type kwargs: Any
305
+ :return: See :py:meth:`HTMLNode.to_html`.
306
+ :type return: str or List[str]
307
+ """
308
+ return super().to_html(indent_level=indent_level - 1, **kwargs)
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
- from .html_node import HTMLNode, one_line, RawText
13
+ from .html_node import HTMLNode, no_end_tag, one_line, RawText
14
14
  from typing import Dict
15
15
 
16
16
 
@@ -38,3 +38,40 @@ class TextNode(HTMLNode):
38
38
  super().__init__(children=[
39
39
  RawText(text)
40
40
  ], attributes=attributes, style=style)
41
+
42
+
43
+ class Body(HTMLNode):
44
+ """The `<body>` element containing the visible content of a document."""
45
+ pass
46
+
47
+
48
+ @one_line
49
+ @no_end_tag
50
+ class Doctype(HTMLNode):
51
+ """The `<!DOCTYPE html>` doctype declaration of a document."""
52
+
53
+ def __init__(self):
54
+ """Creates a `<!DOCTYPE html>` doctype declaration element."""
55
+ super().__init__()
56
+
57
+ @property
58
+ def start_tag(self) -> str:
59
+ """Overrides the start tag for this node."""
60
+ return "<!DOCTYPE html>"
61
+
62
+
63
+ class Head(HTMLNode):
64
+ """The `<head>` element containing metadata about a document."""
65
+ pass
66
+
67
+
68
+ class Html(HTMLNode):
69
+ """The `<html>` element of an HTML document."""
70
+ pass
71
+
72
+
73
+ @one_line
74
+ @no_end_tag
75
+ class Link(HTMLNode):
76
+ """A `<link>` element for linking to external resources."""
77
+ pass
@@ -10,5 +10,7 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
+ from .indentation import *
14
+ from .representation import *
13
15
  from .sanitizing import *
14
16
  from .validation import *
@@ -0,0 +1,25 @@
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
+ def get_indentation(level: int, size: int = 4) -> str:
14
+ """Returns an indentation string for the given level.
15
+
16
+ :param level: The level of indentation. If negative, this
17
+ function will return an empty string representing no indentation.
18
+ :type level: int
19
+ :param size: The number of spaces to use for each indentation level.
20
+ Defaults to 4 spaces.
21
+ :type size: int
22
+ :return: A string representing the indentation.
23
+ :rtype: str
24
+ """
25
+ return ' ' * (max(level, 0) * size)
@@ -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,14 @@
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
+ from .compiled_website import CompiledWebsite
14
+ from .website import Website
@@ -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
+ from typing import List
14
+ from webwidgets.utility.representation import ReprMixin
15
+
16
+
17
+ class CompiledWebsite(ReprMixin):
18
+ """A utility class to store compiled HTML and CSS code obtained from a
19
+ :py:class:`Website` object.
20
+ """
21
+
22
+ def __init__(self, html_content: List[str], css_content: str):
23
+ """Stores compiled HTML and CSS content.
24
+
25
+ :param html_content: The compiled HTML code of each page in the
26
+ website.
27
+ :type html_content: List[str]
28
+ :param css_content: The compiled CSS code of the website, shared across
29
+ all pages.
30
+ :type css_content: str
31
+ """
32
+ super().__init__()
33
+ self.html_content = html_content
34
+ self.css_content = css_content
@@ -0,0 +1,88 @@
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
+ from .compiled_website import CompiledWebsite
14
+ from typing import Any, Callable, List
15
+ from webwidgets.compilation.css import compile_css, CSSRule, apply_css
16
+ from webwidgets.utility.representation import ReprMixin
17
+ from webwidgets.widgets.containers.page import Page
18
+
19
+
20
+ class Website(ReprMixin):
21
+ """A collection of :py:class:`Page` objects that make up the structure of a
22
+ web site."""
23
+
24
+ def __init__(self, pages: List[Page] = None):
25
+ """Creates a new website with an optional list of pages.
26
+
27
+ :param pages: The pages of the website. Defaults to an empty list.
28
+ :type pages: List[Page]
29
+ """
30
+ super().__init__()
31
+ self.pages = [] if pages is None else pages
32
+
33
+ def add(self, page: Page):
34
+ """Adds a new page to the website.
35
+
36
+ :param page: The page to be added.
37
+ :type page: Page
38
+ """
39
+ self.pages.append(page)
40
+
41
+ def compile(self,
42
+ collapse_empty: bool = True,
43
+ css_file_name: str = "styles.css",
44
+ force_one_line: bool = False,
45
+ indent_level: int = 0,
46
+ indent_size: int = 4,
47
+ rule_namer: Callable[[List[CSSRule], int], str] = None,
48
+ **kwargs: Any) -> CompiledWebsite:
49
+ """Compiles the website into HTML and CSS code.
50
+
51
+ :param collapse_empty: See :py:meth:`HTMLNode.to_html`.
52
+ :type collapse_empty: bool
53
+ :param css_file_name: See :py:meth:`Page.build`.
54
+ :type css_file_name: str
55
+ :param force_one_line: See :py:meth:`HTMLNode.to_html`.
56
+ :type force_one_line: bool
57
+ :param indent_level: See :py:meth:`HTMLNode.to_html`.
58
+ :type indent_level: int
59
+ :param indent_size: See :py:meth:`HTMLNode.to_html` and
60
+ :py:meth:`CompiledCSS.to_css`.
61
+ :type indent_size: int
62
+ :param rule_namer: See :py:func:`compile_css`.
63
+ :type rule_namer: Callable[[List[CSSRule], int], str]
64
+ :param kwargs: See :py:meth:`HTMLNode.to_html`.
65
+ :type kwargs: Any
66
+ :return: A new :py:class:`CompiledWebsite` object containing the
67
+ compiled HTML and CSS code.
68
+ :rtype: CompiledWebsite
69
+ """
70
+ # Building the HTML representation of each page
71
+ trees = [page.build(css_file_name=css_file_name)
72
+ for page in self.pages]
73
+
74
+ # Compiling HTML and CSS code
75
+ compiled_css = compile_css(trees, rule_namer)
76
+ for tree in trees:
77
+ apply_css(compiled_css, tree)
78
+ html_content = [tree.to_html(
79
+ collapse_empty=collapse_empty,
80
+ force_one_line=force_one_line,
81
+ indent_level=indent_level,
82
+ indent_size=indent_size,
83
+ **kwargs
84
+ ) for tree in trees]
85
+ css_content = compiled_css.to_css(indent_size=indent_size)
86
+
87
+ # Storing the result in a new CompiledWebsite object
88
+ return CompiledWebsite(html_content, css_content)
@@ -0,0 +1,14 @@
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
+ from .containers import *
14
+ from .widget import Widget
@@ -0,0 +1,14 @@
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
+ from .container import Container
14
+ from .page import Page
@@ -0,0 +1,38 @@
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
+ from typing import List
14
+ from webwidgets.widgets.widget import Widget
15
+
16
+
17
+ class Container(Widget):
18
+ """
19
+ A widget that can contain other widgets.
20
+ """
21
+
22
+ def __init__(self, widgets: List[Widget] = None):
23
+ """Creates a new Container with optional widgets inside.
24
+
25
+ :param widgets: A list of widgets to be contained within the container.
26
+ Defaults to an empty list.
27
+ :type widgets: List[Widget]
28
+ """
29
+ super().__init__()
30
+ self.widgets = [] if widgets is None else widgets
31
+
32
+ def add(self, widget: Widget) -> None:
33
+ """Adds a widget to the container.
34
+
35
+ :param widget: The widget to add to the container.
36
+ :type widget: Widget
37
+ """
38
+ self.widgets.append(widget)
@@ -0,0 +1,59 @@
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
+ from .container import Container
14
+ from webwidgets.compilation.html.html_node import RootNode
15
+ from webwidgets.compilation.html.html_tags import Body, Doctype, Head, Html, \
16
+ Link
17
+
18
+
19
+ class Page(Container):
20
+ """A widget representing a web page. It contains other widgets and is
21
+ responsible for laying them out within the page.
22
+ """
23
+
24
+ def build(self, css_file_name: str = "styles.css") -> RootNode:
25
+ """Builds the HTML representation of the page.
26
+
27
+ This method constructs an HTML structure that includes a doctype
28
+ declaration, a head section with meta tags, and a body section
29
+ containing the widgets. The widgets are rendered recurisvely by calling
30
+ their :py:meth:`build` method.
31
+
32
+ :param css_file_name: The name of the CSS file to link to the page if
33
+ the page elements contain any styles. Defaults to "styles.css".
34
+ :type css_file_name: str
35
+ :return: An :py:class:`RootNode` object representing the page.
36
+ :rtype: RootNode
37
+ """
38
+ # Building nodes from the page's widgets
39
+ nodes = [w.build() for w in self.widgets]
40
+
41
+ # Initializing the head section of the page
42
+ head = Head()
43
+
44
+ # Checking if there is any style sheet to link to the page.
45
+ # To do so, we just check if any child node has a non-empty style.
46
+ if any(style for n in nodes for style in n.get_styles().values()):
47
+ head.add(Link(
48
+ attributes={"href": css_file_name, "rel": "stylesheet"}
49
+ ))
50
+
51
+ # Building the HTML representation of the page
52
+ return RootNode(
53
+ children=[
54
+ Doctype(),
55
+ Html(
56
+ children=[head, Body(children=nodes)]
57
+ )
58
+ ]
59
+ )
@@ -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
+ from abc import ABC, abstractmethod
14
+ from webwidgets.compilation.html.html_node import HTMLNode
15
+ from webwidgets.utility.representation import ReprMixin
16
+
17
+
18
+ class Widget(ABC, ReprMixin):
19
+ """
20
+ Abstract base class for all widgets.
21
+
22
+ All subclasses of :py:class:`Widget` must implement a :py:meth:`build`
23
+ method that returns an :py:class:`HTMLNode` object.
24
+ """
25
+
26
+ @abstractmethod
27
+ def build(self) -> HTMLNode:
28
+ """Builds the widget and returns the corresponding :py:class:`HTMLNode`
29
+ object.
30
+
31
+ This method must be overridden by subclasses to create specific HTML
32
+ elements.
33
+ """
34
+ pass
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: webwidgets
3
+ Version: 1.0.0
4
+ Summary: A Python package for designing web UIs.
5
+ Project-URL: Source code, https://github.com/mlaasri/WebWidgets
6
+ Author: mlaasri
7
+ License-File: LICENSE
8
+ Keywords: design,webui
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # WebWidgets
15
+
16
+ ![CI Status](https://img.shields.io/github/actions/workflow/status/mlaasri/WebWidgets/ci-full.yml?branch=main)
17
+
18
+ A Python package for creating web UIs
19
+
20
+ ## Installation
21
+
22
+ You can install **WebWidgets** with `pip`. To install the latest stable version, run:
23
+
24
+ ```bash
25
+ pip install webwidgets
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ **WebWidgets** allows you to create custom widgets and build websites with them. For example:
31
+
32
+ ```python
33
+ import webwidgets as ww
34
+ from webwidgets.compilation.html import HTMLNode, RawText
35
+
36
+ # A <div> element
37
+ class Div(HTMLNode):
38
+ pass
39
+
40
+ # A simple text widget
41
+ class Text(ww.Widget):
42
+ def build(self):
43
+ return Div([RawText("Hello, World!")])
44
+
45
+ # A website with one page containing a Text widget
46
+ page = ww.Page([Text()])
47
+ website = ww.Website([page])
48
+
49
+ # Compile the website into HTML code
50
+ compiled = website.compile()
51
+ print(compiled.html_content[0])
52
+ ```
53
+
54
+ Prints the following result:
55
+
56
+ ```console
57
+ <!DOCTYPE html>
58
+ <html>
59
+ <head></head>
60
+ <body>
61
+ <div>
62
+ Hello, World!
63
+ </div>
64
+ </body>
65
+ </html>
66
+ ```
@@ -0,0 +1,24 @@
1
+ webwidgets/__init__.py,sha256=IZAAOuEwU_cC_oy0dJ2WSw9wXnwrLyZ4mqszBPma7MA,549
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=93WuxY-ADUDK_cBuGS-hUuV4HctZjtaG3cqeoE4S3oQ,9725
5
+ webwidgets/compilation/html/__init__.py,sha256=iupXt6punHDLAFdygshmQeFVOLCeZ8HbwCWclL1FH54,521
6
+ webwidgets/compilation/html/html_node.py,sha256=lOf1LEoVx23teWHfdlt5IpSv14cE6EI-aDyFGC_BXSU,11697
7
+ webwidgets/compilation/html/html_tags.py,sha256=tFc5P6_rqyetFXJKlxt8n_IhBD758LHPql9peU9__6o,2188
8
+ webwidgets/utility/__init__.py,sha256=Sl-dzpPPTHykkmLSfobhqHmlzUSPtvhaR4xtJy_tiOg,505
9
+ webwidgets/utility/indentation.py,sha256=BaOQRqWdG7T5k_g1-ia9jewPFZjD3afjZH_Fc4NSVwo,906
10
+ webwidgets/utility/representation.py,sha256=lQ15v_DZOHBQKLM8pzRE1tuJkU_modhPTpWpSJ2lBCE,1061
11
+ webwidgets/utility/sanitizing.py,sha256=OKJRDqk-OXYCWeK6ie3GdfQvb49wTs93kd971mg5oK0,5770
12
+ webwidgets/utility/validation.py,sha256=bUjpiGP59GW3DPvQ1hwR5ezBMmcSd6v4xlDLwTHZv_A,4261
13
+ webwidgets/website/__init__.py,sha256=zp4N3CtY0SLNfDV9p2Y0tqbta-vFOX1PSJr7eQ9rQdk,471
14
+ webwidgets/website/compiled_website.py,sha256=lR_sabYtdWiRWicyxEFs4yxRUB_TbMowpsNz3CtqQBQ,1129
15
+ webwidgets/website/website.py,sha256=a5Qmm4DOIYMXHoBDyKzB6Ex2kaPFCSPgodF7LAVyrPE,3339
16
+ webwidgets/widgets/__init__.py,sha256=J2br7F-16URKvWshkJcc4nth27YQsaLrdVZu0xXx5CU,449
17
+ webwidgets/widgets/widget.py,sha256=8ZRcVmmtjQzeA_uGZi10H4XvqgGEtGmr9275FId8zt0,1039
18
+ webwidgets/widgets/containers/__init__.py,sha256=6LPlYaxXiMgC5YPHhi0HLhn7iCeh_5IFY70mv7a-wSA,452
19
+ webwidgets/widgets/containers/container.py,sha256=blpO2y9IiZ_4opwe9pqsSJPAZEN1P_ZRK4a27NpnMrg,1158
20
+ webwidgets/widgets/containers/page.py,sha256=sJ8QDmZ_6jzRFt4lyiAEPwjPvDTKm8EWOjz_Tq_cdjU,2180
21
+ webwidgets-1.0.0.dist-info/METADATA,sha256=DHfGCHdolxBicrkQjAzWGm_B7d_SgY2OnwZ261tSxUk,1434
22
+ webwidgets-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ webwidgets-1.0.0.dist-info/licenses/LICENSE,sha256=LISw1mw5eK6i8adFSlx6zltZxrJFwurngVdZAEU8g_I,1064
24
+ webwidgets-1.0.0.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: webwidgets
3
- Version: 0.2.0
4
- Summary: A Python package for designing web UIs.
5
- Project-URL: Source code, https://github.com/mlaasri/WebWidgets
6
- Author: mlaasri
7
- License-File: LICENSE
8
- Keywords: design,webui
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Programming Language :: Python :: 3
11
- Requires-Python: >=3.9
12
- Description-Content-Type: text/markdown
13
-
14
- # WebWidgets
15
-
16
- ![CI Status](https://img.shields.io/github/actions/workflow/status/mlaasri/WebWidgets/ci-full.yml?branch=main)
17
-
18
- A Python package for creating web UIs
@@ -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,,