simplibs-extractor-html 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simplibs/extractor_html/__init__.py +82 -0
- simplibs/extractor_html/actions/ApplyToHtmlBlocks.py +29 -0
- simplibs/extractor_html/actions/CleanupHtmlWhitespace.py +22 -0
- simplibs/extractor_html/actions/CompactHtml.py +22 -0
- simplibs/extractor_html/actions/HtmlOutline.py +23 -0
- simplibs/extractor_html/actions/RemoveTemplateImports.py +22 -0
- simplibs/extractor_html/actions/StripHtmlComments.py +23 -0
- simplibs/extractor_html/actions/StripTemplateComments.py +27 -0
- simplibs/extractor_html/actions/TemplateOutline.py +23 -0
- simplibs/extractor_html/actions/__init__.py +57 -0
- simplibs/extractor_html/annotations/TemplateEngine.py +26 -0
- simplibs/extractor_html/annotations/__init__.py +14 -0
- simplibs/extractor_html/constants/HTML_ANY_TAG_PATTERN.py +16 -0
- simplibs/extractor_html/constants/HTML_BLOCK_LANGUAGE_BY_TYPE.py +41 -0
- simplibs/extractor_html/constants/HTML_COMMENT_END_PATTERN.py +19 -0
- simplibs/extractor_html/constants/HTML_CONDITIONAL_PATTERN.py +23 -0
- simplibs/extractor_html/constants/HTML_DEPENDENCY_DIR_NAMES.py +18 -0
- simplibs/extractor_html/constants/HTML_EXTRA_LANGUAGE_BY_SUFFIX.py +31 -0
- simplibs/extractor_html/constants/HTML_HEADING_ELEMENTS.py +15 -0
- simplibs/extractor_html/constants/HTML_ID_ATTRIBUTE_PATTERN.py +21 -0
- simplibs/extractor_html/constants/HTML_IMPLIED_END.py +34 -0
- simplibs/extractor_html/constants/HTML_LICENSE_START.py +14 -0
- simplibs/extractor_html/constants/HTML_LICENSE_TAGS.py +14 -0
- simplibs/extractor_html/constants/HTML_OUTLINE_ELEMENTS.py +32 -0
- simplibs/extractor_html/constants/HTML_OUTLINE_TEXT_LIMIT.py +15 -0
- simplibs/extractor_html/constants/HTML_OUTPUT_DIR_NAMES.py +43 -0
- simplibs/extractor_html/constants/HTML_PLAIN_SUFFIXES.py +18 -0
- simplibs/extractor_html/constants/HTML_RAW_TEXT_END_PATTERNS.py +28 -0
- simplibs/extractor_html/constants/HTML_SUFFIXES.py +22 -0
- simplibs/extractor_html/constants/HTML_TAG_NAME_PATTERN.py +16 -0
- simplibs/extractor_html/constants/HTML_TEMPLATE_BLOCKS.py +38 -0
- simplibs/extractor_html/constants/HTML_TEMPLATE_IMPORT_TAGS.py +29 -0
- simplibs/extractor_html/constants/HTML_TEMPLATE_OUTLINE_TAGS.py +35 -0
- simplibs/extractor_html/constants/HTML_TEMPLATE_SUFFIXES.py +19 -0
- simplibs/extractor_html/constants/HTML_TEMPLATE_TAG_NAME_PATTERN.py +16 -0
- simplibs/extractor_html/constants/HTML_TEMPLATE_TOKEN_PATTERN.py +16 -0
- simplibs/extractor_html/constants/HTML_TEXT_ELEMENTS.py +16 -0
- simplibs/extractor_html/constants/HTML_TOKEN_PATTERN.py +26 -0
- simplibs/extractor_html/constants/HTML_TYPE_ATTRIBUTE_PATTERN.py +21 -0
- simplibs/extractor_html/constants/HTML_VOID_ELEMENTS.py +21 -0
- simplibs/extractor_html/constants/HTML_WRAPPED_STEM_ENDING.py +14 -0
- simplibs/extractor_html/constants/HTML_WRAPPED_SUFFIXES.py +15 -0
- simplibs/extractor_html/constants/__init__.py +77 -0
- simplibs/extractor_html/file_rules/__init__.py +56 -0
- simplibs/extractor_html/file_rules/atomic/DEPENDENCY_DIRS.py +17 -0
- simplibs/extractor_html/file_rules/atomic/DOT_NAME.py +19 -0
- simplibs/extractor_html/file_rules/atomic/OUTPUT_DIRS.py +17 -0
- simplibs/extractor_html/file_rules/atomic/VCS_DIRS.py +21 -0
- simplibs/extractor_html/file_rules/atomic/WRAPPED_HTML.py +22 -0
- simplibs/extractor_html/file_rules/atomic/__init__.py +26 -0
- simplibs/extractor_html/file_rules/html/HTML_BASE.py +22 -0
- simplibs/extractor_html/file_rules/html/HTML_FILES.py +24 -0
- simplibs/extractor_html/file_rules/html/HTML_PLAIN_BASE.py +24 -0
- simplibs/extractor_html/file_rules/html/HTML_STRICT_EXCLUDE.py +21 -0
- simplibs/extractor_html/file_rules/html/__init__.py +20 -0
- simplibs/extractor_html/logic/__init__.py +44 -0
- simplibs/extractor_html/logic/_helpers/HtmlBlock.py +31 -0
- simplibs/extractor_html/logic/_helpers/HtmlOutlineBuilder.py +189 -0
- simplibs/extractor_html/logic/_helpers/HtmlScan.py +50 -0
- simplibs/extractor_html/logic/_helpers/HtmlTag.py +32 -0
- simplibs/extractor_html/logic/_helpers/Ranges.py +48 -0
- simplibs/extractor_html/logic/_helpers/TemplateScan.py +43 -0
- simplibs/extractor_html/logic/_helpers/TemplateTag.py +33 -0
- simplibs/extractor_html/logic/_helpers/__init__.py +68 -0
- simplibs/extractor_html/logic/_helpers/blank_spans.py +40 -0
- simplibs/extractor_html/logic/_helpers/build_html_outline.py +38 -0
- simplibs/extractor_html/logic/_helpers/build_template_outline.py +75 -0
- simplibs/extractor_html/logic/_helpers/find_blanks_start.py +29 -0
- simplibs/extractor_html/logic/_helpers/find_block_language.py +35 -0
- simplibs/extractor_html/logic/_helpers/find_comments.py +60 -0
- simplibs/extractor_html/logic/_helpers/find_protected_spans.py +48 -0
- simplibs/extractor_html/logic/_helpers/find_template_import_spans.py +37 -0
- simplibs/extractor_html/logic/_helpers/fit_removal_span.py +40 -0
- simplibs/extractor_html/logic/_helpers/is_license_comment.py +26 -0
- simplibs/extractor_html/logic/_helpers/line_of.py +23 -0
- simplibs/extractor_html/logic/_helpers/raise_block_result_invalid.py +38 -0
- simplibs/extractor_html/logic/_helpers/replace_spans.py +42 -0
- simplibs/extractor_html/logic/_helpers/scan_checked.py +45 -0
- simplibs/extractor_html/logic/_helpers/scan_html.py +208 -0
- simplibs/extractor_html/logic/_helpers/scan_templates.py +128 -0
- simplibs/extractor_html/logic/_helpers/scan_templates_checked.py +39 -0
- simplibs/extractor_html/logic/_helpers/skip_html_comment.py +41 -0
- simplibs/extractor_html/logic/_helpers/skip_html_tag.py +46 -0
- simplibs/extractor_html/logic/_helpers/tidy_lines.py +86 -0
- simplibs/extractor_html/logic/_helpers/warn_problems.py +32 -0
- simplibs/extractor_html/logic/apply_to_html_blocks.py +103 -0
- simplibs/extractor_html/logic/cleanup_html_whitespace.py +54 -0
- simplibs/extractor_html/logic/compact_html.py +44 -0
- simplibs/extractor_html/logic/html_comment_spans.py +54 -0
- simplibs/extractor_html/logic/html_outline.py +66 -0
- simplibs/extractor_html/logic/strip_html_comments.py +63 -0
- simplibs/extractor_html/logic/templates/__init__.py +25 -0
- simplibs/extractor_html/logic/templates/remove_template_imports.py +51 -0
- simplibs/extractor_html/logic/templates/strip_template_comments.py +60 -0
- simplibs/extractor_html/logic/templates/template_comment_spans.py +49 -0
- simplibs/extractor_html/logic/templates/template_outline.py +62 -0
- simplibs/extractor_html/plugin/__init__.py +14 -0
- simplibs/extractor_html/plugin/register_all.py +60 -0
- simplibs/extractor_html/presets/__init__.py +43 -0
- simplibs/extractor_html/presets/html_clean_steps.py +54 -0
- simplibs/extractor_html/presets/html_no_imports_steps.py +50 -0
- simplibs/extractor_html/presets/html_outline_steps.py +28 -0
- simplibs/extractor_html/presets/template_structure_steps.py +27 -0
- simplibs/extractor_html/profiles/DJANGO_CLEAN.py +36 -0
- simplibs/extractor_html/profiles/DJANGO_NO_IMPORTS.py +32 -0
- simplibs/extractor_html/profiles/DJANGO_OUTLINE.py +32 -0
- simplibs/extractor_html/profiles/DJANGO_STRUCTURE.py +32 -0
- simplibs/extractor_html/profiles/HTML_CODE_CLEAN.py +32 -0
- simplibs/extractor_html/profiles/HTML_CODE_NO_IMPORTS.py +30 -0
- simplibs/extractor_html/profiles/HTML_DATA_ALL.py +26 -0
- simplibs/extractor_html/profiles/HTML_OUTLINE.py +31 -0
- simplibs/extractor_html/profiles/HTML_PROFILES.py +42 -0
- simplibs/extractor_html/profiles/HTML_SRC_CLEAN.py +31 -0
- simplibs/extractor_html/profiles/JINJA_CLEAN.py +35 -0
- simplibs/extractor_html/profiles/JINJA_NO_IMPORTS.py +32 -0
- simplibs/extractor_html/profiles/JINJA_OUTLINE.py +30 -0
- simplibs/extractor_html/profiles/JINJA_STRUCTURE.py +31 -0
- simplibs/extractor_html/profiles/__init__.py +86 -0
- simplibs/extractor_html/profiles/steps_for_engine.py +39 -0
- simplibs/extractor_html/profiles/steps_for_pages.py +36 -0
- simplibs/extractor_html/py.typed +1 -0
- simplibs_extractor_html-0.1.0.dist-info/METADATA +504 -0
- simplibs_extractor_html-0.1.0.dist-info/RECORD +127 -0
- simplibs_extractor_html-0.1.0.dist-info/WHEEL +5 -0
- simplibs_extractor_html-0.1.0.dist-info/entry_points.txt +2 -0
- simplibs_extractor_html-0.1.0.dist-info/licenses/LICENSE +21 -0
- simplibs_extractor_html-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from simplibs.validate import validate_call
|
|
2
|
+
# Outers
|
|
3
|
+
from ..annotations import TemplateEngine
|
|
4
|
+
# Inners
|
|
5
|
+
from ._helpers import Ranges, find_protected_spans, scan_checked, tidy_lines
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def cleanup_html_whitespace(text: str, *, engine: TemplateEngine | None = None) -> str:
|
|
10
|
+
"""Tidy the whitespace of a page without changing how it looks: trailing blanks go, runs of blank lines become one.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: The source.
|
|
14
|
+
engine: The template language of the page (`"django"` or `"jinja"`); `None` for a page that is only HTML.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The page with no blanks at the ends of the lines and no more than one blank line in a row, and none
|
|
18
|
+
at the start or at the end. The lines that touch the content of `pre`, `textarea`, `script`, `style`
|
|
19
|
+
or a verbatim block are left exactly as they were. A page that looks damaged comes back unchanged,
|
|
20
|
+
with a warning.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# 1. Read the page; a damaged one is left as it is
|
|
24
|
+
scan = scan_checked(text, engine=engine)
|
|
25
|
+
if scan is None:
|
|
26
|
+
return text
|
|
27
|
+
|
|
28
|
+
# 2. Tidy every line that has no part in a protected range
|
|
29
|
+
return tidy_lines(text, Ranges(find_protected_spans(scan)), keep_blank_line=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_DESIGN_NOTES = """
|
|
33
|
+
# cleanup_html_whitespace — Tidy, and Leave the `pre` Alone
|
|
34
|
+
|
|
35
|
+
## Purpose
|
|
36
|
+
The step of the profiles that clean pages. The general cleaning of
|
|
37
|
+
`simplibs-text` does not know HTML: it would trim the lines of a `<pre>` and
|
|
38
|
+
change what the reader sees. This one reads the page first.
|
|
39
|
+
|
|
40
|
+
## What Is Safe
|
|
41
|
+
Between tags a browser treats a run of blanks and line breaks as one space, so
|
|
42
|
+
the blanks at the end of a line and the extra blank lines between elements
|
|
43
|
+
change nothing on screen.
|
|
44
|
+
|
|
45
|
+
## What Is Left Alone
|
|
46
|
+
`pre`, `textarea`, `script`, `style` and verbatim blocks (see `find_protected_spans`).
|
|
47
|
+
|
|
48
|
+
## Idempotent
|
|
49
|
+
A second run finds nothing to tidy.
|
|
50
|
+
|
|
51
|
+
## Known Limits
|
|
52
|
+
Inline text is judged as the other text: `a <b>b</b>` keeps its two spaces,
|
|
53
|
+
because the step trims line ends only; it never joins or splits lines.
|
|
54
|
+
"""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from simplibs.validate import validate_call
|
|
2
|
+
# Outers
|
|
3
|
+
from ..annotations import TemplateEngine
|
|
4
|
+
# Inners
|
|
5
|
+
from ._helpers import Ranges, find_protected_spans, scan_checked, tidy_lines
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def compact_html(text: str, *, engine: TemplateEngine | None = None) -> str:
|
|
10
|
+
"""Remove every blank line of a page and the trailing blanks of its lines, without changing how it looks.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: The source.
|
|
14
|
+
engine: The template language of the page (`"django"` or `"jinja"`); `None` for a page that is only HTML.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The page with no blank lines and no blanks at the ends of the lines. The lines that touch the content of
|
|
18
|
+
`pre`, `textarea`, `script`, `style` or a verbatim block are left exactly as they were. A page that looks
|
|
19
|
+
damaged comes back unchanged, with a warning.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# 1. Read the page; a damaged one is left as it is
|
|
23
|
+
scan = scan_checked(text, engine=engine)
|
|
24
|
+
if scan is None:
|
|
25
|
+
return text
|
|
26
|
+
|
|
27
|
+
# 2. Tidy every line that has no part in a protected range, dropping all the blank ones
|
|
28
|
+
return tidy_lines(text, Ranges(find_protected_spans(scan)), keep_blank_line=False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_DESIGN_NOTES = """
|
|
32
|
+
# compact_html — The Page Without Air
|
|
33
|
+
|
|
34
|
+
## Purpose
|
|
35
|
+
The step of the profiles that reduce a page to its content: the same as
|
|
36
|
+
`cleanup_html_whitespace`, but no blank line survives. It is the counterpart
|
|
37
|
+
of the `COMPACT` preset of `simplibs-text` for pages.
|
|
38
|
+
|
|
39
|
+
## Same Care
|
|
40
|
+
The protected ranges are the same as for the gentler step.
|
|
41
|
+
|
|
42
|
+
## Idempotent
|
|
43
|
+
A second run finds nothing to drop.
|
|
44
|
+
"""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from simplibs.text import Span
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ..annotations import TemplateEngine
|
|
5
|
+
# Inners
|
|
6
|
+
from ._helpers import find_comments
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@validate_call
|
|
10
|
+
def html_comment_spans(
|
|
11
|
+
text: str,
|
|
12
|
+
*,
|
|
13
|
+
engine: TemplateEngine | None = None,
|
|
14
|
+
keep_conditional: bool = True,
|
|
15
|
+
keep_license: bool = False,
|
|
16
|
+
) -> list[Span]:
|
|
17
|
+
"""Find the HTML comments of a page.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
text: The source.
|
|
21
|
+
engine: The template language of the page (`"django"` or `"jinja"`); `None` for a page that is only HTML.
|
|
22
|
+
keep_conditional: Leave out the Internet Explorer conditional comments (`<!--[if IE]> ... <![endif]-->`).
|
|
23
|
+
keep_license: Leave out the comments that tools keep (`<!--! ... -->`, `@license`, `@preserve`).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A span for each comment, from the blanks in front of it to its end; an empty list for a page
|
|
27
|
+
that looks damaged (a comment, a tag or a `script` that is never closed), with a warning.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# 1. The comments the scanner found, the ones that are to stay aside
|
|
31
|
+
return find_comments(
|
|
32
|
+
text,
|
|
33
|
+
engine=engine,
|
|
34
|
+
keep_conditional=keep_conditional,
|
|
35
|
+
keep_license=keep_license,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_DESIGN_NOTES = """
|
|
40
|
+
# html_comment_spans — Where the Comments Are
|
|
41
|
+
|
|
42
|
+
## Purpose
|
|
43
|
+
The spans of the comments, for anything that wants to look at them or cut them.
|
|
44
|
+
|
|
45
|
+
## What Is a Comment
|
|
46
|
+
`<!-- ... -->` outside an attribute, a `script`, a `style`, a `textarea` and a
|
|
47
|
+
`title`. A `<!--` inside any of those is text. Template comments (`{# #}`) are
|
|
48
|
+
not reported here.
|
|
49
|
+
|
|
50
|
+
## Never Raises
|
|
51
|
+
The scanner has no syntax it refuses: whatever the text is, it gets an answer.
|
|
52
|
+
A page that looks damaged is answered with no spans and a warning, so a step
|
|
53
|
+
that cuts them leaves the page as it is.
|
|
54
|
+
"""
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from simplibs.types import int_positive
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ..annotations import TemplateEngine
|
|
5
|
+
# Inners
|
|
6
|
+
from ._helpers import build_html_outline, scan_checked
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@validate_call
|
|
10
|
+
def html_outline(text: str, *, engine: TemplateEngine | None = None, max_depth: int_positive | None = None) -> str:
|
|
11
|
+
"""Write an overview of a page: its landmarks, its headings and everything that has an `id`, indented by nesting.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
text: The source.
|
|
15
|
+
engine: The template language of the page (`"django"` or `"jinja"`); `None` for a page that is only HTML.
|
|
16
|
+
max_depth: How many levels of nesting to list (`1` is the top level only); `None` lists all.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
One line for each listed element: `header#top`, `h2 "Opening hours"`; an empty string for a page
|
|
20
|
+
with nothing to list. A page that looks damaged (a comment, a tag or a `script` that is never
|
|
21
|
+
closed) comes back unchanged, with a warning.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
html_outline('<main><h1>Hi</h1><div id="a"></div></main>')
|
|
25
|
+
# 'main\\n h1 "Hi"\\n div#a\\n'
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# 1. Read the page with its tags; a damaged one is left as it is
|
|
29
|
+
scan = scan_checked(text, engine=engine, collect_tags=True)
|
|
30
|
+
if scan is None:
|
|
31
|
+
return text
|
|
32
|
+
|
|
33
|
+
# 2. The outline
|
|
34
|
+
return build_html_outline(text, scan, max_depth)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_DESIGN_NOTES = """
|
|
38
|
+
# html_outline — What a Page Is Made Of, Without the Markup
|
|
39
|
+
|
|
40
|
+
## Purpose
|
|
41
|
+
The step of the profile `html/outline`: the parts of a page and what they are
|
|
42
|
+
called, in a fraction of its size. For a reader with a limited window it is the
|
|
43
|
+
map of a page that would not fit.
|
|
44
|
+
|
|
45
|
+
## What Is Listed
|
|
46
|
+
* the landmarks (`header`, `nav`, `main`, `section`, `article`, `aside`,
|
|
47
|
+
`footer`, `form`, `table`, `dialog`, `template`),
|
|
48
|
+
* the title and the headings, with their text,
|
|
49
|
+
* every element with an `id`, written `tag#id`.
|
|
50
|
+
|
|
51
|
+
Depth is the number of listed elements around, so the `div`s between them are
|
|
52
|
+
not counted.
|
|
53
|
+
|
|
54
|
+
## Not Listed
|
|
55
|
+
Classes, attributes other than the `id`, text outside the headings, scripts and
|
|
56
|
+
styles. They are the content; the outline is the shape.
|
|
57
|
+
|
|
58
|
+
## Templates
|
|
59
|
+
With an `engine` the constructs are read first, so `<div {% if a > b %}>` is a
|
|
60
|
+
tag. An `id` that contains a template construct is written as it stands
|
|
61
|
+
(`item-{{ i }}`), and so is a heading with `{{ title }}` in it.
|
|
62
|
+
|
|
63
|
+
## A Damaged Page
|
|
64
|
+
The whole text is returned, as `python_outline` does for a source it cannot
|
|
65
|
+
parse, so the reader sees the page and the report says why it is not an outline.
|
|
66
|
+
"""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from simplibs.text import remove_spans
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ..annotations import TemplateEngine
|
|
5
|
+
# Inners
|
|
6
|
+
from ._helpers import find_comments
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@validate_call
|
|
10
|
+
def strip_html_comments(
|
|
11
|
+
text: str,
|
|
12
|
+
*,
|
|
13
|
+
engine: TemplateEngine | None = None,
|
|
14
|
+
keep_conditional: bool = True,
|
|
15
|
+
keep_license: bool = False,
|
|
16
|
+
) -> str:
|
|
17
|
+
"""Remove the HTML comments of a page.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
text: The source.
|
|
21
|
+
engine: The template language of the page (`"django"` or `"jinja"`); `None` for a page that is only HTML.
|
|
22
|
+
keep_conditional: Keep the Internet Explorer conditional comments (`<!--[if IE]> ... <![endif]-->`).
|
|
23
|
+
keep_license: Keep the comments that tools keep (`<!--! ... -->`, `@license`, `@preserve`).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The text without HTML comments; a line that held only a comment disappears. A page that looks
|
|
27
|
+
damaged (a comment, a tag or a `script` that is never closed) comes back unchanged, with a warning.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# 1. Which comments go
|
|
31
|
+
comments = find_comments(
|
|
32
|
+
text,
|
|
33
|
+
engine=engine,
|
|
34
|
+
keep_conditional=keep_conditional,
|
|
35
|
+
keep_license=keep_license,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 2. Cut them
|
|
39
|
+
return remove_spans(text, comments)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_DESIGN_NOTES = """
|
|
43
|
+
# strip_html_comments — The Comments Out, The Scripts Untouched
|
|
44
|
+
|
|
45
|
+
## Purpose
|
|
46
|
+
A step of the profiles that clean pages. It removes only `<!-- ... -->`: a
|
|
47
|
+
`<!--` inside an attribute, a `script`, a `style` or a `textarea` is text and
|
|
48
|
+
stays.
|
|
49
|
+
|
|
50
|
+
## Options
|
|
51
|
+
`engine` tells the scanner that the page is a template, so `{{ }}`, `{% %}`
|
|
52
|
+
and `{# #}` are read first and cannot confuse the HTML. It does not remove
|
|
53
|
+
template comments; that is a step of its own. `keep_conditional` is on by
|
|
54
|
+
default: an Internet Explorer conditional comment holds real tags, and
|
|
55
|
+
`keep_license` is off by default.
|
|
56
|
+
|
|
57
|
+
## A Damaged Page
|
|
58
|
+
It is returned as it is, and the warning makes the extractor call the file
|
|
59
|
+
`partial`. The step never cuts on a basis it does not trust.
|
|
60
|
+
|
|
61
|
+
## Idempotent
|
|
62
|
+
A second run finds nothing and returns the very same text.
|
|
63
|
+
"""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .remove_template_imports import remove_template_imports
|
|
2
|
+
from .strip_template_comments import strip_template_comments
|
|
3
|
+
from .template_comment_spans import template_comment_spans
|
|
4
|
+
from .template_outline import template_outline
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_DESIGN_NOTES = """
|
|
8
|
+
# templates — The Steps for Django and Jinja Templates
|
|
9
|
+
|
|
10
|
+
Plain imports, no `__all__` (the package root decides what is public).
|
|
11
|
+
|
|
12
|
+
## Internal Components Registry
|
|
13
|
+
|
|
14
|
+
| Component | Type | Description |
|
|
15
|
+
| :------------------------ | :------- | :--------------------------------------------------------------- |
|
|
16
|
+
| `remove_template_imports` | Function | Removes `{% load %}` (Django), `{% import %}` and `{% from %}` (Jinja). |
|
|
17
|
+
| `strip_template_comments` | Function | Removes `{# ... #}` and `{% comment %} ... {% endcomment %}`. |
|
|
18
|
+
| `template_comment_spans` | Function | The spans of the template comments. |
|
|
19
|
+
| `template_outline` | Function | The skeleton of a template: extends, include, blocks. |
|
|
20
|
+
|
|
21
|
+
## Layering & Design
|
|
22
|
+
- Every step takes the engine as a required keyword: `"django"` or `"jinja"`.
|
|
23
|
+
- The steps read the template constructs only; the HTML around them is not read.
|
|
24
|
+
- A template that looks damaged is left as it is and reported by a warning.
|
|
25
|
+
"""
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from simplibs.text import remove_spans
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ...annotations import TemplateEngine
|
|
5
|
+
from .._helpers import find_template_import_spans, scan_templates_checked
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def remove_template_imports(text: str, *, engine: TemplateEngine) -> str:
|
|
10
|
+
"""Remove the tags that bring names into a template: `{% load %}` (Django), `{% import %}` and `{% from %}` (Jinja).
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: The source.
|
|
14
|
+
engine: `"django"` or `"jinja"`.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
The text without those tags; a line that held only one disappears. A template that looks damaged
|
|
18
|
+
(a `{% comment %}` that is never closed) comes back unchanged, with a warning.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# 1. Read the constructs; a damaged template is left as it is
|
|
22
|
+
scan = scan_templates_checked(text, engine=engine)
|
|
23
|
+
if scan is None:
|
|
24
|
+
return text
|
|
25
|
+
|
|
26
|
+
# 2. Each tag with one of the engine's import names is a range; cut them
|
|
27
|
+
return remove_spans(text, find_template_import_spans(text, scan, engine))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_DESIGN_NOTES = """
|
|
31
|
+
# remove_template_imports — The Page Without Its Imports
|
|
32
|
+
|
|
33
|
+
## Purpose
|
|
34
|
+
A step of the profile of pages without imports: what a template needs from
|
|
35
|
+
other files is the part of its text that says least about the page itself.
|
|
36
|
+
|
|
37
|
+
## What Is an Import
|
|
38
|
+
Only the tags of `HTML_TEMPLATE_IMPORT_TAGS`: `load` in Django, `import` and
|
|
39
|
+
`from` in Jinja. `{% extends %}` and `{% include %}` stay, as they say what
|
|
40
|
+
the page is made of.
|
|
41
|
+
|
|
42
|
+
## By the Scan, Not by a Line Pattern
|
|
43
|
+
Tags are read by the scanner of templates, so a `{% load static %}` inside a
|
|
44
|
+
`{% comment %}` or a `{% verbatim %}` is never cut, and one on the same line
|
|
45
|
+
as other text (`{% load a %}{% load b %}`) is cut alone.
|
|
46
|
+
|
|
47
|
+
## Removing Is Not Harmless
|
|
48
|
+
A template that uses `{% static 'x' %}` no longer says where `static` comes
|
|
49
|
+
from. That is exactly what the reader of a page without imports gives up, as
|
|
50
|
+
the reader of a program without its `import` lines does.
|
|
51
|
+
"""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from simplibs.text import remove_spans
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ...annotations import TemplateEngine
|
|
5
|
+
from .._helpers import scan_templates_checked
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def strip_template_comments(text: str, *, engine: TemplateEngine) -> str:
|
|
10
|
+
"""Remove the comments of a template: `{# ... #}` and `{% comment %} ... {% endcomment %}`.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: The source.
|
|
14
|
+
engine: `"django"` (a `{# #}` stays on one line; `{% comment %}` exists) or `"jinja"` (a `{# #}`
|
|
15
|
+
may span lines; there is no comment tag).
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The text without template comments; a line that held only a comment disappears. A template that
|
|
19
|
+
looks damaged (a `{% comment %}` that is never closed) comes back unchanged, with a warning.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# 1. Read the constructs; a damaged template is left as it is
|
|
23
|
+
scan = scan_templates_checked(text, engine=engine)
|
|
24
|
+
if scan is None:
|
|
25
|
+
return text
|
|
26
|
+
|
|
27
|
+
# 2. Cut the comments
|
|
28
|
+
return remove_spans(text, scan.comments)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_DESIGN_NOTES = """
|
|
32
|
+
# strip_template_comments — The Notes of the Author
|
|
33
|
+
|
|
34
|
+
## Purpose
|
|
35
|
+
The step of the profiles that clean templates. An HTML comment is sent to the
|
|
36
|
+
browser, a template comment never leaves the server: this step removes the
|
|
37
|
+
second kind, `strip_html_comments` the first. A profile uses both, in either
|
|
38
|
+
order.
|
|
39
|
+
|
|
40
|
+
## Why `engine` Is Required
|
|
41
|
+
Whether a `{# ... #}` may span lines, and whether `{% comment %}` exists at all,
|
|
42
|
+
depends on the engine, and a page cannot say which one it is written for.
|
|
43
|
+
Guessing would cut text in one engine that is not a comment in the other.
|
|
44
|
+
|
|
45
|
+
## No Gap Needed
|
|
46
|
+
A comment between two words (`a{# c #}b`) disappears from the output of the
|
|
47
|
+
engine without a trace, so cutting it changes nothing.
|
|
48
|
+
|
|
49
|
+
## Whitespace Control
|
|
50
|
+
Jinja's `{#- ... -#}` also trims the whitespace around it in the output.
|
|
51
|
+
Removing the comment keeps that whitespace, so the rendered text can differ
|
|
52
|
+
by a blank; the structure of the template does not.
|
|
53
|
+
|
|
54
|
+
## A Damaged Template
|
|
55
|
+
It is returned as it is, and the warning makes the extractor call the file
|
|
56
|
+
`partial`.
|
|
57
|
+
|
|
58
|
+
## Idempotent
|
|
59
|
+
A second run finds nothing and returns the very same text.
|
|
60
|
+
"""
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from simplibs.text import Span
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ...annotations import TemplateEngine
|
|
5
|
+
from .._helpers import scan_templates_checked
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def template_comment_spans(text: str, *, engine: TemplateEngine) -> list[Span]:
|
|
10
|
+
"""Find the comments of a template: `{# ... #}` and `{% comment %} ... {% endcomment %}`.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: The source.
|
|
14
|
+
engine: `"django"` (a `{# #}` stays on one line; `{% comment %}` exists) or `"jinja"` (a `{# #}`
|
|
15
|
+
may span lines; there is no comment tag).
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A span for each comment, from the blanks in front of it to its end; an empty list for a template
|
|
19
|
+
that looks damaged (a `{% comment %}` that is never closed), with a warning.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# 1. Read the constructs; a damaged template is left as it is
|
|
23
|
+
scan = scan_templates_checked(text, engine=engine)
|
|
24
|
+
if scan is None:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
return list(scan.comments)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_DESIGN_NOTES = """
|
|
31
|
+
# template_comment_spans — Where the Template Comments Are
|
|
32
|
+
|
|
33
|
+
## Purpose
|
|
34
|
+
The spans of the comments that the template engine drops and the browser never
|
|
35
|
+
sees, for anything that wants to look at them or cut them.
|
|
36
|
+
|
|
37
|
+
## What Is a Comment
|
|
38
|
+
`{# ... #}` in both engines, and in Django `{% comment %} ... {% endcomment %}`
|
|
39
|
+
with an optional note. What stands inside is not read: a `{{ x }}` in a
|
|
40
|
+
comment is text of the comment.
|
|
41
|
+
|
|
42
|
+
## What Is Not
|
|
43
|
+
A `{# ... #}` inside `{% verbatim %}` or `{% raw %}` is plain text for the
|
|
44
|
+
engine and is not reported. A `{#` whose `#}` is on a later line is, in
|
|
45
|
+
Django, only text.
|
|
46
|
+
|
|
47
|
+
## Never Raises
|
|
48
|
+
A template that looks damaged is answered with no spans and a warning.
|
|
49
|
+
"""
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from simplibs.types import int_positive
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ...annotations import TemplateEngine
|
|
5
|
+
from .._helpers import build_template_outline, scan_templates_checked, warn_problems
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def template_outline(text: str, *, engine: TemplateEngine, max_depth: int_positive | None = None) -> str:
|
|
10
|
+
"""Write the skeleton of a template: `extends`, `include`, the blocks, and in Jinja the macros.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
text: The source.
|
|
14
|
+
engine: `"django"` or `"jinja"`.
|
|
15
|
+
max_depth: How many levels of blocks to list (`1` is the top level only); `None` lists all.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
One line for each of those tags, written as it stands in the template and indented by the depth of
|
|
19
|
+
its blocks; an empty string for a template with none. A template that looks damaged (a
|
|
20
|
+
`{% comment %}` that is never closed, blocks that do not pair up) comes back unchanged, with a warning.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
template_outline("{% extends 'b.html' %}{% block a %}{% include 'c.html' %}{% endblock %}", engine="django")
|
|
24
|
+
# "{% extends 'b.html' %}\\n{% block a %}\\n {% include 'c.html' %}\\n"
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# 1. Read the constructs; a damaged template is left as it is
|
|
28
|
+
scan = scan_templates_checked(text, engine=engine)
|
|
29
|
+
if scan is None:
|
|
30
|
+
return text
|
|
31
|
+
|
|
32
|
+
# 2. The skeleton; blocks that do not pair up mean the nesting cannot be trusted
|
|
33
|
+
lines, problems = build_template_outline(scan, engine, max_depth)
|
|
34
|
+
if problems:
|
|
35
|
+
warn_problems(problems)
|
|
36
|
+
return text
|
|
37
|
+
|
|
38
|
+
return "\n".join(lines) + "\n" if lines else ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_DESIGN_NOTES = """
|
|
42
|
+
# template_outline — What the Template Is Built Of
|
|
43
|
+
|
|
44
|
+
## Purpose
|
|
45
|
+
The step of the profile for templates: which template this one extends, what it
|
|
46
|
+
includes and which blocks it defines or fills in, in the order and nesting in
|
|
47
|
+
which they stand. A reader who has the outlines of all the templates of a
|
|
48
|
+
project sees how its pages are put together without reading a page.
|
|
49
|
+
|
|
50
|
+
## Why `engine` Is Required
|
|
51
|
+
A Jinja `macro` is part of the skeleton and a Django one does not exist, and a
|
|
52
|
+
`{# #}` that spans lines is a comment only in Jinja. A page cannot say which
|
|
53
|
+
engine it is written for.
|
|
54
|
+
|
|
55
|
+
## Tags in Comments
|
|
56
|
+
A tag inside `{% comment %}` or `{% verbatim %}` is not a tag of the template
|
|
57
|
+
and is not listed.
|
|
58
|
+
|
|
59
|
+
## A Damaged Template
|
|
60
|
+
It is returned as it is, with a warning, so the extractor calls the file
|
|
61
|
+
`partial`.
|
|
62
|
+
"""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .register_all import register_all
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_DESIGN_NOTES = """
|
|
5
|
+
# plugin — The Meeting Point With the Extractor
|
|
6
|
+
|
|
7
|
+
Plain imports, no `__all__`.
|
|
8
|
+
|
|
9
|
+
## Internal Components Registry
|
|
10
|
+
|
|
11
|
+
| Component | Type | Description |
|
|
12
|
+
| :------------- | :------- | :--------------------------------------------------------------- |
|
|
13
|
+
| `register_all` | Function | Registers the languages and profiles; the target of the entry point. |
|
|
14
|
+
"""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from simplibs.extractor import register_language, register_profile
|
|
2
|
+
from simplibs.validate import validate_call
|
|
3
|
+
# Outers
|
|
4
|
+
from ..constants import HTML_EXTRA_LANGUAGE_BY_SUFFIX
|
|
5
|
+
from ..profiles import HTML_PROFILES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@validate_call
|
|
9
|
+
def register_all(*, replace_existing: bool = True) -> list[str]:
|
|
10
|
+
"""Register every profile and language of this library in the registries of the extractor.
|
|
11
|
+
|
|
12
|
+
The extractor calls it, with no arguments, for every installed add-on
|
|
13
|
+
(entry-point group `simplibs.extractor.plugins`).
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
replace_existing: Replace a profile or a language that already has the key (the default, so calling it twice changes nothing).
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The keys of the registered profiles (`"html/code-clean"`...).
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
StateError: A profile key is taken and `replace_existing` is `False`.
|
|
23
|
+
ParamError: A language suffix is taken and `replace_existing` is `False`.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# 1. The languages of the other suffixes, so the output fences them properly
|
|
27
|
+
for suffix, language in HTML_EXTRA_LANGUAGE_BY_SUFFIX.items():
|
|
28
|
+
register_language(suffix, language, replace_existing=replace_existing)
|
|
29
|
+
|
|
30
|
+
# 2. Register each profile
|
|
31
|
+
keys: list[str] = []
|
|
32
|
+
for profile in HTML_PROFILES:
|
|
33
|
+
registered = register_profile(profile, replace_existing=replace_existing)
|
|
34
|
+
keys.append(registered.key)
|
|
35
|
+
|
|
36
|
+
return keys
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_DESIGN_NOTES = """
|
|
40
|
+
# register_all — What the Entry Point Calls
|
|
41
|
+
|
|
42
|
+
## Purpose
|
|
43
|
+
`pip install simplibs-extractor-html` is the whole installation: the entry
|
|
44
|
+
point `html = "simplibs.extractor_html:register_all"` makes the extractor find
|
|
45
|
+
this function, and the thirteen profiles appear in `--list-systems`.
|
|
46
|
+
|
|
47
|
+
## The Rules of an Add-On Function
|
|
48
|
+
It takes no required argument, registers with `replace_existing=True`, only
|
|
49
|
+
registers (no scanning, no network, no printing) and uses only the public API
|
|
50
|
+
of the extractor (`Profile`, `register_profile`, `register_language`).
|
|
51
|
+
|
|
52
|
+
## Languages
|
|
53
|
+
`.htm`, `.xhtml` and the suffixes of the Jinja family get their fence language
|
|
54
|
+
here. Without it the extractor would write their code blocks with no language
|
|
55
|
+
and a viewer would not colour them.
|
|
56
|
+
|
|
57
|
+
## Order
|
|
58
|
+
Languages first: a profile that is registered is usable at once, and its
|
|
59
|
+
output should already be fenced correctly.
|
|
60
|
+
"""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .html_clean_steps import html_clean_steps
|
|
2
|
+
from .html_no_imports_steps import html_no_imports_steps
|
|
3
|
+
from .html_outline_steps import html_outline_steps
|
|
4
|
+
from .template_structure_steps import template_structure_steps
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"html_clean_steps",
|
|
9
|
+
"html_no_imports_steps",
|
|
10
|
+
"html_outline_steps",
|
|
11
|
+
"template_structure_steps",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DESIGN_NOTES = """
|
|
16
|
+
# simplibs.extractor_html.presets — Ready-Made Pipeline Steps
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
The chains of actions that the profiles use. Each is a function of the template
|
|
20
|
+
engine (`None` for a page that is only HTML), because the chain is the same
|
|
21
|
+
for every engine and only the option of its actions differs:
|
|
22
|
+
|
|
23
|
+
from simplibs.extractor_html.presets import html_clean_steps
|
|
24
|
+
steps = html_clean_steps("django")
|
|
25
|
+
|
|
26
|
+
## Domain Folder
|
|
27
|
+
`presets/` has its own `__all__` and is not re-exported from the package root.
|
|
28
|
+
It stands to the right of `actions` (it uses them) and to the left of `profiles`.
|
|
29
|
+
|
|
30
|
+
## The Other Add-Ons
|
|
31
|
+
The presets for the code inside a page come from the add-ons of its languages
|
|
32
|
+
(`JS_CLEAN`, `CSS_PLAIN_CLEAN`...): this library depends on both, and a program
|
|
33
|
+
that wants other steps for the blocks calls `apply_to_html_blocks` itself.
|
|
34
|
+
|
|
35
|
+
## Internal Components Registry
|
|
36
|
+
|
|
37
|
+
| Component | Type | Description |
|
|
38
|
+
| :------------------------ | :----- | :---------------------------------------------------------------- |
|
|
39
|
+
| `html_clean_steps` | Preset | Comments out, code inside cleaned, whitespace tidied. |
|
|
40
|
+
| `html_no_imports_steps` | Preset | The same, without imports and blank lines. |
|
|
41
|
+
| `html_outline_steps` | Preset | The landmarks, headings and ids of a page. |
|
|
42
|
+
| `template_structure_steps`| Preset | The `extends`, `include` and blocks of a template. |
|
|
43
|
+
"""
|