Terrace/packages/python/docs/recipes.inc.tce
Joshua Bemenderfer 9d9757e868 Updates.
2025-09-08 16:24:38 -04:00

265 lines
8.8 KiB
Plaintext

Heading 2 Recipes
class mt-12
Markdown
Common patterns and recipes for working with Terrace documents in Python.
Heading 3 Configuration File Parser
class mt-8 mb-4
Markdown
Parse a hierarchical configuration file with sections and key-value pairs.
CodeBlock python
from terrace import use_document, create_file_reader
from typing import Dict, Any
def parse_config_file(file_path: str) -> Dict[str, Any]:
"""Parse a Terrace configuration file into a nested dictionary"""
reader = create_file_reader(file_path)
doc = use_document(reader)
config = {}
stack = [config]
for node in doc:
# Adjust stack to current level
while len(stack) > node.level + 1:
stack.pop()
current_dict = stack[-1]
if node.tail: # Key-value pair
current_dict[node.head] = node.tail
else: # Section header
current_dict[node.head] = {}
stack.append(current_dict[node.head])
return config
# Usage
config = parse_config_file('app.tce')
print(config['database']['host'])
Heading 3 Document Outline Generator
class mt-8 mb-4
Markdown
Extract a document outline based on heading levels.
CodeBlock python
from terrace import use_document, create_string_reader
from dataclasses import dataclass
from typing import List
@dataclass
class Heading:
level: int
title: str
children: List['Heading']
def extract_outline(content: str) -> List[Heading]:
"""Extract document outline from Terrace content"""
reader = create_string_reader(content)
doc = use_document(reader)
headings = []
stack = []
for node in doc:
if node.is_('heading') or node.is_('h1') or node.is_('h2') or node.is_('h3'):
heading = Heading(level=node.level, title=node.tail, children=[])
# Find the right parent in the stack
while stack and stack[-1].level >= heading.level:
stack.pop()
if stack:
stack[-1].children.append(heading)
else:
headings.append(heading)
stack.append(heading)
return headings
Heading 3 Template Engine
class mt-8 mb-4
Markdown
Simple template engine that processes Terrace templates with variables.
CodeBlock python
from terrace import use_document, create_string_reader
from typing import Dict, Any
import re
def render_template(template: str, variables: Dict[str, Any]) -> str:
"""Render a Terrace template with variable substitution"""
reader = create_string_reader(template)
doc = use_document(reader)
output = []
indent_str = " " # Two spaces per level
for node in doc:
# Apply indentation
indentation = indent_str * node.level
if node.is_('var'):
# Variable substitution: var name -> value
var_name = node.tail
value = variables.get(var_name, f"{{undefined: {var_name}}}")
output.append(f"{indentation}{value}")
elif node.is_('if'):
# Conditional rendering: if variable_name
condition = node.tail
if variables.get(condition):
# Process children if condition is truthy
for child in node.children():
child_line = f"{indent_str * child.level}{child.content}"
# Substitute variables in child content
child_line = re.sub(r'\{\{(\w+)\}\}',
lambda m: str(variables.get(m.group(1), m.group(0))),
child_line)
output.append(child_line)
elif node.is_('loop'):
# Loop over array: loop items
array_name = node.tail
items = variables.get(array_name, [])
for item in items:
for child in node.children():
child_line = f"{indent_str * child.level}{child.content}"
# Make item available as 'item' variable
temp_vars = {**variables, 'item': item}
child_line = re.sub(r'\{\{(\w+)\}\}',
lambda m: str(temp_vars.get(m.group(1), m.group(0))),
child_line)
output.append(child_line)
else:
# Regular content with variable substitution
content = node.content
content = re.sub(r'\{\{(\w+)\}\}',
lambda m: str(variables.get(m.group(1), m.group(0))),
content)
output.append(f"{indentation}{content}")
return '\n'.join(output)
# Usage
template = """
title {{page_title}}
if show_author
author {{author_name}}
content
loop articles
article {{item.title}}
summary {{item.summary}}
"""
variables = {
'page_title': 'My Blog',
'show_author': True,
'author_name': 'John Doe',
'articles': [
{'title': 'First Post', 'summary': 'This is the first post'},
{'title': 'Second Post', 'summary': 'This is the second post'}
]
}
result = render_template(template, variables)
print(result)
Heading 3 Data Validation
class mt-8 mb-4
Markdown
Validate Terrace document structure against a schema.
CodeBlock python
from terrace import use_document, create_string_reader
from typing import Dict, List, Set, Optional
from dataclasses import dataclass
@dataclass
class ValidationError:
line_number: int
message: str
class TerraceValidator:
def __init__(self):
self.required_fields: Dict[str, Set[str]] = {}
self.allowed_fields: Dict[str, Set[str]] = {}
self.field_types: Dict[str, type] = {}
def require_fields(self, context: str, fields: List[str]):
"""Require specific fields in a context"""
self.required_fields[context] = set(fields)
def allow_fields(self, context: str, fields: List[str]):
"""Allow specific fields in a context"""
self.allowed_fields[context] = set(fields)
def validate(self, content: str) -> List[ValidationError]:
"""Validate content and return list of errors"""
reader = create_string_reader(content)
doc = use_document(reader)
errors = []
context_stack = ['root']
found_fields = {'root': set()}
for node in doc:
# Update context stack based on indentation
target_depth = node.level + 1
while len(context_stack) > target_depth:
# Check required fields when leaving context
leaving_context = context_stack.pop()
required = self.required_fields.get(leaving_context, set())
found = found_fields.get(leaving_context, set())
missing = required - found
if missing:
errors.append(ValidationError(
node.line_number,
f"Missing required fields in {leaving_context}: {', '.join(missing)}"
))
found_fields.pop(leaving_context, None)
current_context = context_stack[-1]
# Check if field is allowed
allowed = self.allowed_fields.get(current_context, None)
if allowed is not None and node.head not in allowed:
errors.append(ValidationError(
node.line_number,
f"Field '{node.head}' not allowed in context '{current_context}'"
))
# Track found fields
found_fields.setdefault(current_context, set()).add(node.head)
# If this node has children, it becomes a new context
if any(True for _ in node.children()): # Check if has children
context_stack.append(node.head)
found_fields[node.head] = set()
return errors
# Usage
validator = TerraceValidator()
validator.require_fields('root', ['title', 'content'])
validator.allow_fields('root', ['title', 'author', 'date', 'content'])
validator.allow_fields('content', ['section', 'paragraph'])
content = """
title My Document
content
section Introduction
paragraph Hello world
"""
errors = validator.validate(content)
for error in errors:
print(f"Line {error.line_number}: {error.message}")