Skip to content

Commit

Permalink
Add map_explicit_tags_only setting. Handle None default_mapper.
Browse files Browse the repository at this point in the history
  • Loading branch information
adamghill committed Nov 15, 2024
1 parent 3584de6 commit cd228a5
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 28 deletions.
32 changes: 28 additions & 4 deletions docs/source/examples.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
# Examples

## Bare HTML
`dj-angles` is pretty flexible when determining what HTML to parse. Here are some examples of to show what can be done. Set up [custom mappers](mappers.md) to handle additional use cases.

## React-style include
## Tags without a dj- prefix

```python
# settings.py

ANGLES = {
"initial_tag_regex": r"(?=[A-Z])", # regex matches upper-case letter lookahead
"slugify_tag": True, # without this the template `PartialOne.html` will be loaded
"initial_tag_regex": r"(?=\w)", # lookahead match anything that starts with a letter
"map_explicit_tags_only": True, # only map tags we know about to prevent mapping standard HTML tags
}
```

```html
<block name='content'>
<include 'partial.html' />
</block>
```

This would transpile to the following.

```text
{% block content %}
<dj-partial>{% include 'partial.html' %}</dj-partial>
{% endblock content %}
```

## React-style include

```python
# settings.py

ANGLES = {
"initial_tag_regex": r"(?=[A-Z])", # lookahead match upper-case letter
}
```

```html
<PartialOne />
```

Expand Down
44 changes: 24 additions & 20 deletions docs/source/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,6 @@

Settings can be configured via an `ANGLES` dictionary in `settings.py`.

## `slots_enabled`

