Modular, Markdown-based documentation generator that makes pdf, docx, html, and more.
—
Foliant's backend system provides pluggable output generation for multiple document formats. Backends orchestrate preprocessors and handle the final transformation from processed Markdown to the desired output format (PDF, HTML, DOCX, etc.).
Foundation class for all output generation backends providing preprocessor orchestration and common functionality.
class BaseBackend:
"""Base backend class that all backends must inherit from."""
targets: tuple = ()
required_preprocessors_before: tuple = ()
required_preprocessors_after: tuple = ()
def __init__(self, context: dict, logger: Logger, quiet=False, debug=False):
"""
Initialize backend with build context.
Parameters:
- context (dict): Build context containing project_path, config, target, backend
- logger (Logger): Logger instance for build messages
- quiet (bool): Suppress output messages
- debug (bool): Enable debug logging
"""
def get_slug(self) -> str:
"""
Generate project slug from title, version, and date.
Used for output file/directory naming.
Returns:
str: Generated slug (title-version-date format)
"""
def apply_preprocessor(self, preprocessor):
"""
Apply single preprocessor to working directory content.
Parameters:
- preprocessor (str or dict): Preprocessor name or config dict
Raises:
ModuleNotFoundError: If preprocessor not installed
RuntimeError: If preprocessor application fails
"""
def preprocess_and_make(self, target: str) -> str:
"""
Apply all required preprocessors then generate output.
Main entry point for backend execution.
Parameters:
- target (str): Output format (pdf, html, docx, etc.)
Returns:
str: Path to generated output
"""
def make(self, target: str) -> str:
"""
Generate output from preprocessed source.
Must be implemented by each backend.
Parameters:
- target (str): Output format
Returns:
str: Path to generated output
Raises:
NotImplementedError: If not implemented by subclass
"""Built-in backend that applies preprocessors and returns preprocessed source without further transformation.
class Backend(BaseBackend):
"""
Preprocessor-only backend that applies preprocessors
and returns processed source without further transformation.
"""
targets = ('pre',)
def __init__(self, *args, **kwargs):
"""
Initialize preprocessor backend with configuration.
Parameters:
- *args: Arguments passed to BaseBackend
- **kwargs: Keyword arguments passed to BaseBackend
"""
def make(self, target: str) -> str:
"""
Copy preprocessed content to output directory.
Uses slug from backend config or generates default slug.
Parameters:
- target (str): Must be 'pre'
Returns:
str: Path to preprocessed content directory (ends with .pre)
"""# Context dictionary passed to backends
Context = {
'project_path': Path, # Path to project directory
'config': dict, # Parsed configuration
'target': str, # Target format (html, pdf, etc.)
'backend': str # Backend name
}from foliant.backends.base import BaseBackend
from pathlib import Path
import subprocess
class CustomBackend(BaseBackend):
"""Custom backend for special output format."""
targets = ('custom', 'special')
required_preprocessors_before = ('includes',)
required_preprocessors_after = ('_unescape',)
def make(self, target: str) -> str:
"""Generate custom format output."""
output_path = f"{self.get_slug()}.{target}"
# Custom processing logic
self.logger.info(f"Generating {target} format")
# Example: run external tool
subprocess.run([
'custom-tool',
'--input', str(self.working_dir),
'--output', output_path,
'--format', target
], check=True)
return output_pathfrom foliant.backends.base import BaseBackend
from foliant.config import Parser
from pathlib import Path
import logging
# Set up context
project_path = Path('./my-project')
config = Parser(project_path, 'foliant.yml', logging.getLogger()).parse()
context = {
'project_path': project_path,
'config': config,
'target': 'html',
'backend': 'mkdocs'
}
# Import and use backend
from foliant.backends.mkdocs import Backend
backend = Backend(
context=context,
logger=logging.getLogger(),
quiet=False,
debug=True
)
# Generate output
result = backend.preprocess_and_make('html')
print(f"Documentation generated at: {result}")from foliant.backends.base import BaseBackend
class ExampleBackend(BaseBackend):
targets = ('example',)
def make(self, target: str) -> str:
# Apply individual preprocessors
self.apply_preprocessor('includes')
self.apply_preprocessor({
'plantuml': {
'format': 'png',
'server_url': 'http://localhost:8080'
}
})
# Generate output
output_file = f"{self.get_slug()}.{target}"
# ... backend-specific generation logic
return output_filefrom foliant.utils import get_available_backends
# Get all available backends
backends = get_available_backends()
print("Available backends:")
for name, targets in backends.items():
print(f" {name}: {', '.join(targets)}")
# Check if backend supports target
def validate_backend(backend_name: str, target: str) -> bool:
backends = get_available_backends()
if backend_name not in backends:
return False
return target in backends[backend_name]
# Usage
if validate_backend('pandoc', 'pdf'):
print("Pandoc can generate PDF")
if not validate_backend('mkdocs', 'pdf'):
print("MkDocs cannot generate PDF")class DocumentationBackend(BaseBackend):
"""Backend with specific preprocessor requirements."""
targets = ('docs',)
# These run before config preprocessors
required_preprocessors_before = ('metadata', 'includes')
# These run after config preprocessors
required_preprocessors_after = ('links', '_unescape')
def make(self, target: str) -> str:
# Preprocessor execution order:
# 1. required_preprocessors_before
# 2. config['preprocessors']
# 3. required_preprocessors_after
output_dir = Path(f"{self.get_slug()}_docs")
# Generate documentation
return str(output_dir)Backends can access configuration through self.config:
class ConfigurableBackend(BaseBackend):
def make(self, target: str) -> str:
# Access backend-specific config
backend_config = self.config.get('backend_config', {})
my_config = backend_config.get('my_backend', {})
# Use configuration
theme = my_config.get('theme', 'default')
custom_css = my_config.get('custom_css', [])
# Apply configuration in output generation
return self.generate_with_config(theme, custom_css)Example foliant.yml backend configuration:
title: My Project
backend_config:
my_backend:
theme: material
custom_css:
- assets/style.css
- assets/custom.css
options:
minify: trueInstall with Tessl CLI
npx tessl i tessl/pypi-foliant