diff --git a/src/bloggereasy/multipage.py b/src/bloggereasy/multipage.py new file mode 100644 index 0000000..05ae8d6 --- /dev/null +++ b/src/bloggereasy/multipage.py @@ -0,0 +1,101 @@ +"""Multi-page site generator for BloggerEasy. + +Generates a coordinated set of pages (home, about, contact) from a single +configuration, producing Blogger XML themes for each page. +""" +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from bloggereasy.integrations.sdk import generate_from_html +from bloggereasy.theme.presets import PRESETS, apply_preset + + +MULTIPAGE_TEMPLATES = { + "home": "templates/multipage/home.html", + "about": "templates/multipage/about.html", + "contact": "templates/multipage/contact.html", +} + +DEFAULT_CONFIG: dict[str, Any] = { + "site_title": "My Blog", + "site_tagline": "Thoughts and stories", + "hero_text": "Welcome to our corner of the internet.", + "year": 2025, + "contact_email": "hello@example.com", + "contact_twitter": "@myblog", + "contact_github": "myblog", + "features": [ + {"title": "Fast", "description": "Optimized for speed"}, + {"title": "Responsive", "description": "Looks great everywhere"}, + {"title": "Accessible", "description": "Built for everyone"}, + ], + "team_members": [ + {"name": "Jane Doe", "role": "Founder", "bio": "Building since 2020."}, + {"name": "John Smith", "role": "Engineer", "bio": "Full-stack developer."}, + ], +} + + +def _render_template(template_path: str, config: dict[str, Any]) -> str: + """Simple mustache-style template rendering.""" + text = Path(template_path).read_text(encoding="utf-8") + for key, value in config.items(): + if isinstance(value, str): + text = text.replace("{{" + key + "}}", value) + return text + + +def generate_multipage( + config: dict[str, Any] | None = None, + template: str = "simple", + output_dir: Path | str | None = None, +) -> dict[str, Any]: + """Generate a multi-page blog site with home, about, and contact pages. + + Args: + config: Site configuration dict. Merged with DEFAULT_CONFIG. + template: Theme preset name from PRESETS. + output_dir: Directory for output XML files. + + Returns: + Dict with per-page results and validation status. + """ + cfg = {**DEFAULT_CONFIG, **(config or {})} + out = Path(output_dir) if output_dir else Path("output") + out.mkdir(parents=True, exist_ok=True) + + if template not in PRESETS: + template = "simple" + + results: dict[str, Any] = {"template": template, "pages": {}} + + for page_name, tpl_rel in MULTIPAGE_TEMPLATES.items(): + tpl_path = Path(__file__).resolve().parents[2] / tpl_rel + if not tpl_path.exists(): + results["pages"][page_name] = {"ok": False, "error": f"Template not found: {tpl_path}"} + continue + + html_content = _render_template(str(tpl_path), cfg) + out_file = out / f"{page_name}.xml" + + gen_result = generate_from_html( + html_content if isinstance(html_content, str) else str(html_content), + out_file, + template=template, + ) + results["pages"][page_name] = { + "ok": gen_result.get("validation", {}).get("ok", False), + "output": str(out_file), + "additions": gen_result.get("additions", 0), + } + + results["all_ok"] = all(p.get("ok") for p in results["pages"].values()) + return results + + +def list_templates() -> list[str]: + """List available multipage template names.""" + return sorted(MULTIPAGE_TEMPLATES.keys()) diff --git a/templates/multipage/about.html b/templates/multipage/about.html new file mode 100644 index 0000000..dc137a3 --- /dev/null +++ b/templates/multipage/about.html @@ -0,0 +1,35 @@ + + + + + +About — {{site_title}} + + + +
+

{{site_title}}

+

{{site_tagline}}

+
+ +
+

About {{site_title}}

+
+ {{#team_members}} +
+

{{name}}

+

{{role}}

+

{{bio}}

+
+ {{/team_members}} +
+
+ + + \ No newline at end of file diff --git a/templates/multipage/contact.html b/templates/multipage/contact.html new file mode 100644 index 0000000..6bc92b2 --- /dev/null +++ b/templates/multipage/contact.html @@ -0,0 +1,40 @@ + + + + + +Contact — {{site_title}} + + + +
+

{{site_title}}

+

{{site_tagline}}

+
+ +
+

Get in Touch

+
+
+ + + + +
+
+

Other Ways to Reach Us

+

Email: {{contact_email}}

+

Twitter: {{contact_twitter}}

+

GitHub: {{contact_github}}

+
+
+
+ + + \ No newline at end of file diff --git a/templates/multipage/home.html b/templates/multipage/home.html new file mode 100644 index 0000000..050400f --- /dev/null +++ b/templates/multipage/home.html @@ -0,0 +1,37 @@ + + + + + +{{site_title}} — Home + + + +
+

{{site_title}}

+

{{site_tagline}}

+
+ +
+
+

Welcome to {{site_title}}

+

{{hero_text}}

+
+
+ {{#features}} +
+

{{title}}

+

{{description}}

+
+ {{/features}} +
+
+ + + \ No newline at end of file diff --git a/tests/test_multipage.py b/tests/test_multipage.py new file mode 100644 index 0000000..47048f5 --- /dev/null +++ b/tests/test_multipage.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from pathlib import Path + +from bloggereasy.multipage import generate_multipage, list_templates +from bloggereasy.theme.presets import PRESETS + + +def test_list_templates() -> None: + templates = list_templates() + assert "home" in templates + assert "about" in templates + assert "contact" in templates + assert len(templates) == 3 + + +def test_generate_multipage_creates_all_pages(tmp_path: Path) -> None: + result = generate_multipage(output_dir=tmp_path) + assert result["all_ok"], f"Not all pages passed: {result}" + assert (tmp_path / "home.xml").exists() + assert (tmp_path / "about.xml").exists() + assert (tmp_path / "contact.xml").exists() + + +def test_generate_multipage_with_custom_config(tmp_path: Path) -> None: + config = { + "site_title": "Test Blog", + "site_tagline": "Testing", + "hero_text": "Hello Test", + } + result = generate_multipage(config=config, output_dir=tmp_path, template="simple") + assert result["all_ok"] + for page in result["pages"].values(): + assert page["ok"] + + +def test_generate_multipage_rejects_bad_template(tmp_path: Path) -> None: + result = generate_multipage(template="nonexistent", output_dir=tmp_path) + assert result["template"] == "simple" # falls back + + +def test_multipage_supports_all_presets(tmp_path: Path) -> None: + for preset_name in ["simple", "dark", "magazine", "personal", "docs"]: + result = generate_multipage(template=preset_name, output_dir=tmp_path / preset_name) + assert result["all_ok"], f"{preset_name} failed: {result}"