post_template.py
The markata.plugins.post_template
plugin handles the rendering of posts using Jinja2
templates. It provides extensive configuration options for HTML head elements, styling,
and template customization.
Installation
This plugin is built-in and enabled by default through the 'default' plugin. If you want to be explicit, you can add it to your list of plugins:
hooks = [ "markata.plugins.post_template", ]
Uninstallation
Since this plugin is included in the default plugin set, to disable it you must explicitly add it to the disabled_hooks list if you are using the 'default' plugin:
disabled_hooks = [ "markata.plugins.post_template", ]
Configuration
Head Elements
Configure HTML head elements in markata.toml
:
# Meta tags [[markata.head.meta]] name = "og:type" content = "article" [[markata.head.meta]] name = "og:author" content = "Your Name" # Links [[markata.head.link]] rel = "canonical" href = "https://example.com" # Scripts [[markata.head.script]] src = "/assets/main.js" # Raw HTML markata.head.text = ''' <style> /* Custom CSS */ </style> '''
Styling
Configure default styles:
[markata.style] color_bg = "#1f2022" color_text = "#eefbfe" color_link = "#fb30c4" color_accent = "#e1bd00c9" body_width = "800px"
Templates
Configure template settings:
[markata] # Default template post_template = "post.html" # Template directories templates_dir = "templates" dynamic_templates_dir = ".markata.cache/templates" template_cache_dir = ".markata.cache/template_bytecode" # Jinja environment options env_options = { trim_blocks = true }
Functionality
Template Rendering
The plugin:
- Loads templates from configured directories
- Compiles and caches templates for performance
- Renders posts with Jinja2 templating
- Supports template inheritance and includes
- Provides template bytecode caching
Post-Specific Overrides
Each post can override global settings:
--- template: custom.html config_overrides: head: meta: - name: og:type content: video style: color_bg: "#000000" ---
Template Context
Templates have access to:
- Post attributes
- Global configuration
- Custom filters and functions
- Markata instance
Performance Features
- Template bytecode caching
- Template compilation caching
- Configurable Jinja2 environment
- Efficient head element rendering
Dependencies
This plugin depends on:
- jinja2 for templating
- pydantic for configuration
- typer for CLI commands
Add head configuration
This snippet allows users to configure their head in markata.toml
.
{{ config.get('head', {}).pop('text') if 'text' in config.get('head',{}).keys() }} {% for tag, meta in config.get('head', {}).items() %} {% for _meta in meta %} <{{ tag }} {% for attr, value in _meta.items() %}{{ attr }}="{{ value }}"{% endfor %} /> {% endfor %} {% endfor %}
Users can specify any sort of tag in their markata.toml
[[markata.head.meta]] name = "og:type" content = "article" [[markata.head.meta]] name = "og:author" content = "Waylon Walker"
The above configuration becomes this once rendered.
<meta name='og:type' content='article' /> <meta name='og:Author' content='Waylon Walker' />
!! Note
Article variables can be used for dynamic entries like canonical_url
``` toml
[markata]
url = "markata.dev"
[[markata.head.meta]]
href="{{ config.url }}/{{ slug }}/"
rel="canonical"
```
Optionally users can also specify plain text to be appended to the head of their documents. This works well for things that involve full blocks.
[[markata.head.text]] value = ''' <script> console.log('hello world') </script> ''' [[markata.head.text]] value=''' html { font-family: "Space Mono", monospace; background: var(--color-bg); color: var(--color-text); } '''
Add scripts to head
Markata config also supports adding scripts to the head via configuration.
[[ markata.head.script ]] src = "https://cdn.tailwindcss.com"
Function
get_template function
Get a template from the cache or compile it.
get_template source
def get_template(markata, template): """Get a template from the cache or compile it.""" try: return markata.config.jinja_env.get_template(template) except jinja2.TemplateNotFound: if template.startswith("/"): template_path = Path(template) else: template_path = ( Path(__file__).parent.parent / "templates" / template ).resolve() if not template_path.exists(): template_path = Path(template) if not template_path.exists(): raise FileNotFoundError(f"Template not found: {template}") template_content = template_path.read_text() return Template(template_content, undefined=SilentUndefined)
Function
render_article function
Render an article using cached templates.
render_article source
def render_article(markata, cache, article): """Render an article using cached templates.""" key = markata.make_hash( "post_template", __version__, article.key, ) html = markata.precache.get(key) if html is not None: return html if isinstance(article.template, str): template = get_template(markata, article.template) html = render_template(markata, article, template) if isinstance(article.template, dict): html = { slug: render_template(markata, article, get_template(markata, template)) for slug, template in article.template.items() } cache.set(key, html, expire=markata.config.default_cache_expire) return html
Function
render_template function
Render a template with article context.
render_template source
def render_template(markata, article, template): """Render a template with article context.""" merged_config = markata.config # Get the body content - prefer article_html, fallback to html body = getattr(article, "article_html", None) if body is None: body = getattr(article, "html", "") context = { "post": article, "markata": markata, "config": merged_config, "body": body, } try: return template.render(**context) except Exception as e: markata.console.print(f"[red]Error rendering template for {article.path}[/]") markata.console.print(f"[red]{str(e)}[/]") raise
Function
cli function
Markata hook to implement base cli commands.
cli source
def cli(app: typer.Typer, markata: "Markata") -> None: """ Markata hook to implement base cli commands. """ templates_app = typer.Typer() app.add_typer(templates_app, name="templates") @templates_app.callback() def templates(): "template management" @templates_app.command() def show( template: str = typer.Argument(None, help="template to show"), theme: str = typer.Option(None, help="pygments syntax theme"), ) -> None: markata.console.quiet = True if template: template = get_template(markata, template) markata.console.quiet = False markata.console.print(template.filename) if theme is None or theme.lower() == "none": markata.console.print(Path(template.filename).read_text()) else: syntax = Syntax.from_path(template.filename, theme=theme) markata.console.print(syntax) return templates = markata.config.jinja_env.list_templates() markata.console.quiet = False markata.console.print("Templates directories:", style="green underline") markata_templates = Path(__file__).parents[1] / "templates" for dir in markata.config.templates_dir: if dir == markata.config.dynamic_templates_dir: markata.console.print( f"[gold3]{dir}[/][grey50] (dynamically created templates from configuration)[/] [gold3]\[markata.config.dynamic_templates_dir][/]", style="red", ) elif dir == markata_templates: markata.console.print( f"[cyan]{dir}[/][grey50] (built-in)[/]", style="red" ) else: markata.console.print( f"[orchid]{dir}[/] [orchid]\[markata.config.templates_dir][/]", style="red", ) markata.console.print() markata.console.print( "Available Templates: [white]name -> path[/]", style="green underline" ) for template in templates: source, file, uptodate = markata.config.jinja_env.loader.get_source( markata.config.jinja_env, template ) if Path(file).is_relative_to(markata.config.dynamic_templates_dir): markata.console.print( f"[gold3]{template} -> [red]{file}[/] [grey50](dynamic)[/]" ) elif Path(file).is_relative_to(markata_templates): markata.console.print( f"[cyan]{template} -> [red]{file}[/] [grey50](built-in)[/]" ) else: markata.console.print(f"[orchid]{template}[/] -> [red]{file}[/]")
Class
MarkataTemplateCache class
Template bytecode cache for improved performance.
MarkataTemplateCache source
class MarkataTemplateCache(jinja2.BytecodeCache): """Template bytecode cache for improved performance.""" def __init__(self, directory): self.directory = Path(directory) self.directory.mkdir(parents=True, exist_ok=True) def load_bytecode(self, bucket): filename = self.directory / f"{bucket.key}.cache" if filename.exists(): with open(filename, "rb") as f: bucket.bytecode_from_string(f.read()) def dump_bytecode(self, bucket): filename = self.directory / f"{bucket.key}.cache" with open(filename, "wb") as f: f.write(bucket.bytecode_to_string())
Function
configure function
Massages the configuration limitations of toml to allow a little bit easier
experience to the end user making configurations while allowing an simpler
jinja template. This enablees the use of the markata.head.text
list in
configuration.
configure source
def configure(markata: "Markata") -> None: """ Massages the configuration limitations of toml to allow a little bit easier experience to the end user making configurations while allowing an simpler jinja template. This enablees the use of the `markata.head.text` list in configuration. """
Function
pre_render function
FOR EACH POST: Massages the configuration limitations of toml/yaml to allow
a little bit easier experience to the end user making configurations while
allowing an simpler jinja template. This enables the use of the
markata.head.text
list in configuration.
pre_render source
def pre_render(markata: "Markata") -> None: """ FOR EACH POST: Massages the configuration limitations of toml/yaml to allow a little bit easier experience to the end user making configurations while allowing an simpler jinja template. This enables the use of the `markata.head.text` list in configuration. """ markata.config.dynamic_templates_dir.mkdir(parents=True, exist_ok=True) head_template = markata.config.dynamic_templates_dir / "head.html" head_template.write_text( markata.config.jinja_env.get_template("dynamic_head.html").render( {"markata": markata} ), ) for article in [a for a in markata.articles if "config_overrides" in a]: raw_text = article.get("config_overrides", {}).get("head", {}).get("text", "") if isinstance(raw_text, list): article["config_overrides"]["head"]["text"] = "\n".join( flatten([t.values() for t in raw_text]), )
Function
templates function
template management
templates source
def templates(): "template management"