Skip to content

Commit

Permalink
DjHTML Version 3
Browse files Browse the repository at this point in the history
For this release, a substantial part the codebase has been refactored.
Here is a non-exhaustive summary of all the things that have changed:

- Support for Python 3.6 and 3.7 has been dropped
- A Django middleware class to indent HTML responses has been added
- Ignore both opening and closing comment tags
- New handling off both relative and absolute offsets
- Return correct `repr()` strings with the `--debug` option
- New token class "OpenDouble" and a revised indentation algorithm
- Additional `line` argument to `create_token()` methods
- Changed return value of `create_token()` methods
- Changed constructor arguments of Token and Line classes
- Refactored all `create_token()` methods to get rid of return statements
- Simplified test suite runner that reindents the expected output
- Don't indent the contents of template variables (closes #75)
- Improved handling of Django tags inside HTML elements
- Improved JavaScript `case` indentation (closes #76)
- Improved JavaScript method chaining (closes #59)
- Improved CSS multiline statements (closes #74)
- New multiline HTML element indentation (closes #50)
- New multiline HTML attribute value indentation
- Extensive test coverage with lots of edge cases
  • Loading branch information
JaapJoris committed Feb 13, 2023
1 parent 03699d5 commit c2c706d
Show file tree
Hide file tree
Showing 28 changed files with 805 additions and 1,261 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ on:
jobs:
tests:
name: Python ${{ matrix.python-version }}
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04

strategy:
matrix:
python-version:
- 3.6
- 3.7
- 3.8
- 3.9
- '3.10'
Expand Down
52 changes: 41 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

***A pure-Python Django/Jinja template indenter without dependencies.***

DjHTML is a fully automatic template indenter that works with mixed
HTML/CSS/Javascript templates that contain
DjHTML indents mixed HTML/CSS/Javascript templates that contain
[Django](https://docs.djangoproject.com/en/stable/ref/templates/language/)
or [Jinja](https://jinja.palletsprojects.com/templates/) template
tags. It works similar to other code-formatting tools such as
Expand Down Expand Up @@ -59,10 +58,10 @@ Install DjHTML with the following command:

$ pip install djhtml

Note that [Windows still uses legacy code pages for the system
encoding](https://docs.python.org/3/using/windows.html#win-utf8-mode).
It is highly advised to set the environment variable `PYTHONUTF8` to
`1` to avoid issues with indenting UTF-8 files. You can do so with the
Note that
[Windows still uses legacy code pages](https://docs.python.org/3/using/windows.html#win-utf8-mode)
instead of UTF-8. It is highly advised to set the environment variable
`PYTHONUTF8` to `1` with the
[setx](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/setx)
command:

Expand All @@ -78,12 +77,42 @@ command:
reindented template.html
1 template has been reindented.

You can also run `djhtml .` to indent all HTML files beneath the
current directory.

An exit status of 0 means that everything went well, regardless of
whether any files were changed. When the option `-c` / `--check` is
used, the exit status is 1 when one or more files would have changed,
but no changes are actually made. The exit status of 123 means that
there was an error while indenting one or more files. All available
options are given by `djthml -h` / `djthml --help`.
but no changes are actually made. All available options are given by
`djthml -h` / `djthml --help`.


## New! Multi-line HTML tag alignment

As of version 3, DjHTML indents multi-line HTML tags and multi-line
attribute values like this:

```html
<blockquote cite="John Lennon"
style="color: dimgray;
font-style: italic;
border-left: 5px solid gray;
">
It's weird not to be weird.
</blockquote>
```


## New! Django middleware

To automatically indent all the HTML responses from your Django web
application, add the following to your settings file:

TABWIDTH = 4
MIDDLEWARE += ['djhtml.middleware.DjHTMLMiddleware']

(Caution: when used in production, it is advised to use some kind of
[caching](https://docs.djangoproject.com/en/stable/topics/cache/).)


## `fmt:off` and `fmt:on`
Expand All @@ -98,7 +127,7 @@ You can exclude specific lines from being processed with the
/ .\
\_,--._/
{# fmt:on #}
"/>
"/>
```

Contents inside `<pre> ... </pre>`, `<!-- ... --->`, `/* ... */`, and
Expand All @@ -124,7 +153,7 @@ The indenter operates in one of three different modes:

## pre-commit configuration

The best way to use DjHTML is as a [pre-commit](https://pre-commit.com/)
A great way to use DjHTML is as a [pre-commit](https://pre-commit.com/)
hook, so all your HTML, CSS and JavaScript files will automatically be
indented upon every commit.

Expand Down Expand Up @@ -203,6 +232,7 @@ happy, please do the following:

Your feedback for improving DjHTML is very welcome!


## Development

Use your preferred system for setting up a virtualenv, docker environment,
Expand Down
9 changes: 4 additions & 5 deletions djhtml/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ def main():

# Indent input file
try:
if options.debug:
print(Mode(source).debug())
sys.exit()
result = Mode(source).indent(options.tabwidth)
except Exception:
_error(
Expand All @@ -66,8 +63,7 @@ def main():
)
raise

changed = _verify_changed(source, result)
if changed:
if changed := _verify_changed(source, result):
changed_files += 1
else:
unchanged_files += 1
Expand Down Expand Up @@ -102,6 +98,9 @@ def main():
f"{problematic_files} template{s} could not be processed due to an error."
)

if options.debug:
print(Mode(source).debug(), file=sys.stderr)

# Exit with appropriate exit status
if problematic_files:
sys.exit(123)
Expand Down
48 changes: 21 additions & 27 deletions djhtml/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,25 @@ class Line:
"""
A single output line not including the final newline.
The behavior regarding final newlines has changed between DjHTML
v1.4.14 and v1.5.0. It used to always append the final newline,
but now this will only happen when the source file already
contains a final newline.
See https://github.com/rtts/djhtml/issues/56 for the discussion
that led to this change.
"""

def __init__(self, nr=1):
self.nr = nr
self.tokens = []
self.level = 0
def __init__(self, tokens=None, level=0, offset=0, ignore=False):
self.tokens = tokens or []
self.level = level
self.offset = offset
self.ignore = ignore

def append(self, token):
"""
Append tokens to the line.
"""
token.line_nr = self.nr
self.tokens.append(token)

@property
def text(self):
"""
The unindented text of this line without leading/trailing spaces.
The text of this line without leading/trailing spaces.
"""
return "".join([str(token) for token in self.tokens]).strip()
Expand All @@ -39,20 +31,22 @@ def indent(self, tabwidth):
and optionally offset before calling this method.
"""
if self.tokens:
if self.tokens[0].ignore:
return "".join([str(token) for token in self.tokens])
elif self.text:
offset = self.tokens[0].offset * tabwidth
spaces = tabwidth * self.level + offset
return " " * spaces + self.text
if self.ignore:
return "".join([str(token) for token in self.tokens])
if self.text:
spaces = tabwidth * self.level + self.offset
return " " * spaces + self.text
return ""

def __repr__(self):
return repr(self.tokens)

def __bool__(self):
return bool(self.tokens and self.text)
return bool(self.tokens)

def __len__(self):
return len(self.text)

def __next__(self):
return Line(nr=self.nr + 1)
def __repr__(self):
kwargs = ""
for attr in ["level", "offset", "ignore"]:
if value := getattr(self, attr):
kwargs += f", {attr}={value}"
return f"Line({repr(self.tokens)}{kwargs})"
35 changes: 35 additions & 0 deletions djhtml/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from djhtml.modes import DjHTML

try:
TABWIDTH = settings.TABWIDTH
except AttributeError:
TABWIDTH = 4


class DjHTMLMiddleware:
"""
Django middleware class to indent HTML responses.
"""

def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
if (
"Content-Type" in response
and response["Content-Type"] == "text/html; charset=utf-8"
):
if "Content-Length" in response:
raise ImproperlyConfigured(
"Please load DjHTMLMiddleware _after_ CommonMiddleware (otherwise"
' the "Content-Length" header will be incorrect)'
)
response.content = (
DjHTML(response.content.decode()).indent(TABWIDTH).encode()
)
return response
Loading

0 comments on commit c2c706d

Please sign in to comment.