summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsko Nõmm <asko@nmm.ee>2024-12-31 00:50:26 +0200
committerAsko Nõmm <asko@nmm.ee>2024-12-31 00:50:26 +0200
commitec34e42b87ed39ccb5d4f5b278be0ac73a77a55e (patch)
tree993dc1e907c6f99d4d1c37a5ed8581585ef028c6
parent77f6f957480d3674756ab6e5166641a3bab3e696 (diff)
bump
-rw-r--r--README.md391
-rw-r--r--htmtl/__init__.py2
-rw-r--r--htmtl/attribute_parsers/__init__.py3
-rw-r--r--htmtl/expression_modifiers/__init__.py1
-rw-r--r--htmtl/htmtl.py3
5 files changed, 397 insertions, 3 deletions
diff --git a/README.md b/README.md
index 0d94bda..f6c31b7 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,393 @@
HTMTL (HyperText Markup _Templating_ Language) is a templating language that uses HTML attributes for its rendering logic.
It is both a subset and superset of HTML, meaning that valid HTML is also valid HTMTL and valid HTMTL is also valid HTML, allowing
-you to use any editor without needing any additional plugins. \ No newline at end of file
+you to use any editor without needing any additional editor extensions.
+
+## Features
+
+- **Interpolation**: You can interpolate data from a data dictionary into your templates.
+- **Modifiers**: You can modify the interpolated values using modifiers.
+- **Conditionals**: You can show or hide blocks using expressions.
+- **Partials**: You can include other templates inside your templates.
+- **Loops**: You can loop over iterable data.
+- **Extendable**: You can implement custom attribute parsers and expression modifiers.
+-
+## Example syntax
+
+```html
+<!DOCTYPE html>
+<html>
+<head>
+ <title inner-text="{title}"></title>
+</head>
+<body>
+ <h1 inner-text="{title}"></h1>
+
+ <div class="posts" if="posts">
+ <div foreach="posts as post">
+ <h2 class="post-title">
+ <a :href="/blog/{post.url}" inner-text="{post.title | capitalize}"></a>
+ </h2>
+ <div class="post-date" inner-text="{post.date | date:yyyy-MM-dd}"></div>
+ <div class="post-content" inner-html="{post.body}"></div>
+ </div>
+ </div>
+</body>
+</html>
+```
+
+## Installation
+
+```
+pip install htmtl
+```
+
+## Usage
+
+A simple example of how to use HTMTL with default configuration looks like this:
+
+```python
+from htmtl import Htmtl
+
+htmtl = Htmtl('<p inner-text="Hello {who}"></p>', {'who': 'World'})
+html = htmtl.html() # returns: <p>Hello World</p>
+```
+
+## Attributes
+
+HTMTL works by parsing attributes in the template.
+
+### `inner-text`
+
+Sets the inner text of the element to the value of the attribute.
+
+HTMTL template where `title` key is `Hello, World!`:
+
+```html
+<h1 inner-text="{title}"></h1>
+```
+
+Results in:
+
+```html
+<h1>Hello, World!</h1>
+```
+
+### `inner-html`
+
+Sets the inner HTML of the element to the value of the attribute.
+
+HTMTL template where `content` key is `<p>Hello, World!</p>`:
+
+```html
+<div inner-html="{content}"></div>
+```
+
+Results in:
+
+```html
+<div>
+ <p>Hello, World!</p>
+</div>
+```
+
+### `inner-partial`
+
+Sets the inner HTML of the element to the value of the parsed HTMTL template. Inherits all the same data
+as the parent template.
+
+HTMTL template with data such as:
+
+```python
+data = {
+ 'title': 'My Web Portal Thing',
+ 'header': '<div class="header"><h1 inner-text="title"></h1></div>'
+}
+```
+
+And where the template is:
+
+```html
+<div inner-partial="header"></div>
+```
+
+Results in:
+
+```html
+<div>
+ <div class="header">
+ <h1>My Web Portal Thing</h1>
+ </div>
+</div>
+```
+
+### `outer-text`
+
+Sets the outer text of the element to the value of the attribute.
+
+HTMTL template where `title` key is `Hello, World!`:
+
+```html
+<h1 outer-text="{title}"></h1>
+```
+
+Results in:
+
+```html
+Hello, World!
+```
+
+### `outer-html`
+
+Sets the outer HTML of the element to the value of the attribute.
+
+HTMTL template where `content` key is `<p>Hello, World!</p>`:
+
+```html
+<div outer-html="{content}"></div>
+```
+
+Results in:
+
+```html
+<p>Hello, World!</p>
+```
+
+### `outer-partial`
+
+Sets the outer HTML of the element to the value of the parsed Toretto template. Inherits all the same data
+as the parent template.
+
+HTMTL template with data such as:
+
+```python
+data = {
+ 'title': 'My Web Portal Thing',
+ 'header': '<div class="header"><h1 inner-text="title"></h1></div>'
+}
+```
+
+And where the template is:
+
+```html
+<div outer-partial="header"></div>
+```
+
+Results in:
+
+```html
+<div class="header">
+ <h1>My Web Portal Thing</h1>
+</div>
+```
+
+### `if`
+
+Removes the element if the attribute is false-y.
+
+HTMTL template where `show` key is `False`:
+
+```html
+<div if="show">Hello, World!</div>
+```
+
+Results in:
+
+```html
+<!-- Empty -->
+```
+
+### `unless`
+
+Removes the element if the attribute is truthy.
+
+HTMTL template where `hide` key is `True`:
+
+```html
+<div unless="hide">Hello, World!</div>
+```
+
+Results in:
+
+```html
+<!-- Empty -->
+```
+
+### `foreach`
+
+Loops anything iterable.
+
+For example, to loop over a collection of `posts` and then use `post` as the variable of each iteration, you can do something
+like this:
+
+```php
+<div foreach="posts as post">
+ <h2 inner-text="post.title"></h2>
+</div>
+```
+
+If you do not care about using any of the iteration data, you can also entirely omit `as ...` from the expression,
+like so:
+
+```php
+<div foreach="posts">
+ ...
+</div>
+```
+
+And, you can also assign the key of the iteration to a variable, like so:
+
+```php
+<div foreach="posts as index:post">
+ <h2 :class="post-{post.index}" inner-text="post.title"></h2>
+</div>
+```
+
+This would add the key of the iteration to as `post.index` variable, but you can name it whatever you want.
+
+### `:*` (Generic Value Attributes)
+
+You can use the `:*` attribute to set any attribute on an element to the interpolated value of the generic value attribute.
+
+For example, to set the `href` attribute of an element, you can use the `:href` attribute:
+
+```html
+<a :href="/blog/{slug}">Hello, World!</a>
+```
+
+Results in:
+
+```html
+<a href="/blog/hello-world">Hello, World!</a>
+```
+
+If the `slug` key is `hello-world`.
+
+## Modifiers
+
+All interpolated values in expressions can be modified using modifiers. Modifiers are applied to the value of the attribute, and they can be chained, like so:
+
+```html
+<h1 inner-text="{title | uppercase | reverse}"></h1>
+```
+
+Note that if you have nothing other than the interpolated variable in the attribute, then you can omit the curly brackets, and so
+this would also work:
+
+```html
+<h1 inner-text="title | uppercase | reverse"></h1>
+```
+
+### `date`
+
+Parses the value into a formatted date string.
+
+```html
+<p inner-text="published_at | date:YYYY-mm-dd"></p>
+```
+
+### `truncate`
+
+Truncates the value to the specified length.
+
+```html
+<p inner-text="{title | truncate:10}"></p>
+```
+
+This also works on collections, so you can use `truncate` to limit items in an array as well.
+
+## Extending
+
+### Attribute Parsers
+
+You can add (or replace) attribute parsers in HTMTL when creating a new instance of the `Htmtl` class, like so:
+
+```python
+from htmtl import Htmtl
+from htmtl.attribute_parsers import InnerText
+
+htmtl = Htmtl('<p inner-text="Hello {who}"></p>', {'who': 'World'})
+htmtl.set_attribute_parsers([
+ InnerText,
+])
+
+html = htmtl.html() # returns: <p>Hello World</p>
+```
+
+Attribute parsers must extend the `AttributeParser` class, like so:
+
+```python
+from typing import Optional
+from dompa.nodes import Node, TextNode
+from htmtl import AttributeParser
+
+
+class InnerText(AttributeParser):
+ def traverse(self, node: Node) -> Optional[Node]:
+ if "inner-text" in node.attributes:
+ node.children = [TextNode(value=self.expression(node.attributes["inner-text"]))]
+ node.attributes.pop("inner-text")
+
+ return node
+```
+
+All attribute parsers traverse the entire DOM tree to do whatever DOM manipulation they want. It's important to know that
+an attribute parser must have the `traverse` method, and it must return a `Node`, or `None` if you want to remove the `Node`.
+
+HTMTL is built upon the [Dompa](https://github.com/askonomm/dompa) HTML parser, so check that out for more granular info on things.
+
+#### List of built-in attribute parsers
+
+- `htmtl.attribute_parsers.GenericValue` - Parser the `:*` attributes. (**soon**)
+- `htmtl.attribute_parsers.If` - Parser the `if` attributes. (**soon**)
+- `htmtl.attribute_parsers.Unless` - Parser the `unless` attributes. (**soon**)
+- `htmtl.attribute_parsers.InnerPartial` - Parser the `inner-partial` attributes. (**soon**)
+- `htmtl.attribute_parsers.InnerHtml` - Parser the `inner-html` attributes. (**soon**)
+- `htmtl.attribute_parsers.InnerText` - Parser the `inner-text` attributes.
+- `htmtl.attribute_parsers.OuterPartial` - Parser the `outer-partial` attributes. (**soon**)
+- `htmtl.attribute_parsers.OuterHtml` - Parser the `outer-html` attributes. (**soon**)
+- `htmtl.attribute_parsers.OuterText` - Parser the `outer-text` attributes.
+- `htmtl.attribute_parsers.Foreach` - Parses the `foreach` attributes. (**soon**)
+
+### Expression Modifiers
+
+You can add (or replace) expression modifiers in HTMTL when creating a new instance of the `Htmtl` class, like so:
+
+```python
+from htmtl import Htmtl
+from htmtl.expression_modifiers import Truncate
+
+htmtl = Htmtl('<p inner-text="Hello {who}"></p>', {'who': 'World'})
+htmtl.set_expression_modifiers([
+ Truncate,
+])
+
+html = htmtl.html() # returns: <p>Hello World</p>
+```
+
+Expression modifiers must implement the `ExpressionModifier` class, like so:
+
+```python
+from typing import Any
+from htmtl import ExpressionModifier, modifier
+
+
+@modifier("truncate")
+class Truncate(ExpressionModifier):
+ def modify(self, value: Any, opts: list[Any]) -> Any:
+ if isinstance(value, str) and len(opts) > 0:
+ if all([x in "1234567890" for x in opts[0]]):
+ char_limit = int(opts[0])
+
+ if len(value) > char_limit:
+ return f"{value[:char_limit - 3]}..."
+
+ return value
+```
+
+All expression modifiers need to have a name (that you will use in your templates), and you can name your modifier
+with the `modifier` decorator.
+
+#### List of built-in expression modifiers
+
+- `htmtl.expression_modifiers.Truncate` - Truncates the value (both strings and collections).
diff --git a/htmtl/__init__.py b/htmtl/__init__.py
index bdf128f..4286154 100644
--- a/htmtl/__init__.py
+++ b/htmtl/__init__.py
@@ -1 +1,3 @@
from .htmtl import Htmtl
+from .attribute_parser import AttributeParser
+from .expression_modifier import ExpressionModifier, modifier \ No newline at end of file
diff --git a/htmtl/attribute_parsers/__init__.py b/htmtl/attribute_parsers/__init__.py
index d05c6db..4ad8185 100644
--- a/htmtl/attribute_parsers/__init__.py
+++ b/htmtl/attribute_parsers/__init__.py
@@ -1 +1,2 @@
-from .inner_text import InnerText \ No newline at end of file
+from .inner_text import InnerText
+from .outer_text import OuterText \ No newline at end of file
diff --git a/htmtl/expression_modifiers/__init__.py b/htmtl/expression_modifiers/__init__.py
index e69de29..f200bbd 100644
--- a/htmtl/expression_modifiers/__init__.py
+++ b/htmtl/expression_modifiers/__init__.py
@@ -0,0 +1 @@
+from .truncate import Truncate \ No newline at end of file
diff --git a/htmtl/htmtl.py b/htmtl/htmtl.py
index d9fb312..1deecac 100644
--- a/htmtl/htmtl.py
+++ b/htmtl/htmtl.py
@@ -19,7 +19,6 @@ class Htmtl:
self.__data = data or {}
self.__attribute_parsers = self.__default_attribute_parsers()
self.__expression_modifiers = self.__default_expression_modifiers()
- self.__parse()
@staticmethod
def __default_attribute_parsers() -> list[type[AttributeParser]]:
@@ -56,4 +55,6 @@ class Htmtl:
self.__dom.update(parser_instance.traverse)
def html(self) -> str:
+ self.__parse()
+
return self.__dom.html() \ No newline at end of file