Skip to content

Using markmeld as a library

While markmeld is primarily designed as a command-line tool, it can also be used as a Python library for programmatic document generation. This is particularly useful when you need to:

  • Generate documents dynamically from application data
  • Integrate markmeld into larger Python applications
  • Build documents without creating intermediate files
  • Programmatically create and modify configurations

Basic usage

The core class for library usage is MarkdownMelder. Here's a simple example:

import yaml
from markmeld import MarkdownMelder, load_config_file

# Load configuration from a file
cfg = load_config_file("_markmeld.yaml")

# Create a MarkdownMelder instance
mm = MarkdownMelder(cfg)

# Build a target (render only, don't run command)
result = mm.build_target("my_target", print_only=True)

# Access the rendered output
print(result.melded_output)

Direct content input (no files needed)

Starting with version 0.5.0, markmeld supports providing content directly without creating files on disk. This is achieved through two new configuration keys: md_content and yaml_content.

Example: Markdown content from strings

import yaml
from markmeld import MarkdownMelder

# Create configuration programmatically
config = {
    "_cfg_file_path": "/tmp/temp.yaml",  # Required for path resolution
    "targets": {
        "letter": {
            "_workpath": "/tmp",  # Required working directory
            "_defpath": "/tmp",   # Required definition path
            "data": {
                "md_content": {
                    "body": "Dear {{name}},\n\nThank you for your purchase of {{product}}.\n\nBest regards,\nThe Team"
                },
                "yaml_content": {
                    "customer": {
                        "name": "John Doe",
                        "product": "Premium Widget"
                    }
                }
            },
            "jinja_template": "letter_template.jinja",
            "command": None  # Just render, don't execute
        }
    }
}

# Create melder and build
mm = MarkdownMelder(config)
result = mm.build_target("letter", print_only=True)
print(result.melded_output)

Example: Markdown with frontmatter

You can provide markdown content with frontmatter metadata:

from markmeld import MarkdownMelder

config = {
    "_cfg_file_path": "/tmp/temp.yaml",
    "targets": {
        "blog_post": {
            "_workpath": "/tmp",
            "_defpath": "/tmp",
            "data": {
                "md_content": {
                    "post": {
                        "content": "# My Blog Post\n\nThis is the content.",
                        "frontmatter": {
                            "title": "Amazing Post",
                            "author": "Jane Smith",
                            "date": "2024-01-15",
                            "tags": ["python", "documentation"]
                        }
                    }
                }
            },
            "jinja_template": "blog_template.jinja",
            "command": None
        }
    }
}

mm = MarkdownMelder(config)
result = mm.build_target("blog_post", print_only=True)

# Frontmatter is available in _global_frontmatter
print(result.melded_input['_global_frontmatter']['dict']['title'])  # "Amazing Post"

Example: Using python-frontmatter objects

If you're already working with the python-frontmatter library, you can pass Post objects directly:

import frontmatter
from markmeld import MarkdownMelder

# Create a frontmatter Post object
post = frontmatter.Post("# Document Title\n\nDocument content here.")
post.metadata = {
    "author": "John Doe",
    "version": "1.0",
    "date": "2024-01-01"
}

config = {
    "_cfg_file_path": "/tmp/temp.yaml",
    "targets": {
        "document": {
            "_workpath": "/tmp",
            "_defpath": "/tmp",
            "data": {
                "md_content": {
                    "doc": post  # Pass the Post object directly
                }
            },
            "jinja_template": "doc_template.jinja",
            "command": None
        }
    }
}

mm = MarkdownMelder(config)
result = mm.build_target("document", print_only=True)

Programmatic configuration building

You can build configurations programmatically for dynamic document generation:

from markmeld import MarkdownMelder
import json

def generate_report(data_dict, template_path):
    """Generate a report from a data dictionary"""

    config = {
        "_cfg_file_path": "dynamic_config.yaml",
        "targets": {
            "report": {
                "_workpath": ".",
                "_defpath": ".",
                "data": {
                    "yaml_content": {
                        "report_data": data_dict
                    },
                    "md_content": {
                        "summary": f"# Report for {data_dict.get('client_name', 'Unknown')}\n\nGenerated on {data_dict.get('date', 'today')}"
                    }
                },
                "jinja_template": template_path,
                "command": "pandoc --output report.pdf"
            }
        }
    }

    mm = MarkdownMelder(config)
    return mm.build_target("report")

# Use the function
report_data = {
    "client_name": "Acme Corp",
    "date": "2024-01-15",
    "metrics": {
        "revenue": 1000000,
        "growth": "15%"
    }
}

result = generate_report(report_data, "templates/report.jinja")

Combining file and content sources

You can mix file-based and content-based data sources:

config = {
    "_cfg_file_path": "config.yaml",
    "targets": {
        "combined": {
            "_workpath": ".",
            "_defpath": ".",
            "data": {
                # From files
                "md_files": {
                    "header": "templates/header.md",
                    "footer": "templates/footer.md"
                },
                "yaml_files": {
                    "config": "config/settings.yaml"
                },
                # From memory
                "md_content": {
                    "dynamic_section": generated_markdown_content
                },
                "yaml_content": {
                    "runtime_data": {
                        "timestamp": datetime.now().isoformat(),
                        "user": os.getenv("USER")
                    }
                }
            },
            "jinja_template": "combined_template.jinja",
            "command": None
        }
    }
}

Working with melded output

The build_target method returns a Target object with useful attributes:

result = mm.build_target("my_target", print_only=True)

# Access the rendered output
print(result.melded_output)

# Access the input data that was passed to the template
print(result.melded_input.keys())

# Check the return code (0 for success)
print(result.returncode)

# Access target metadata
print(result.meta)

Using with loops

You can use markmeld's loop functionality programmatically:

config = {
    "_cfg_file_path": "config.yaml",
    "targets": {
        "mail_merge": {
            "_workpath": ".",
            "_defpath": ".",
            "loop": {
                "loop_data": "recipients",
                "assign_to": "recipient"
            },
            "data": {
                "yaml_content": {
                    "recipients": [
                        {"name": "Alice", "email": "[email protected]"},
                        {"name": "Bob", "email": "[email protected]"},
                        {"name": "Charlie", "email": "[email protected]"}
                    ]
                },
                "md_content": {
                    "template": "Dear {{recipient.name}},\n\nYour email is {{recipient.email}}."
                }
            },
            "jinja_template": "email.jinja",
            "output_file": "emails/{{recipient.name}}.txt",
            "command": None
        }
    }
}

mm = MarkdownMelder(config)
results = mm.build_target("mail_merge", print_only=True)

# Results will be a dict with one entry per loop iteration
for idx, result in results.items():
    print(f"Email {idx}: {result.melded_output}")