Template in Place
Happy New Year!
Our website is a little bit adhoc. A while back, I envisioned a very basic templater. This is a Python script that I came up with.
The test cases at the bottom should explain a bit about how it works. It's for making files current, correct, and determining if things need to be changed at all. Very, very trivial system that allows basic templating without breaking HTML syntax, so your syntax highlighting still works.
We are certainly not remotely modern with our website, but it does function.
I have this file saved as tip.py.
from dataclasses import dataclass
from pathlib import Path
from typing import Callable
# These are optimized for HTML.
BEGIN_TIP_COMMENT_PREFIX = '<!-- BEGIN TIP "'
BEGIN_TIP_COMMENT_SUFFIX = '" -->'
END_TIP_COMMENT = "<!-- END TIP -->"
@dataclass
class TemplateInPlace:
templaters: dict[str, Callable[[str], str]]
begin_tip_comment_prefix: str = BEGIN_TIP_COMMENT_PREFIX
begin_tip_comment_suffix: str = BEGIN_TIP_COMMENT_SUFFIX
end_tip_comment: str = END_TIP_COMMENT
def _parse_begin_comment_templater(self, line: str) -> str:
"""Grab which templater to use from the line."""
return line.removeprefix(self.begin_tip_comment_prefix).removesuffix(
self.begin_tip_comment_suffix
)
def _template(self, template: str, file: Path = Path()) -> str:
if template not in self.templaters:
raise ValueError(f"{template} has no matching templater.")
return self.templaters[template](file=file)
def check_string(self, data: str) -> bool:
"""Checks if a string is up to date and valid."""
return self.process_string(data) == data
def check_file(self, file: Path) -> bool:
"""Checks if a file is up to date and valid."""
data = file.read_text()
return self.process_string(data, file) == data
def rewrite_file(self, file: Path) -> None:
"""Rewrite a file."""
current = file.read_text()
file.write_text(self.process_string(current, file))
def process_file(self, file: Path) -> None:
"""Process a file as needed."""
if not self.check_file(file):
self.rewrite_file(file)
def process_string(self, data: str, file: Path = Path()) -> str:
"""Process a string."""
output = ""
in_tip = False
for line in data.splitlines():
if not in_tip:
output += line + "\n"
if self.begin_tip_comment_prefix in line:
if in_tip:
raise ValueError("Found a nested begin tip line!")
in_tip = True
output += (
self._template(self._parse_begin_comment_templater(line), file)
+ "\n"
)
elif self.end_tip_comment in line:
output += line + "\n"
if in_tip:
in_tip = False
else:
raise ValueError("Ended tip before started tip!")
if in_tip:
raise ValueError("File is invalid! Ended with tip!")
return output
# Testing
def year(file: Path) -> str:
return "2023"
templaters = {"year": year}
tip = TemplateInPlace(templaters=templaters)
CURRENT = """
<html>
<body>
<!-- BEGIN TIP "year" -->
2023
<!-- END TIP -->
</body>
</html>
"""
assert tip.check_string(CURRENT) is True
INCORRECT_DATA = """
<html>
<body>
<!-- BEGIN TIP "year" -->
300000
<!-- END TIP -->
</body>
</html>
"""
assert tip.check_string(INCORRECT_DATA) is False
EMPTY_DATA = """
<html>
<body>
<!-- BEGIN TIP "year" -->
<!-- END TIP -->
</body>
</html>
"""
assert tip.check_string(EMPTY_DATA) is False
For a usage example, I can share my embarrassing build_html.py file.
import os
import json
from pathlib import Path
from enum import Enum
from tip import TemplateInPlace
operating_systems = json.loads(Path("slugs/os.json").read_text())
flavors = json.loads(Path("slugs/flavors.json").read_text())
regions = json.loads(Path("slugs/regions.json").read_text())
flavors_html = """
<table class="table table-striped">
<tr>
<th>Provider (--provider)</th>
<th>Flavor "slug" (--flavor)</th>
<th>Cores</th>
<th>Memory</th>
<th>Disk</th>
<th>Bandwidth</th>
<th>Price per month (30 days, USD)</th>
</tr>
"""
for flavor in flavors:
monthly_price = f'${flavor["price"] * 0.01 * 30:,.2f}'
flavors_html += f"""
<tr>
<td>{flavor["provider"]}</td>
<td>{flavor["slug"]}</td>
<td>{flavor["cores"]}vCPU(s)</td>
<td>{flavor["memory"]} MiB</td>
<td>{flavor["disk"]}GiB</td>
<td>{flavor["bandwidth_per_month"]} TiB</td>
<td>{monthly_price}</td>
</tr>
"""
flavors_html += "</table>"
def _flavors_html(file: Path) -> str:
return flavors_html
regions_html = """
<table class="table table-striped">
<tr>
<th>Provider (--provider)</th>
<th>Region "slug" (--region)</th>
<th>Region</th>
</tr>
"""
for region in regions:
regions_html += f"""
<tr>
<td>{region["provider"]}</td>
<td>{region["slug"]}</td>
<td>{region["name"]}</td>
</tr>
"""
regions_html += "</table>"
def _regions_html(file: Path) -> str:
return regions_html
operating_systems_html = """
<table class="table table-striped">
<tr>
<th>Provider (--provider)</th>
<th>OS "slug" (--operating-system)</th>
<th>Operating System</th>
</tr>
"""
for operating_system in operating_systems:
operating_systems_html += f"""
<tr>
<td>{operating_system["provider"]}</td>
<td>{operating_system["slug"]}</td>
<td>{operating_system["name"]}</td>
</tr>
"""
operating_systems_html += "</table>"
def _operating_systems_html(file: Path) -> str:
return operating_systems_html
def head(file: Path) -> str:
return Path("components/head.html").read_text()
def footer(file: Path) -> str:
return Path("components/footer.html").read_text()
def link(path: str, title: str, is_active: bool) -> str:
active = ""
if is_active:
active = " active"
output = '<div><a class="btn btn-outline-primary btn-block'
output += f'{active}" href="{path}">{title}</a>'
output += "</div>\n"
return output
PAGES_AND_PATHS = {
"Main": "/",
"About": "/about/",
"Pricing": "/pricing/",
"Affiliate": "/affiliate/",
"Contact": "/contact/",
"FAQ": "/faq/",
"API": "/api_documentation/",
"Blog": "/blog",
}
def links(file: Path) -> str:
output = '<div class="d-md-flex flex-wrap">\n'
# FIXME: Very hacky!
for title in PAGES_AND_PATHS:
is_active = False
# Python 3.11 can't do match case with Pathlib.
# TypeError: Path() accepts 0 positional sub-patterns (1 given)
match str(file):
case "html/index.html":
if title == "Main":
is_active = True
case "html/pricing/index.html":
if title == "Pricing":
is_active = True
case "html/affiliate/index.html":
if title == "Affiliate":
is_active = True
case "html/about/index.html":
if title == "About":
is_active = True
case "html/contact/index.html":
if title == "Contact":
is_active = True
case "html/faq/index.html":
if title == "FAQ":
is_active = True
case "html/api_documentation/index.html":
if title == "API":
is_active = True
case _:
is_active = False
output += link(path=PAGES_AND_PATHS[title], title=title, is_active=is_active)
output += "</div>\n"
return output
for page in Path("html").rglob("*.html"):
tip = TemplateInPlace(
templaters={
"head": head,
"footer": footer,
"links": links,
"flavors": _flavors_html,
"regions": _regions_html,
"operating_systems": _operating_systems_html,
}
)
needs_update = not tip.check_file(page)
if needs_update:
print(f"{page} needs to be rewritten!")
tip.rewrite_file(page)
else:
print(f"{page} needs is already correct.")
As to the HTML files themselves, you can view source and see "BEGIN TIP" and such, for more of an idea of how it works.
This is not meant for unsanitized input!
Probably not relevant to many, but maybe it'll benefit someone.