User Guide
This guide covers the core concepts and features of ViewText in detail.
Field Registry
The Field Registry is the foundation of ViewText. It maps field names to getter functions that extract values from context dictionaries.
Creating a Registry
from viewtext import BaseFieldRegistry
registry = BaseFieldRegistry()
Registering Fields
Fields are registered with a name and a callable that takes a context dictionary:
# Simple field getter
registry.register("username", lambda ctx: ctx["user"]["name"])
# More complex getter with default values
registry.register("status", lambda ctx: ctx.get("status", "offline"))
# Computed fields
registry.register("full_name", lambda ctx: f"{ctx['first']} {ctx['last']}")
Checking for Fields
if registry.has_field("username"):
getter = registry.get("username")
value = getter(context)
Formatter System
Formatters transform raw values into formatted strings for display. ViewText includes several built-in formatters and supports custom formatters.
ViewText provides built-in formatters for:
text - Basic text with prefix/suffix
text_uppercase - Convert to uppercase
number - Format numbers with separators and decimals
price - Currency formatting with symbols
datetime - Format timestamps and dates
relative_time - Human-readable time differences (e.g., “5m ago”)
template - Combine multiple fields with Python format specifications
Quick Example
[[layouts.demo.lines]]
field = "price"
index = 0
formatter = "price"
[layouts.demo.lines.formatter_params]
symbol = "$"
decimals = 2
thousands_sep = ","
Input: 1234.50 → Output: "$1,234.50"
For complete documentation of all formatters, parameters, and examples, see Formatters Reference.
Custom Formatters
You can register custom formatters with the FormatterRegistry:
from viewtext import get_formatter_registry
def format_percentage(value, **kwargs):
decimals = kwargs.get("decimals", 1)
return f"{value:.{decimals}f}%"
formatter_registry = get_formatter_registry()
formatter_registry.register("percentage", format_percentage)
Layout Configuration
Layouts are defined in TOML files and specify how fields map to output. ViewText supports two types of layouts: line-based layouts and dictionary-based layouts.
Line-Based Layouts
Line-based layouts map fields to numbered line positions (indices). This is useful for fixed-position text displays like terminal dashboards or e-ink displays.
[layouts.my_layout]
name = "My Layout"
[[layouts.my_layout.lines]]
field = "field_name"
index = 0
formatter = "text"
[layouts.my_layout.lines.formatter_params]
prefix = "Label: "
The build_line_str() method returns a list of strings, one per line:
lines = engine.build_line_str(layout, context)
# Returns: ["Label: value", "Line 2", ...]
Dictionary-Based Layouts
Dictionary-based layouts map fields to named keys, producing key-value pairs. This is useful for JSON APIs, configuration files, or structured data output.
[layouts.my_dict_layout]
name = "My Dictionary Layout"
[[layouts.my_dict_layout.items]]
key = "display_name"
field = "field_name"
formatter = "text"
[layouts.my_dict_layout.items.formatter_params]
prefix = "Label: "
[[layouts.my_dict_layout.items]]
key = "temperature"
field = "temp"
formatter = "number"
[layouts.my_dict_layout.items.formatter_params]
decimals = 1
suffix = "°"
The build_dict_str() method returns a dictionary with formatted string values:
result = engine.build_dict_str(layout, context)
# Returns: {"display_name": "Label: value", "temperature": "72.5°"}
When to Use Each Type
Line-based layouts: Terminal UIs, e-ink displays, fixed-position text grids
Dictionary-based layouts: JSON APIs, key-value stores, structured data export
CLI Support
The CLI automatically detects layout type:
# List shows layout type
viewtext list
# Render outputs key:value pairs for dict layouts
echo '{"temp": 72.5}' | viewtext render weather_dict
# JSON output returns dict for dict layouts, array for line layouts
echo '{"temp": 72.5}' | viewtext render weather_dict --json
Multiple Layouts
A single TOML file can contain multiple layouts:
[layouts.compact]
name = "Compact View"
# ... lines ...
[layouts.detailed]
name = "Detailed View"
# ... lines ...
Formatter Parameters
Each line can have formatter-specific parameters:
[[layouts.demo.lines]]
field = "price"
index = 0
formatter = "price"
[layouts.demo.lines.formatter_params]
symbol = "$"
decimals = 2
thousands_sep = ","
symbol_position = "prefix"
Formatter Presets
Define reusable formatter configurations (presets) to promote consistency and reduce duplication:
[formatters.usd_price]
type = "price"
symbol = "$"
decimals = 2
thousands_sep = ","
[formatters.time_only]
type = "datetime"
format = "%H:%M"
Presets can be referenced by name directly in layouts:
[layouts.product]
name = "Product Display"
[[layouts.product.lines]]
field = "price"
index = 0
formatter = "usd_price" # References preset
[[layouts.product.lines]]
field = "created_at"
index = 1
formatter = "time_only" # References preset
Presets can also be used in template formatter field_formatters:
[[layouts.crypto.lines]]
field = "ticker"
index = 0
formatter = "template"
[layouts.crypto.lines.formatter_params]
template = "{symbol}: {price}"
fields = ["symbol", "price"]
field_formatters = { "price": "usd_price" }
For more details on formatter presets, see Formatters Reference.
Layout Engine
The Layout Engine combines field registries, formatters, and layout configurations to generate formatted output.
Creating an Engine
from viewtext import LayoutEngine
# Without field registry (uses context directly)
engine = LayoutEngine()
# With field registry
engine = LayoutEngine(field_registry=registry)
Building Output
ViewText provides two methods for building output, depending on your layout type:
Line-Based Layouts
context = {
"temp": 72.5,
"humidity": 65,
"city": "San Francisco"
}
lines = engine.build_line_str(layout, context)
# lines is a list of strings, one per line
for i, line in enumerate(lines):
print(f"Line {i}: {line}")
Dictionary-Based Layouts
context = {
"temp": 72.5,
"price": 19.99,
"message": "Hello"
}
result = engine.build_dict_str(layout, context)
# result is a dict with string values
print(result["temp"]) # "72.5°"
print(result["price"]) # "$19.99"
print(result["message"]) # "Hello"
# Export as JSON
import json
print(json.dumps(result, indent=2))
Field Resolution
The engine resolves fields in this order:
Check field registry (if provided)
Check context dictionary directly
Return None if not found
This allows mixing registered fields with direct context values.
For detailed information on field types and definitions, see Fields Reference.
Field Validation
ViewText provides comprehensive field validation to ensure data quality and type safety. Validation rules are defined declaratively in TOML configuration files.
Quick Example
[fields.user_age]
context_key = "age"
type = "int"
min_value = 0
max_value = 120
on_validation_error = "use_default"
default = 0
This field definition ensures that user_age is:
An integer value
Between 0 and 120
Falls back to 0 if validation fails
Validation Features
ViewText supports:
Type Checking: Ensure values are the correct type (str, int, float, bool, list, dict)
Constraint Validation: Enforce numeric ranges, string lengths, patterns, and allowed values
Error Handling: Control what happens when validation fails (use_default, raise, skip, coerce)
Type Coercion: Automatically convert compatible types
For complete documentation of validation parameters, error handling strategies, and examples, see Field Validation Reference.
Computed Fields
Computed fields allow you to perform calculations on source data without writing Python code. All operations are defined in TOML configuration files and are compiled at load time.
Available Operations
Temperature Conversions
celsius_to_fahrenheit- Convert Celsius to Fahrenheitfahrenheit_to_celsius- Convert Fahrenheit to Celsius
Arithmetic Operations
multiply- Multiply two or more valuesdivide- Divide two values (safe with divide-by-zero handling)add- Sum multiple valuessubtract- Subtract two valuesmodulo- Modulo operation (remainder after division)
Aggregate Operations
average- Calculate average of multiple valuesmin- Find minimum of multiple valuesmax- Find maximum of multiple values
Mathematical Operations
abs- Absolute valueround- Round to nearest integer (optionally specify decimals)ceil- Round up to nearest integerfloor- Round down to nearest integerlinear_transform- Apply formula:(value * multiply / divide) + add
String Operations
concat- Join multiple strings with a separatorsplit- Split a string by separator and take a specific indexsubstring- Extract substring from start to end position
Conditional Operations
conditional- Return different values based on field equality condition (condition,if_true,if_false)
Defining Computed Fields
Computed fields are defined in the [fields] section of your TOML configuration:
# Temperature conversion
[fields.temp_f]
operation = "celsius_to_fahrenheit"
sources = ["temp_c"]
default = 0.0
# Price calculation
[fields.total_price]
operation = "multiply"
sources = ["price", "quantity"]
default = 0.0
# Discount calculation
[fields.discount_price]
operation = "linear_transform"
sources = ["price"]
multiply = 0.8
default = 0.0
# Average score
[fields.average_score]
operation = "average"
sources = ["score1", "score2", "score3"]
default = 0.0
Operation Parameters
Each computed field requires:
operation- Name of the operation to performsources- List of field names from context to use as inputsdefault- Value to return if operation fails or sources are missing
Some operations support additional parameters:
Linear Transform Parameters
multiply- Multiplier for the value (default: 1)divide- Divisor for the value (default: 1)add- Addend for the value (default: 0)
Formula: (value * multiply / divide) + add
# Convert km/h to mph
[fields.speed_mph]
operation = "linear_transform"
sources = ["speed_kmh"]
multiply = 0.621371
default = 0.0
# Apply 20% discount and add $5 handling fee
[fields.discounted_price]
operation = "linear_transform"
sources = ["price"]
multiply = 0.8
add = 5.0
default = 0.0
Round Operations
# Scale mempool size and round up
[fields.vsize_scaled]
operation = "linear_transform"
context_key = "mempool.vsize"
divide = 1000000
default = 0
[fields.vsize_mb]
operation = "ceil"
sources = ["vsize_scaled"]
default = 0
String Operations Parameters
separator- Separator for concat/split operations (default: empty string for concat, space for split)index- Index for split operation (which part to extract)start- Start position for substring operationend- End position for substring operation (optional)
# Concatenate first and last name
[fields.full_name]
operation = "concat"
sources = ["first_name", "last_name"]
separator = " "
default = ""
# Extract domain from email
[fields.domain]
operation = "split"
sources = ["email"]
separator = "@"
index = 1
default = ""
# Extract year from date string
[fields.year]
operation = "substring"
sources = ["date"]
start = 0
end = 4
default = ""
# Get last 3 characters
[fields.suffix]
operation = "substring"
sources = ["text"]
start = -3
default = ""
Modulo Operation
# Check if number is even/odd (result will be 0 or 1)
[fields.remainder]
operation = "modulo"
sources = ["number", "divisor"]
default = 0
Conditional Operations
# Display price with currency formatting
[fields.price_display]
operation = "conditional"
condition = { field = "currency", equals = "USD" }
if_true = "$~amount~"
if_false = "~amount~ ~currency~"
default = ""
The ~field_name~ syntax in if_true and if_false allows embedding other field values.
Error Handling
Computed fields include automatic error handling:
Missing source values return the default
Non-numeric values return the default (for numeric operations)
Division by zero returns the default
Modulo by zero returns the default
Out-of-bounds string indices return the default
Invalid operations raise
ValueErrorat configuration load time
Example Use Cases
Unit Conversions
[fields.temp_f]
operation = "celsius_to_fahrenheit"
sources = ["temp_c"]
default = 0.0
[fields.meters_to_feet]
operation = "linear_transform"
sources = ["meters"]
multiply = 3.28084
default = 0.0
E-commerce Calculations
# Line item total
[fields.line_total]
operation = "multiply"
sources = ["price", "quantity"]
default = 0.0
# Discounted price
[fields.sale_price]
operation = "linear_transform"
sources = ["price"]
multiply = 0.85
default = 0.0
Data Aggregation
# Daily temperature range
[fields.temp_min]
operation = "min"
sources = ["temp_morning", "temp_noon", "temp_evening"]
default = 0.0
[fields.temp_max]
operation = "max"
sources = ["temp_morning", "temp_noon", "temp_evening"]
default = 0.0
[fields.temp_avg]
operation = "average"
sources = ["temp_morning", "temp_noon", "temp_evening"]
default = 0.0
Benefits
Declarative - Define calculations in configuration, not code
Reusable - Same operations work across different layouts
Safe - Built-in error handling prevents crashes
Maintainable - Easy to understand and modify
Fast - Compiled at configuration load time
See examples/computed_fields.toml and examples/demo_computed_fields.py for complete examples.
Layout Loader
The LayoutLoader handles loading and parsing TOML configuration files.
Loading Layouts
from viewtext import LayoutLoader
# Load from specific file
loader = LayoutLoader("config/layouts.toml")
# Load from default location (./layouts.toml)
loader = LayoutLoader()
# Get a specific layout
layout = loader.get_layout("weather")
Split Configuration Files
For large projects, you can split your configuration into separate files for better organization and maintainability:
from viewtext import LayoutLoader
# Method 1: Using constructor parameters
loader = LayoutLoader(
config_path="layouts.toml",
formatters_path="formatters.toml",
fields_path="fields.toml"
)
config = loader.load()
# Method 2: Using static method
config = LayoutLoader.load_from_files(
layouts_path="layouts.toml",
formatters_path="formatters.toml",
fields_path="fields.toml"
)
Example: Separate Formatters File
formatters.toml:
[formatters.price_usd]
type = "price"
symbol = "$"
decimals = 2
[formatters.price_eur]
type = "price"
symbol = "€"
decimals = 2
layouts.toml:
[layouts.product]
name = "Product Display"
[[layouts.product.lines]]
field = "price"
index = 0
formatter = "price_usd"
Example: Separate Fields File
fields.toml:
[fields.temperature]
context_key = "temp"
default = 0
[fields.city]
context_key = "location.city"
default = "Unknown"
[fields.first_tag]
context_key = "tags.0"
default = ""
CLI Usage with Split Files
# Use --formatters and --fields flags
viewtext --config layouts.toml \\
--formatters formatters.toml \\
--fields fields.toml \\
list
viewtext -c layouts.toml -f formatters.toml -F fields.toml render weather
Benefits of Split Files
Modularity: Separate concerns into different files
Reusability: Share formatters and fields across multiple layout files
Team Collaboration: Different team members can work on different files
Maintainability: Easier to find and update specific configurations
Merging Behavior
When multiple files are provided:
Fields from
fields.tomlare merged into the base configurationFormatters from
formatters.tomlare merged into the base configurationIf the same key exists in multiple files, values from separate files take precedence
All separate files are optional
Getting Formatter Parameters
# Get global formatter configuration
params = loader.get_formatter_params("usd_price")
Error Handling
ViewText raises specific exceptions for common errors:
from viewtext import LayoutLoader, BaseFieldRegistry
# FileNotFoundError
try:
loader = LayoutLoader("missing.toml")
loader.load()
except FileNotFoundError as e:
print(f"Config file not found: {e}")
# ValueError for unknown layout
try:
layout = loader.get_layout("nonexistent")
except ValueError as e:
print(f"Layout error: {e}")
# ValueError for unknown field
registry = BaseFieldRegistry()
try:
getter = registry.get("unknown_field")
except ValueError as e:
print(f"Field error: {e}")
Best Practices
Separate concerns: Keep field logic in the registry, formatting in formatters, and layout structure in TOML files
Use meaningful names: Choose descriptive field and layout names
Provide defaults: Use .get() with defaults in field getters for optional data
Validate data: Formatters should handle None and invalid values gracefully
Reuse formatters: Define global formatter configurations for consistency
Test layouts: Verify layouts with sample data before deployment
Command Line Interface
ViewText includes a CLI for inspecting and testing layouts.
Basic Commands
# List all available layouts
viewtext list
# Show specific layout configuration
viewtext show weather
# Show field mappings from config
viewtext fields
# Show all available formatters
viewtext formatters
# Show all template formatters in layouts
viewtext templates
# Render a layout with mock data
viewtext render weather
# Render a layout with JSON input from stdin (autodetected)
echo '{"temp": 72.5}' | viewtext render weather
# Render a layout with JSON input and JSON output
echo '{"temp": 72.5}' | viewtext render weather --json
# Show configuration info
viewtext info
Global Config Option
Use the --config or -c option to specify a custom configuration file:
# Global option can be placed before any command
viewtext -c examples/layouts.toml list
viewtext --config my_layouts.toml show weather
viewtext -c custom.toml render crypto_ticker
The default config file is layouts.toml in the current directory.
CLI Output
The CLI provides rich formatted output with tables and colors:
$ viewtext list
Configuration File: layouts.toml
┌────────────────┬─────────────────────┬───────┐
│ Layout Name │ Display Name │ Lines │
├────────────────┼─────────────────────┼───────┤
│ weather │ Weather Display │ 6 │
│ crypto_ticker │ Crypto Ticker │ 5 │
└────────────────┴─────────────────────┴───────┘
Total layouts: 2
JSON Input and Output
The render command automatically detects JSON input from stdin and can output results as JSON arrays:
# JSON input is autodetected from stdin, output is rich formatted text
$ echo '{"demo1": "Line 1", "demo2": "Line 2"}' | viewtext render demo
# Use --json flag to output as JSON array instead of formatted text
$ echo '{"demo1": "Line 1", "demo2": "Line 2"}' | viewtext render demo --json
[
"Line 1",
"Line 2"
]
The --json flag controls the output format only. Input JSON is automatically detected when available on stdin.
Template Formatters Command
The templates command shows all layouts using template formatters:
$ viewtext -c examples/demo_template_formatter.toml templates
Configuration File: examples/demo_template_formatter.toml
┌────────────────────────┬──────────────┬─────────────────────────┬──────────────┐
│ Layout │ Field │ Template │ Fields Used │
├────────────────────────┼──────────────┼─────────────────────────┼──────────────┤
│ crypto_composite_price │ current_price│ {fiat} - ${usd} - {sat} │ price.fiat, │
│ (Crypto Price Display) │ │ │ price.usd, │
│ │ │ │ price.sat │
└────────────────────────┴──────────────┴─────────────────────────┴──────────────┘
Total template formatters: 1
Advanced Usage
Singleton Pattern
ViewText provides singleton accessors for global instances:
from viewtext import (
get_layout_engine,
get_formatter_registry,
get_layout_loader
)
# These return global singleton instances
engine = get_layout_engine(field_registry=registry)
formatters = get_formatter_registry()
loader = get_layout_loader("layouts.toml")
Dynamic Layouts
Build layouts dynamically from data:
def create_dynamic_layout(fields):
layout = {
"name": "Dynamic Layout",
"lines": []
}
for i, field in enumerate(fields):
layout["lines"].append({
"field": field,
"index": i,
"formatter": "text"
})
return layout
# Use the dynamic layout
layout = create_dynamic_layout(["temp", "humidity", "pressure"])
lines = engine.build_line_str(layout, context)
Context Factories
Create reusable context builders:
class WeatherContext:
def __init__(self, api_data):
self.data = api_data
def to_context(self):
return {
"temp": self.data["main"]["temp"],
"humidity": self.data["main"]["humidity"],
"city": self.data["name"],
"timestamp": self.data["dt"]
}
weather = WeatherContext(api_response)
lines = engine.build_line_str(layout, weather.to_context())
TOML Schema Validation
ViewText provides a JSON Schema for validating TOML configuration files with editor support.
Editor Support
The schema enables validation and autocomplete in editors that support Taplo:
VS Code
Install the Even Better TOML extension for:
Syntax validation
Property autocomplete
Hover documentation
Format on save
Neovim
Use the taplo LSP for validation and completion.
Other Editors
Any editor with Taplo LSP support will work.
Schema Features
The schema validates:
Field definitions - Ensures proper structure with required properties
Formatter configurations - Validates types and parameters
Layout definitions - Validates layout structure
Computed operations - Validates operation names and parameters
Validation rules - Validates type constraints and error handling
Example with autocomplete:
[fields.user_age]
context_key = "age"
type = "int" # Autocomplete suggests: str, int, float, bool, list, dict
min_value = 0
max_value = 120
on_validation_error = "use_default" # Autocomplete suggests: use_default, raise, skip, coerce
[fields.temp_f]
operation = "celsius_to_fahrenheit" # Autocomplete suggests all operations
Validation Command
Check TOML files manually using the taplo command:
# Check a single file
taplo check layouts.toml
# Format and check
taplo format layouts.toml
Configuration
The schema is configured in .taplo.toml and automatically applies to:
**/layouts*.toml- Layout configuration files**/fields.toml- Field-only configuration files**/formatters.toml- Formatter-only configuration files
See .taplo/README.md for more information on the schema.