Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d815b10
Create Options class
MaddyGuthridge Feb 5, 2025
4b495d5
Require Python 3.10 to support kw_only dataclasses
MaddyGuthridge Feb 5, 2025
efa006b
Poetry lock
MaddyGuthridge Feb 5, 2025
d990063
Use render options during rendering
MaddyGuthridge Feb 12, 2025
2258340
Regenerate tags and remove portal tag
MaddyGuthridge Feb 12, 2025
0356fb3
Fix failing tests
MaddyGuthridge Feb 12, 2025
f524829
Fix linting
MaddyGuthridge Feb 12, 2025
b57d798
Actually fix linting
MaddyGuthridge Feb 12, 2025
8f2761c
Implement spacing rules
MaddyGuthridge Feb 12, 2025
9b645ce
Add _get_default_render_options method to Tag class
MaddyGuthridge Feb 12, 2025
606308e
Rename Options to RenderOptions
MaddyGuthridge Feb 12, 2025
522fff9
Implement default render options in generator code
MaddyGuthridge Feb 12, 2025
9e17beb
Fix failing test cases
MaddyGuthridge Feb 12, 2025
c32e13a
Add test case for paragraph default rendering options
MaddyGuthridge Feb 12, 2025
3682a92
Document rendering options
MaddyGuthridge Feb 12, 2025
6deff10
Docs site: fix spacing around nested nav items
MaddyGuthridge Feb 12, 2025
5ee0d47
Improve documentation for render options
MaddyGuthridge Feb 12, 2025
294386d
Add test case for render options type safety
MaddyGuthridge Feb 12, 2025
1a6dadc
Export `fencedframe` element
MaddyGuthridge Feb 12, 2025
1c40adc
Fix code coverage
MaddyGuthridge Feb 12, 2025
cad6f1e
Fix linting
MaddyGuthridge Feb 12, 2025
924c3f6
Update module docstring
MaddyGuthridge Feb 12, 2025
7004c26
Bump version to 2.2.0
MaddyGuthridge Feb 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/mkdocs-dryrun.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
id: python
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.10'

# Install dependencies using Poetry
- uses: Gr1N/setup-poetry@v9
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
id: python
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.10'

# Install dependencies using Poetry
- uses: Gr1N/setup-poetry@v9
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.10'

# Install dependencies using Poetry
- uses: Gr1N/setup-poetry@v9
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python 3.9
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.10'
- uses: Gr1N/setup-poetry@v9
- uses: actions/cache@v4
with:
Expand Down Expand Up @@ -60,10 +60,10 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python 3.9
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.10'
- uses: Gr1N/setup-poetry@v9
- uses: actions/cache@v4
with:
Expand All @@ -81,10 +81,10 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up Python 3.9
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: '3.10'
- uses: Gr1N/setup-poetry@v9
- uses: actions/cache@v4
with:
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ Learn more by reading [the documentation](https://comp1010unsw.github.io/pyhtml-
<h1>
Hello, world!
</h1>
<p>
This is my amazing website!
</p>
<p>This is my amazing website!</p>
</body>
</html>

Expand Down
10 changes: 9 additions & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
- attributes
- "_get_tag_name"
- "_get_default_attributes"
- "_get_default_render_options"
- "_get_tag_pre_content"
- "_render"
- "_escape_children"
- "_render"

::: pyhtml.SelfClosingTag
options:
Expand All @@ -20,3 +21,10 @@
::: pyhtml.WhitespaceSensitiveTag
options:
members:

::: pyhtml.RenderOptions
options:
members:
- indent
- spacing
- union
29 changes: 21 additions & 8 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,29 @@ modify the original instance, as I found the old behaviour confusing and
bug-prone.

```py
>>> para = p.p("Base paragraph")
>>> para2 = para("Extra text")
>>> para2
<p>
>>> span1 = p.span("Base paragraph")
>>> span2 = span1("Extra text")
>>> span2
<span>
Base paragraph
Extra text
</p>
>>> para
<p>
</span>
>>> span1
<span>
Base paragraph
</p>
</span>

```

## Spacing between tag instances

Certain tags have default spacing, which helps to avoid subtle rendering
annoyances such as unwanted spaces between linked text and punctuation in
paragraphs.

```py
>>> print(str(p.p(p.a(href="https://example.com")("Example website"), "!")))
<p><a href="https://example.com">Example website</a>!</p>
>>> # Notice how there is no spacing between the link and the exclamation point

```
4 changes: 1 addition & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ Build HTML documents in Python with a simple and learnable syntax.
<h1>
Hello, world!
</h1>
<p>
This is my amazing website!
</p>
<p>This is my amazing website!</p>
</body>
</html>

Expand Down
32 changes: 27 additions & 5 deletions docs/learn/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,29 @@ for person in staff_members:
))
```

### HTML comments
## Rendering options

If you need more control over how PyHTML renders your output HTML, you can use
the [`p.RenderOptions`][pyhtml.RenderOptions] class to specify these.

For example, if you don't want any whitespace between your HTML components,
you can use `p.RenderOptions(spacing="")` to remove it.

```py
>>> import pyhtml as p
>>> print(str(p.div(p.RenderOptions(spacing=""))(
... p.i("No"),
... p.b("spaces"),
... p.u("here!")
... )))
<div><i>No</i><b>spaces</b><u>here!</u></div>

