Template Parameters =================== So far every value in the template has come from the ``calendar`` object or been hard-coded in HTML. Template parameters let you pull values like colors, titles and toggle flags out of the template and into a small XML file. Users can then customize the planner from the command line without editing the template source. **Key topics** * Declaring parameters in ``params.xml``. * Supported types and default values. * Namespaces for grouping related parameters. * Using parameters in the template. * Overriding parameters from the command line with ``-D``. * Live preview with parameters. Why use parameters? ------------------- Consider the off-day color in the Demo Planner. Without parameters you would hard-code it: .. code-block:: html+jinja %% macro day_style(day) {% if day.is_off_day %}color: #C00000{% endif %} %% endmacro If a user wants red instead of dark red they have to open the template, find the color and change it. With parameters you declare the color in ``params.xml``, use ``{{ params.day_off_color }}`` in the template, and the user overrides it on the command line. The ``params.xml`` file ----------------------- Create a file called ``params.xml`` in the same directory as the template: .. code-block:: text planners/ demo/ demo.html params.xml <-- new assets/ ... The file uses XML. If you are comfortable with HTML you already know the syntax. Here is a minimal example: .. code-block:: xml #4A90D9 The root element is always ````. Each child element declares one parameter. The element name becomes the parameter name. The element's text content is the default value. The optional ``help`` attribute is a human-readable description. For values that contain XML special characters (``<``, ``>``, ``&``) wrap the text in ````: .. code-block:: xml ]]> Supported types --------------- Every parameter has a type. When omitted the type defaults to ``str``. .. list-table:: :header-rows: 1 :widths: 15 15 30 30 * - ``type=`` - Python type - Parses from string via - Example * - ``str`` - string - used as-is - ``#4A90D9`` * - ``int`` - integer - ``int(value)`` - ``2026`` * - ``float`` - float - ``float(value)`` - ``1.5`` * - ``bool`` - boolean - see below - ``true``, ``yes``, ``on``, ``1`` ``str`` is the default. This means you can write: .. code-block:: xml #4A90D9 instead of the longer: .. code-block:: xml #4A90D9 Both are equivalent. Bool values ~~~~~~~~~~~ Bool parameters accept the following values (case-insensitive): .. list-table:: :header-rows: 1 :widths: 50 50 * - Truthy - Falsy * - ``true``, ``yes``, ``y``, ``on``, ``1`` - ``false``, ``no``, ``n``, ``off``, ``0`` Default values ~~~~~~~~~~~~~~ If the text content is empty or absent, the parameter defaults to ``None``. This is useful for optional values that the user may or may not provide: .. code-block:: xml In the template use a conditional to check: .. code-block:: html+jinja %% if params.subtitle

{{ params.subtitle }}

%% endif Namespaces ---------- Group related parameters by nesting elements. A parent element with child elements and no ``type`` attribute becomes a namespace. The optional ``help`` attribute documents the namespace: .. code-block:: xml 2026 #4A90D9 #C00000 #FFFFFF My Planner In the template access nested parameters with dot notation: .. code-block:: html+jinja

{{ params.cover.title }}

...
Namespaces can be nested as deep as you need. Naming rules ~~~~~~~~~~~~ Parameter names must be valid Python identifiers: letters, digits and underscores. Hyphens are **not** allowed because Jinja2 would interpret ``params.off-day`` as subtraction (``params.off`` minus ``day``). Use underscores: ````, not ````. Element rules summary ~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 30 30 30 * - Has ``type`` or ``help`` - Has child elements - Result * - Yes - No - Leaf parameter * - No - Yes - Namespace (``help`` allowed) * - ``type`` - Yes - Error (ambiguous) * - No - No - Error (empty) Using parameters in templates ----------------------------- pyplanner injects a ``params`` variable into every template. When a ``params.xml`` file exists next to the template, ``params`` is a namespace built from the declared defaults. When no file exists ``params`` is an empty namespace and existing templates keep working unchanged. Access parameters with ``{{ params.name }}``: .. code-block:: html+jinja %% set year = calendar.year(params.year) %% macro day_style(day) {% if day.is_off_day %}color: {{ params.day_off_color }}{% endif %} %% endmacro

{{ params.title }}

Full example ~~~~~~~~~~~~ ``params.xml``: .. code-block:: xml 2026 #C00000 Template: .. code-block:: html+jinja %% set year = calendar.year(params.year) %% macro day_style(day) {% if day.is_off_day %}color: {{ params.day_off_color }}{% endif %} %% endmacro ... %% for day in calendar.weekdays {{ day.letter }} %% endfor Overriding parameters from the command line -------------------------------------------- Use ``-D`` (or ``--define``) to override one or more parameters when running pyplanner: .. code-block:: bash pyplanner planners/demo -D day_off_color=#E74C3C Multiple parameters in one flag: .. code-block:: bash pyplanner planners/demo -D year=2027 day_off_color=#FF0000 Repeated ``-D`` flags work too: .. code-block:: bash pyplanner planners/demo -D year=2027 -D day_off_color=#FF0000 Use dot notation for nested parameters: .. code-block:: bash pyplanner planners/demo -D colors.accent=#000000 pyplanner validates every ``-D`` override: * Unknown keys are rejected (catches typos like ``-D acent=#FFF``). * Values that do not match the declared type are rejected (for example ``-D year=abc`` when ``year`` is declared as ``int``). Live preview with parameters ----------------------------- Parameters work with ``--watch``. pyplanner reloads ``params.xml`` on every file change, so you can edit the XML, save, and see the result in the browser immediately: .. code-block:: bash pyplanner planners/demo --watch -D day_off_color=#FF0000 Any ``-D`` overrides you pass on the command line are re-applied after each reload. Update the Demo Planner ----------------------- Create ``planners/demo/params.xml`` with three parameters - the planner year, the month and the off-day color that was previously hard-coded in the ``day_color`` macro: .. code-block:: xml 2026 1 #C00000 Open ``planners/demo/demo.html`` and replace the hard-coded year, month and color with parameter references. After this page your template should look like this: .. code-block:: html+jinja %% set year = calendar.year(params.year) %% set month = year.months[params.month - 1] %% macro day_color(day) {% if day.is_off_day %}{{ params.day_off_color }}{% else %}#000000{% endif %} %% endmacro %% macro month_table(month) %% for week in month.table %% for day in week %% if day is not none {{ day }} %% else %% endif %% endfor %% endfor %% endmacro ## -- Cover page --

Demo Planner - {{ month }} {{ year }}

## -- Month calendar --

{{ month }}

{{ year }}

%% for wd in calendar.weekdays %% endfor {{ month_table(month) }}
{{ wd.short_name }}
Three lines changed compared to the previous version: 1. ``calendar.year(2026)`` became ``calendar.year(params.year)`` - the year is now configurable. 2. ``year.months[0]`` became ``year.months[params.month - 1]`` - the month is now configurable. We subtract 1 because the list is zero-indexed but the parameter uses human-friendly numbering (1 = January). 3. ``#C00000`` in ``day_color`` became ``{{ params.day_off_color }}`` - the color is now configurable. Regenerate with the defaults:: pyplanner planners/demo The output looks exactly the same as before. Now try overriding all three parameters:: pyplanner planners/demo -D year=2027 month=6 day_off_color=#0000CC The cover now reads "Demo Planner - June 2027" and weekends appear in blue instead of dark red. The template itself did not change - only the command-line arguments did. What is next ------------ Continue to :doc:`assets-and-styling` to learn how to manage stylesheets, images and fonts in your template.