diff --git a/meta/generate_tag_defs.py b/meta/generate_tag_defs.py index c45e81d..19c6182 100644 --- a/meta/generate_tag_defs.py +++ b/meta/generate_tag_defs.py @@ -30,6 +30,12 @@ def generate_tag_class(output: TextIO, tag: TagInfo): """ text = get_template_class(tag.base) + # Generate default attributes dictionary + default_attrs = repr({ + attr.name: attr.default + for attr in tag.attributes + }) + # Generate attribute arguments, unions and documentation # To get a better idea of these, look inside the template files to see # what would be replaced @@ -41,10 +47,15 @@ def generate_tag_class(output: TextIO, tag: TagInfo): # Yucky hard-coded spaces, I can't be bothered to fix this # Also making everything optional for the sake of users always # being able to remove an attribute - f" {attr.name}: Optional[{attr.type}] = {attr.default!r}," + f" {attr.name}: Optional[{attr.type}] = None," ) attr_unions_gen.append(f" '{attr.name}': {attr.name},") - attr_docs_gen.append(f"* {attr.name}: {attr.doc}") + # Also mention default value if applicable + if attr.default is not None: + attr_docs_gen.append( + f"* {attr.name}: {attr.doc} (defaults to {attr.default})") + else: + attr_docs_gen.append(f"* {attr.name}: {attr.doc}") attr_args = '\n'.join(attr_args_gen).strip() attr_unions = '\n'.join(attr_unions_gen).strip() @@ -70,7 +81,8 @@ def generate_tag_class(output: TextIO, tag: TagInfo): .replace("{attr_unions}", attr_unions)\ .replace("{attr_docs_outer}", attr_docs_outer)\ .replace("{attr_docs_inner}", attr_docs_inner)\ - .replace("{kw_only}", kw_only) + .replace("{kw_only}", kw_only)\ + .replace("{default_attrs}", default_attrs) print(text, file=output) # And a nice trailing newline to make flake8 happy diff --git a/meta/templates/class_attrs_SelfClosingTag.py b/meta/templates/class_attrs_SelfClosingTag.py index 4eb656c..c369b93 100644 --- a/meta/templates/class_attrs_SelfClosingTag.py +++ b/meta/templates/class_attrs_SelfClosingTag.py @@ -41,3 +41,6 @@ def __call__( {attr_unions} } return super().__call__(**attributes) + + def _get_default_attributes(self) -> dict[str, Any]: + return {default_attrs} diff --git a/meta/templates/class_attrs_StylableTag.py b/meta/templates/class_attrs_StylableTag.py index c41d44c..f6c8c77 100644 --- a/meta/templates/class_attrs_StylableTag.py +++ b/meta/templates/class_attrs_StylableTag.py @@ -53,3 +53,6 @@ def __call__( {attr_unions} } return super().__call__(*children, **attributes) + + def _get_default_attributes(self) -> dict[str, Any]: + return {default_attrs} diff --git a/meta/templates/class_attrs_Tag.py b/meta/templates/class_attrs_Tag.py index 3e057de..9353c32 100644 --- a/meta/templates/class_attrs_Tag.py +++ b/meta/templates/class_attrs_Tag.py @@ -41,3 +41,6 @@ def __call__( {attr_unions} } return super().__call__(*children, **attributes) + + def _get_default_attributes(self) -> dict[str, Any]: + return {default_attrs} diff --git a/pyhtml/__tag_base.py b/pyhtml/__tag_base.py index 93e1a44..eb8c4ac 100644 --- a/pyhtml/__tag_base.py +++ b/pyhtml/__tag_base.py @@ -45,15 +45,29 @@ def _get_tag_name(self) -> str: """ return type(self).__name__.removesuffix('_') + def _get_default_attributes(self) -> dict[str, Any]: + """ + Returns the default attributes for the tag + + This is overridden by child classes to return a dictionary of default + attributes that are applied to the class. + """ + return {} + def _render(self) -> list[str]: """ Renders tag and its children to a list of strings where each string is a single line of output """ + attributes = util.filter_attributes(util.dict_union( + self._get_default_attributes(), + self.attributes, + )) + # Tag and attributes opening = f"<{self._get_tag_name()}" - if len(self.attributes): - opening += f" {util.render_tag_attributes(self.attributes)}>" + if len(attributes): + opening += f" {util.render_tag_attributes(attributes)}>" else: opening += ">" diff --git a/pyhtml/__tags/generated.py b/pyhtml/__tags/generated.py index f1fbfba..a6cee83 100644 --- a/pyhtml/__tags/generated.py +++ b/pyhtml/__tags/generated.py @@ -16,46 +16,49 @@ class html(Tag): """ Represents the root (top-level element) of an HTML document, so it is also referred to as the _root element_. All other elements must be descendants of this element. - + [View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html) """ def __init__( self, *children: Any, - + **attributes: Any, ) -> None: """ Represents the root (top-level element) of an HTML document, so it is also referred to as the _root element_. All other elements must be descendants of this element. - + [View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html) """ attributes |= { - + } super().__init__(*children, **attributes) def __call__( self, *children: Any, - + **attributes: Any, ): """ Represents the root (top-level element) of an HTML document, so it is also referred to as the _root element_. All other elements must be descendants of this element. - + [View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html) """ attributes |= { - + } return super().__call__(*children, **attributes) + def _get_default_attributes(self) -> dict[str, Any]: + return {} + class base(SelfClosingTag): """ @@ -108,51 +111,57 @@ def __call__( } return super().__call__(**attributes) + def _get_default_attributes(self) -> dict[str, Any]: + return {'href': None, 'target': None} + class head(Tag): """ Contains machine-readable information (metadata) about the document, like its [title](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title), [scripts](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script), and [style sheets](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style). - + [View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) """ def __init__( self, *children: Any, - + **attributes: Any, ) -> None: """ Contains machine-readable information (metadata) about the document, like its [title](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title), [scripts](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script), and [style sheets](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style). - + [View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) """ attributes |= { - + } super().__init__(*children, **attributes) def __call__( self, *children: Any, - + **attributes: Any, ): """ Contains machine-readable information (metadata) about the document, like its [title](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title), [scripts](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script), and [style sheets](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style). - + [View full documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) """ attributes |= { - + } return super().__call__(*children, **attributes) + def _get_default_attributes(self) -> dict[str, Any]: + return {} + class link(SelfClosingTag): """ @@ -205,70 +214,76 @@ def __call__( } return super().__call__(**attributes) + def _get_default_attributes(self) -> dict[str, Any]: + return {'href': None, 'rel': None} + class meta(Tag): """ Represents [metadata](https://developer.mozilla.org/en-US/docs/Glossary/Metadata) that cannot be represented by other HTML meta-related elements, like [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base), [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link), [`', + ]) + + def test_tags_with_trailing_undercore_render_without(): """ Some tags have a trailing underscore to avoid name collisions. When @@ -154,8 +165,8 @@ def test_larger_page(): ' ', ' Hello, world!', ' ', - ' ', + ' ', ' ', ' ', '

',