```

For more information about the specific allowed properties, view
[this documentation][pyhtml.RenderOptions].

## HTML comments

You can add comments to HTML by using the `Comment` tag. These will be included
in the output HTML, which can be useful for debugging your server from your web
Expand All @@ -77,8 +99,8 @@ browser.
## Embedding raw HTML

By default, PyHTML [escapes certain characters](https://www.w3schools.com/html/html_entities.asp)
within strings passed to tags. This is done to avoid user content from taking
control of your resultant webpages in what is called a
within strings passed to tags. This is done to prevent user content from taking
control of your webpages in what is called a
[cross-site scripting (XSS) attack](https://owasp.org/www-community/attacks/xss/).

Sometimes, you may wish to embed an existing HTML string inside of PyHTML. You
Expand All @@ -94,13 +116,13 @@ can do this using the `p.DangerousRawHtml` tag.

***Be careful though!*** PyHTML escapes these sequences for good reason, so
don't use `DangerousRawHtml` unless you have a *very good reason*, and are
certain that your text is trusted.
certain that the text you are passing it is trusted.

## Custom tags

Since this library includes all modern HTML tags, it is very unlikely that
you'll need to do create a custom tag. However if you really need to, you can
use the `create_tag` function
use the `create_tag` function.

```py
>>> import pyhtml as p
Expand Down
5 changes: 5 additions & 0 deletions docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
--md-primary-fg-color: #ffdf77 !important;
}

/* Fix spacing around nested nav items */
.md-nav__item--nested {
padding-bottom: 8px !important;
}

