{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Builders\n",
    "\n",
    "Domain-specific languages for structured output: HTML, Markdown, XML with validation."
   ]
  },
  {
   "cell_type": "code",
   "source": "# Setup: Install genro-bag (run this cell first on Colab)\\n!pip install -q genro-bag",
   "metadata": {},
   "execution_count": null,
   "outputs": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from genro_builders import BuilderBag, BagBuilderBase\n",
    "from genro_builders.builders import element"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The Core Idea\n",
    "\n",
    "Builders define a **vocabulary** of valid elements. When you build with a builder, you get:\n",
    "\n",
    "- Method chaining for nested structures\n",
    "- Structure validation (what can contain what)\n",
    "- Semantic tags on nodes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# A minimal builder for documents\n",
    "class DocBuilder(BagBuilderBase):\n",
    "    \"\"\"Builder for simple documents.\"\"\"\n",
    "    \n",
    "    @element(sub_tags=\"section, paragraph\")\n",
    "    def document(self): ...\n",
    "    \n",
    "    @element(sub_tags=\"paragraph, heading\")\n",
    "    def section(self): ...\n",
    "    \n",
    "    @element()\n",
    "    def heading(self): ...\n",
    "    \n",
    "    @element()\n",
    "    def paragraph(self): ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use the builder\n",
    "store = BuilderBag(builder=DocBuilder)\n",
    "\n",
    "doc = store.document()\n",
    "sec = doc.section(title=\"Introduction\")\n",
    "sec.heading(\"Welcome\")\n",
    "sec.paragraph(\"This is the first paragraph.\")\n",
    "sec.paragraph(\"This is the second paragraph.\")\n",
    "\n",
    "# Inspect the structure\n",
    "for node in store.walk():\n",
    "    indent = \"  \" * node.path.count(\".\")\n",
    "    print(f\"{indent}{node.tag}: {node.value if not isinstance(node.value, Bag) else ''}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Structure Validation\n",
    "\n",
    "The `sub_tags` parameter defines what elements can be nested inside others."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Check structure validity\nerrors = store.builder._check()\nif errors:\n    for path, node, reasons in errors:\n        print(f\"{path}: {reasons}\")\nelse:\n    print(\"Structure is valid!\")"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Try an invalid structure\ninvalid_store = BuilderBag(builder=DocBuilder)\ndoc = invalid_store.document()\ndoc.heading(\"Direct heading\")  # heading is not allowed directly in document!\n\nerrors = invalid_store.builder._check()\nfor path, node, reasons in errors:\n    print(f\"Error at {path}: {reasons}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cardinality Constraints\n",
    "\n",
    "Control how many of each element type can appear."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class FormBuilder(BagBuilderBase):\n",
    "    \"\"\"Builder with cardinality constraints.\"\"\"\n",
    "    \n",
    "    @element(sub_tags=\"header[:1], field[1:], submit[:1]\")\n",
    "    def form(self): ...\n",
    "    \n",
    "    @element()\n",
    "    def header(self): ...  # At most 1\n",
    "    \n",
    "    @element()\n",
    "    def field(self): ...   # At least 1\n",
    "    \n",
    "    @element()\n",
    "    def submit(self): ...  # At most 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Valid form\nstore = BuilderBag(builder=FormBuilder)\nform = store.form()\nform.header(\"User Registration\")\nform.field(name=\"username\", type=\"text\")\nform.field(name=\"email\", type=\"email\")\nform.submit(\"Register\")\n\nerrors = store.builder._check()\nprint(f\"Valid: {len(errors) == 0}\")"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Invalid: missing required field\nstore = BuilderBag(builder=FormBuilder)\nform = store.form()\nform.header(\"Empty Form\")\n# No fields added!\n\nerrors = store.builder._check()\nfor path, node, reasons in errors:\n    print(f\"{path}: {reasons}\")"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Attributes on Elements\n",
    "\n",
    "Pass keyword arguments to set attributes on nodes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "store = BuilderBag(builder=DocBuilder)\n",
    "doc = store.document(author=\"Alice\", version=\"1.0\")\n",
    "sec = doc.section(title=\"Chapter 1\", level=1)\n",
    "sec.paragraph(\"Content here.\", style=\"italic\")\n",
    "\n",
    "# Access attributes\n",
    "doc_node = store.get_node('document_0')\n",
    "print(f\"Author: {doc_node.attr.get('author')}\")\n",
    "print(f\"Version: {doc_node.attr.get('version')}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Built-in Builders\n",
    "\n",
    "genro-bag includes builders for common formats."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from genro_builders.builders import HtmlBuilder\n",
    "\n",
    "store = BuilderBag(builder=HtmlBuilder)\n",
    "html = store.html()\n",
    "head = html.head()\n",
    "head.title(\"My Page\")\n",
    "\n",
    "body = html.body()\n",
    "body.h1(\"Welcome\")\n",
    "body.p(\"This is a paragraph.\")\n",
    "\n",
    "div = body.div(class_=\"container\")\n",
    "div.p(\"Inside the div.\")\n",
    "\n",
    "print(\"Structure created successfully!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from genro_builders.builders import MarkdownBuilder\n",
    "\n",
    "store = BuilderBag(builder=MarkdownBuilder)\n",
    "doc = store.document()\n",
    "doc.h1(\"Title\")\n",
    "doc.paragraph(\"Some text.\")\n",
    "doc.h2(\"Subtitle\")\n",
    "doc.paragraph(\"More text.\")\n",
    "\n",
    "# Generate markdown output\n",
    "# md_text = store.builder.render()\n",
    "print(\"Markdown structure created!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": "## Key Takeaways\n\n- **Vocabulary**: Define what elements exist with `@element`\n- **Structure**: Control nesting with `sub_tags`\n- **Cardinality**: Constrain counts with `[:1]`, `[1:]`, etc.\n- **Validation**: Call `builder._check()` to verify structure\n- **Attributes**: Pass kwargs to set node attributes"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}