Enables [slots](components.md#slots) functionality for components. `Boolean` which defaults to `False`.

## `initial_tag_regex`

The regex to know that particular HTML should be parsed by `dj-angles`. `Raw string` which defaults to `r"(dj-)"`.

## `lower_case_tag`

Lower-cases the tag. Deprecated and superseded by `kebab_case_tag`. `Boolean` which defaults to `False`.

## `kebab_case_tag`

Makes the tag kebab case based on upper case letter, e.g. "PartialOne" would get converted to "partial-one". `Boolean` which defaults to `True`.

## `mappers`

Provide additional mappers. `Dictionary` which defaults to `{}`. More details about [mappers](mappers.md).

## `default_mapper`

A default mapper. Useful for tighter integration with other component libraries. `String` which defaults to `"dj_angles.mappers.angles.default_mapper"`.
Expand Down Expand Up @@ -59,3 +39,27 @@ Would be translated to the following.
```html
<dj-partial>{% bird partial / %}</dj-partial>
```

## `initial_tag_regex`

The regex to know that particular HTML should be parsed by `dj-angles`. `Raw string` which defaults to `r"(dj-)"`.

## `kebab_case_tag`

Makes the tag kebab case based on upper case letter, e.g. "PartialOne" would get converted to "partial-one". `Boolean` which defaults to `True`.

## `lower_case_tag`

Lower-cases the tag. Deprecated and superseded by `kebab_case_tag`. `Boolean` which defaults to `False`.

## `map_explicit_tags_only`

Do not fallback to the default if a mapper cannot be found. `Boolean` which defaults to `False`.

## `mappers`

Custom additional mappers. `Dictionary` which defaults to `{}`. More details about [mappers](mappers.md).

## `slots_enabled`

Enables [slots](components.md#slots) functionality for components. `Boolean` which defaults to `False`.
7 changes: 6 additions & 1 deletion example/project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@

ROOT_URLCONF = "project.urls"

ANGLES = {"slots_enabled": True}
ANGLES = {
"slots_enabled": True,
# "initial_tag_regex": r"(?=\w)",
# "map_explicit_tags_only": True,
# "default_mapper": None,
}
DJANGO_BIRD = {"ENABLE_AUTO_CONFIG": False}

TEMPLATES = [
Expand Down
7 changes: 4 additions & 3 deletions src/dj_angles/mappers/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get_tag_map() -> Optional[dict[Optional[str], Union[Callable, str]]]:
global tag_map # noqa: PLW0603

if tag_map is None:
tag_map = TAG_NAME_TO_DJANGO_TEMPLATE_TAG_MAP
tag_map = TAG_NAME_TO_DJANGO_TEMPLATE_TAG_MAP.copy()

if tag_map is None:
raise AssertionError("Invalid tag_map")
Expand All @@ -61,8 +61,9 @@ def get_tag_map() -> Optional[dict[Optional[str], Union[Callable, str]]]:
# Add default mapper if in settings, or fallback to the default mapper
default_mapper = get_setting("default_mapper", "dj_angles.mappers.angles.default_mapper")

# Add the default with a magic key of `None`
tag_map.update({None: import_string(default_mapper)})
if default_mapper is not None:
# Add the default with a magic key of `None`
tag_map.update({None: import_string(default_mapper)})

return tag_map

Expand Down
5 changes: 5 additions & 0 deletions src/dj_angles/regex_replacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,16 @@ def get_replacements(html: str, *, raise_for_missing_start_tag: bool = True) ->

tag_map = get_tag_map()

map_explicit_tags_only = get_setting("map_explicit_tags_only", False)

for match in re.finditer(tag_regex, html):
tag_html = html[match.start() : match.end()].strip()
tag_name = match.group("tag_name").strip()
template_tag_args = match.group("template_tag_args").strip()

if (map_explicit_tags_only or tag_map.get(None) is None) and tag_name.lower() not in tag_map:
continue

tag = Tag(
tag_map=tag_map,
html=tag_html,
Expand Down
Empty file.
18 changes: 18 additions & 0 deletions tests/dj_angles/mappers/mapper/test_get_tag_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from dj_angles.mappers.mapper import get_tag_map


def test_default():
expected = "default_mapper"
actual = get_tag_map()

assert len(actual) == 21
assert actual[None].__name__ == expected


def test_none_default_mapper(settings):
settings.ANGLES["default_mapper"] = None

actual = get_tag_map()

assert len(actual) == 20
assert actual.get(None) is None
39 changes: 39 additions & 0 deletions tests/dj_angles/regex_replacer/test_get_replacements.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,42 @@ def test_css(template_string, replacement_string):
actual = get_replacements(template_string, raise_for_missing_start_tag=False)

assert actual == expected


def test_no_prefix(settings):
# settings.ANGLES["default_mapper"] = None
settings.ANGLES["initial_tag_regex"] = r"(?=\w)"

expected = [("<block name='content'>", "{% block content %}")]
actual = get_replacements("<block name='content'>", raise_for_missing_start_tag=False)

assert actual == expected


def test_no_prefix_with_default_mapper(settings):
settings.ANGLES["initial_tag_regex"] = r"(?=\w)"

expected = [("<partial />", "<dj-partial>{% include 'partial.html' %}</dj-partial>")]
actual = get_replacements("<partial />", raise_for_missing_start_tag=False)

assert actual == expected


def test_no_prefix_without_default_mapper(settings):
settings.ANGLES["default_mapper"] = None
settings.ANGLES["initial_tag_regex"] = r"(?=\w)"

expected = []
actual = get_replacements("<partial />", raise_for_missing_start_tag=False)

assert actual == expected


def test_no_prefix_map_explicit_tags_only(settings):
settings.ANGLES["map_explicit_tags_only"] = True
settings.ANGLES["initial_tag_regex"] = r"(?=\w)"

expected = []
actual = get_replacements("<partial />", raise_for_missing_start_tag=False)

assert actual == expected
11 changes: 11 additions & 0 deletions tests/dj_angles/tags/test_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,14 @@ def test_default_mapping(settings):
actual = tag.get_django_template_tag()

assert expected == actual


def test_get_attribute_value_or_first_key():
expected = "'test1'"

html = "<dj-include template='test1'>"

tag = create_tag(html)
actual = tag.get_attribute_value_or_first_key("template")

assert expected == actual

0 comments on commit cd228a5

Please sign in to comment.