"""Emitting the pages mews.page serves. Every page the service produces is itself a Mews page, so this module writes the same prologue, head and body shape as md2mews.py. The emitter is repeated here rather than imported because md2mews.py is a standalone script that pulls in a Markdown parser; tests/test_pages.py fails if the two drift apart. """ from datetime import UTC, datetime import fcntl from html import escape import os from pathlib import Path import sqlite3 from mews import VERSION from mews.lint import validate_bytes DOCTYPE = ( '' ) VARIANTS = ("mews-warm", "mews-cool", "mews-green", "mews-mono") STYLESHEET = "mews-0.1.css" SITE_VARIANT = "mews-cool" # SPEC.md 4.3 caps a page at 256 KB and asks for 64 KB. The directory refuses to # publish a page over the first and warns past the second. SIZE_MUST = 256 * 1024 SIZE_SHOULD = 64 * 1024 def _text(value: str) -> str: """Escape text content, leaving apostrophes alone so prose reads as prose.""" return escape(value, quote=False) def _attr(value: str) -> str: """Escape a value going into an attribute.""" return escape(value, quote=True) def page( title: str, body: list[str], language: str = "en", direction: str | None = None, variant: str | None = None, description: str | None = None, ) -> str: """Wrap body lines in a complete Mews page. Mirrors md2mews.page().""" attrs = f'xml:lang="{language}" lang="{language}"' if direction: attrs += f' dir="{direction}"' head = [ f" {_text(title)}", f' ', ' ', ] if description: head.append(f' ') head.append(f' ') body_attrs = f' class="{variant}"' if variant else "" return "\n".join( [ '', DOCTYPE, f'', " ", *head, " ", f" ", *body, " ", "", "", ] ) def site_page(title: str, body: list[str], description: str | None = None) -> str: """Wrap body lines in a page with the mews.page look.""" return page(title, body, variant=SITE_VARIANT, description=description) def message(title: str, paragraphs: list[str], links: list[tuple[str, str]]) -> str: """Build a short page that says one thing, with a way onwards.""" body = [f"

{_text(title)}

"] body += [f"

{_text(line)}

" for line in paragraphs] body.append("
") body += [ f'

{_text(label)}

' for href, label in links ] return site_page(title, body, description=paragraphs[0] if paragraphs else None) def report(findings_report, *, listed: bool, command: str) -> str: """Build the page an author gets back after a check.""" conforms = findings_report.conforms if listed: title = "Your site is listed" opening = ( "That page follows Mews Profile 0.1, so your site is in the " "directory now. Thank you for adding it." ) elif conforms: title = "This page conforms" opening = "That page follows Mews Profile 0.1. There is nothing to fix." else: title = "This page doesn't conform yet" opening = ( "Here is what to fix. Each line names the section of the spec that " "explains the rule, so you can go and read why." ) body = [f"

{_text(title)}

", f"

{_text(opening)}

"] if findings_report.url: body.append( f'

Checked ' f"{_text(findings_report.url)}.

" ) if findings_report.failures: body.append("

To fix

") body.append(" ") if findings_report.warnings: body.append("

Worth fixing

") body.append(" ") for note in findings_report.notes: body.append(f"

{_text(note)}

") body.append("
") body.append( '

The rules are all in the spec. Checking ' "on your own machine is quicker while you are still fixing things:

" ) body.append(f"
{_text(command)}
") body.append( '

Check another page ' '· The directory

' ) return site_page(title, body) def directory(rows: list[sqlite3.Row]) -> str: """Build the directory page: every site whose page passes the checks.""" body = ["

Mews directory

"] if rows: count = f"{len(rows)} site" + ("" if len(rows) == 1 else "s") body.append( f"

{count} listed. Every one of these pages passed the checks " 'against Mews Profile 0.1, and gets looked ' "at again every week.

" ) body.append(" ") else: body.append("

Nothing here yet. Yours could be the first one in!

") body.append("
") body.append( '

Add your site ' '· Mews

' ) body.append( f"
Updated {datetime.now(UTC).strftime('%Y-%m-%d %H:%M')} " "UTC
" ) return site_page( "Mews directory", body, description="Sites whose pages follow Mews Profile 0.1.", ) def write_directory(target: str, rows: list[sqlite3.Row]) -> list[str]: """Write the directory page, replacing it only if it conforms. The page is validated before it is published, because the one page that has to be a good example is this one. Returns any warnings about it. """ destination = Path(target) destination.parent.mkdir(parents=True, exist_ok=True) text = directory(rows).encode() if len(text) > SIZE_MUST: raise ValueError( "The directory page would be over 256 KB, which section 4.3 " "forbids. Split it before listing more sites." ) lock = destination.with_name(destination.name + ".lock") with open(lock, "w") as handle: fcntl.flock(handle, fcntl.LOCK_EX) result = validate_bytes(text) if not result.conforms: raise ValueError( "The directory page doesn't conform: " + "; ".join(str(f) for f in result.failures) ) temporary = destination.with_name("." + destination.name + ".tmp") temporary.write_bytes(text) os.replace(temporary, destination) warnings = [str(f) for f in result.warnings] if len(text) > SIZE_SHOULD: warnings.append( f"The directory page is {len(text) // 1024} KB, over the 64 KB " "section 4.3 asks for." ) return warnings