Skip to content

Commit

Permalink
SafeString class and docs (#6)
Browse files Browse the repository at this point in the history
* move code

* consistency

* classifiers

* formatting

* docs

* safestring back to class

* docs

* no dataclass

* type

* str
  • Loading branch information
keithasaurus authored Nov 23, 2023
1 parent 0e92bbd commit 9806dc5
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 288 deletions.
7 changes: 0 additions & 7 deletions MANIFEST

This file was deleted.

133 changes: 76 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# simple_html

### Template-less. Type-safe. Minified by default.
### Template-less. Type-safe. Minified by default. Fast.

simple_html is built to simplify HTML rendering in Python. No templates needed. Just create HTML in
normal Python. In most cases, the code will be more concise than standard HTML. Other benefits include:
simple_html allows you to create HTML in standard Python. Benefits include:
- typically faster than jinja2 -- up to 15x faster
- typically renders fewer bytes than template-based rendering
- types mean your editor and tools can help you write correct code faster
- no framework needed
- lightweight
- types let your editor and tools help you write correct code faster
- lightweight and framework agnostic
- always renders valid html


### Installation
Expand All @@ -17,82 +17,101 @@ normal Python. In most cases, the code will be more concise than standard HTML.
### Usage

```python
from simple_html.nodes import body, head, html, p
from simple_html.render import render

node = html(
head,
body(
p.attrs(id="hello")(
"Hello World!"
)
)
)
from simple_html import div, h1, render, p

node = div({},
h1({"id": "hello"},
"Hello World!"),
p({},
"hooray!"))

render(node)
# <div><h1 id="hello">Hello World!</h1><p>hooray!</p></div>
```

There are several ways to render nodes:
```python
from simple_html import br, div, h1, img, render

# raw node
render(br)
# <br/>

# node with attributes only
render(img({"src": "/some/image/url.jpg", "alt": "a great picture"}))
# <img src="/some/image/url.jpg" alt="a great picture"/>

# node with children
render(
node) # returns: <html><head></head><body><p id="hello">Hello World!</p></body></html>
div({},
h1({},
"something"))
)
# <div><h1>something</h1></div>'
```

Tag attributes with `None` as the value will only render the attribute name:
```python
from simple_html import div, render

render(
div({"empty-str-attribute": "",
"key-only-attr": None})
)
# <div empty-str-attribute="" key-only-attr></div>
```

Strings are escaped by default, but you can pass in `SafeString`s to avoid escaping.

```python
from simple_html.nodes import br, p, safe_string
from simple_html.render import render
from simple_html import br, p, SafeString, render

node = p(
"Escaped & stuff",
br,
safe_string("Not escaped & stuff")
)
node = p({},
"Escaped & stuff",
br,
SafeString("Not escaped & stuff"))

render(node) # returns: <p>Escaped &amp; stuff<br/>Not escaped & stuff</p>
```

For convenience, many tags are provided, but you can create your own as well:

Lists and generators are both valid collections of nodes:
```python
from simple_html.nodes import Tag
from simple_html.render import render
from typing import Generator
from simple_html import div, render, Node, br

custom_elem = Tag("custom-elem")

render(
custom_elem.attrs(id="some-custom-elem-id")(
"Wow"
)
) # returns: <custom-elem id="some-custom-elem-id">Wow</custom-elem>
```
def get_list_of_nodes() -> list[Node]:
return ["neat", br]

Likewise, some attributes have been created as type-safe presets. Note that there are multiple ways to create attributes.
The examples below are all equivalent:

```python
from simple_html.attributes import height, id_
from simple_html.nodes import div
render(div({}, get_list_of_nodes()))
# <div>neat<br/></div>


# **kwargs: recommended for most cases
div.attrs(id="some-id", height="100")
def node_generator() -> Generator[Node, None, None]:
yield "neat"
yield br

# *args: useful for attributes that may be reserved keywords or when type constraints are desired.
# Presets, raw tuples, and kwargs can be used interchangeably.
div.attrs(id_("some-id"),
height(100),
("class", "abc"),
width="100")

# renders to: <div id="some-id" height="100" class="abc" width="100"></div>
render(
div({}, node_generator())
)
# <div>neat<br/></div>
```

You can build your own presets, using `str_attr`, `int_attr`, or `bool_attr`. For instance, here are
several of the attribute preset definitions

For convenience, many tags are provided, but you can also create your own:

```python
from simple_html.attributes import bool_attr, int_attr, str_attr
from simple_html import Tag, render

custom_elem = Tag("custom-elem")

# works the same as any other tag
node = custom_elem(
{"id": "some-custom-elem-id"},
"Wow"
)

checked = bool_attr('checked')
class_ = str_attr('class')
cols = int_attr('cols')
render(node) # <custom-elem id="some-custom-elem-id">Wow</custom-elem>
```
But anything that renders to the type of `Attribute` will work.
22 changes: 12 additions & 10 deletions bench/simple.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Tuple

from simple_html.nodes import (
from simple_html import (
h1,
html,
title,
Expand All @@ -10,12 +10,12 @@
p,
ul,
li,
safe_string,
SafeString,
br,
meta,
DOCTYPE_HTML5,
render,
)
from simple_html.render import render


def hello_world_empty(objs: List[None]) -> None:
Expand All @@ -39,7 +39,7 @@ def basic(objs: List[Tuple[str, str, List[str]]]) -> None:
ul(
{},
[
li({"class": "item-stuff"}, safe_string(ss))
li({"class": "item-stuff"}, SafeString(ss))
for ss in oks
],
),
Expand All @@ -55,7 +55,9 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
"<!doctype html>",
html(
{},
head({}, title({}, title_)),
head({},
title({},
title_)),
body(
{},
h1({"class": "great header", "other_attr": "5", "id": "header1"}),
Expand All @@ -65,7 +67,7 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
ul(
{},
[
li({"class": "item-stuff"}, safe_string(ss))
li({"class": "item-stuff"}, SafeString(ss))
for ss in oks
],
),
Expand All @@ -77,7 +79,7 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
ul(
{},
[
li({"class": "item-stuff"}, safe_string(ss))
li({"class": "item-stuff"}, SafeString(ss))
for ss in oks
],
),
Expand All @@ -89,7 +91,7 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
ul(
{},
[
li({"class": "item-stuff"}, safe_string(ss))
li({"class": "item-stuff"}, SafeString(ss))
for ss in oks
],
),
Expand All @@ -101,7 +103,7 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
ul(
{},
[
li({"class": "item-stuff"}, safe_string(ss))
li({"class": "item-stuff"}, SafeString(ss))
for ss in oks
],
),
Expand All @@ -113,7 +115,7 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
ul(
{},
[
li({"class": "item-stuff"}, safe_string(ss))
li({"class": "item-stuff"}, SafeString(ss))
for ss in oks
],
),
Expand Down
18 changes: 16 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
[tool.poetry]
name = "simple-html"
version = "0.7.0"
version = "1.0.0"
readme = "README.md"
description = "Template-less html rendering in Python"
authors = ["Keith Philpott <[email protected]>"]
license = "MIT"
homepage = "https://github.com/keithasaurus/simple_html"
keywords = ["html"]
keywords = ["html", "type hints"]
classifiers = [
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Environment :: MacOS X',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'Operating System :: MacOS',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX :: Linux',
'Operating System :: Unix',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities',
'Typing :: Typed'
]


[tool.poetry.dependencies]
Expand Down
Loading

0 comments on commit 9806dc5

Please sign in to comment.