.md-header {
/* Text colour on header */
color: #000000 !important;
Expand Down
19 changes: 17 additions & 2 deletions meta/generate_tag_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ def _get_tag_pre_content(self) -> Optional[str]:
return {}
"""

GET_DEFAULT_RENDER_OPTIONS = """
def _get_default_render_options(self) -> RenderOptions:
return {}
"""


def get_template_class(name: str):
try:
Expand Down Expand Up @@ -70,8 +75,10 @@ def generate_tag_class(output: TextIO, tag: TagInfo):

attr_args = "\n".join(attr_args_gen).strip()
attr_unions = "\n".join(attr_unions_gen).strip()
attr_docs_outer = "\n".join(increase_indent(attr_docs_gen, 4)).strip()
attr_docs_inner = "\n".join(increase_indent(attr_docs_gen, 8)).strip()
attr_docs_outer = "\n".join(increase_indent(attr_docs_gen, " ")).strip()
attr_docs_inner = "\n".join(
increase_indent(attr_docs_gen, " ")
).strip()

# Determine whether the class should mandate keyword-only args
# If there are no named attributes, we set it to '' to avoid a syntax error
Expand Down Expand Up @@ -106,6 +113,13 @@ def generate_tag_class(output: TextIO, tag: TagInfo):
file=output,
)

# Add render options function if needed
if tag.render_options is not None:
print(
GET_DEFAULT_RENDER_OPTIONS.replace("{}", f"{tag.render_options}"),
file=output,
)

# And a nice trailing newline to make flake8 happy
print(file=output)

Expand All @@ -118,6 +132,7 @@ def main(output: TextIO):

with open(TEMPLATES_FOLDER.joinpath("main.py")) as f:
print(f.read(), file=output)
print(file=output)

for tag in tags:
# Generate the tag
Expand Down
37 changes: 37 additions & 0 deletions meta/scrape_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import yaml
from typing_extensions import NotRequired

from pyhtml.__render_options import RenderOptions

TAGS_YAML = "meta/tags.yml"
"""File location to load custom tag data from"""

Expand Down Expand Up @@ -118,6 +120,15 @@ class AttrYmlItem(TypedDict):
"""Python type to accept for the attribute"""


class RenderOptionsYmlItem(TypedDict):
"""
Render options as a dictionary
"""

indent: NotRequired[str]
spacing: NotRequired[str]


class TagsYmlItem(TypedDict):
"""
A tag which has suggested keys
Expand All @@ -143,6 +154,11 @@ class TagsYmlItem(TypedDict):
Pre-content for the element (eg `<!DOCTYPE html>`)
"""

render_options: NotRequired[RenderOptionsYmlItem]
"""
Render options for this element
"""


TagsYaml = dict[str, TagsYmlItem]
"""Type alias for type of tags.yml file"""
Expand Down Expand Up @@ -216,6 +232,11 @@ class TagInfo:
Pre-content for the element (eg `<!DOCTYPE html>`)
"""

render_options: Optional[RenderOptions]
"""
Render options
"""


def fetch_mdn():
"""
Expand Down Expand Up @@ -468,6 +489,21 @@ def get_tag_pre_content(tags: TagsYaml, tag_name: str) -> Optional[str]:
return tag.get("pre_content", None)


def get_tag_render_options(
tags: TagsYaml, tag_name: str
) -> Optional[RenderOptions]:
"""
Return pre-content for the tag
"""
if tag_name not in tags:
return None
tag = tags[tag_name]
if "render_options" in tag:
return RenderOptions(**tag["render_options"])
else:
return None


def make_mdn_link(tag: str) -> str:
"""Generate an MDN docs link for the given tag"""
return f"{MDN_ELEMENT_PAGE}/{tag}"
Expand Down Expand Up @@ -496,6 +532,7 @@ def elements_to_element_structs(
escape_children=get_tag_escape_children(tag_attrs, name),
attributes=attr_entries_to_object(tag_attrs, name),
pre_content=get_tag_pre_content(tag_attrs, name),
render_options=get_tag_render_options(tag_attrs, name),
)
)

Expand Down
2 changes: 2 additions & 0 deletions meta/tags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ label:

p:
base: StylableTag
render_options:
spacing: ""

br:
base: SelfClosingTag
Expand Down
8 changes: 4 additions & 4 deletions meta/templates/class_attrs_SelfClosingTag.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class {name}({base}):
"""
def __init__(
self,
{kw_only}
*options: RenderOptions,
{attr_args}
**attributes: AttributeType,
) -> None:
Expand All @@ -22,11 +22,11 @@ def __init__(
attributes |= {
{attr_unions}
}
super().__init__(**attributes)
super().__init__(*options, **attributes)

def __call__( # type: ignore
self,
{kw_only}
*options: RenderOptions,
{attr_args}
**attributes: AttributeType,
):
Expand All @@ -40,7 +40,7 @@ def __call__( # type: ignore
attributes |= {
{attr_unions}
}
return super().__call__(**attributes)
return super().__call__(*options, **attributes)

def _get_default_attributes(self, given: dict[str, AttributeType]) -> dict[str, AttributeType]:
return {default_attrs}
6 changes: 4 additions & 2 deletions meta/templates/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

https://creativecommons.org/licenses/by-sa/2.5/
"""
from typing import Any, Optional, Union, Literal
from ..__tag_base import Tag, SelfClosingTag, WhitespaceSensitiveTag
from typing import Literal, Optional, Union

from ..__render_options import RenderOptions
from ..__tag_base import SelfClosingTag, Tag, WhitespaceSensitiveTag
from ..__types import AttributeType, ChildrenType
Loading