Skip to content

Template Interpolation

sysid edited this page Oct 12, 2025 · 2 revisions

Template Interpolation

bkmr provides powerful Jinja2-style template interpolation to create dynamic content that adapts to runtime context. Templates automatically resolve environment variables, execute shell commands, and generate timestamps when bookmarks are accessed.

Overview

Template interpolation transforms static bookmark content into dynamic, context-aware content. Instead of hardcoding values, you can use template syntax that gets resolved when the bookmark is used.

Benefits:

  • Dynamic timestamps - Daily reports, backups, logs with automatic dates
  • Environment awareness - User-specific URLs, workspace paths
  • Runtime context - Current git branch, hostname, system info
  • Reusable patterns - Single template works across environments

Template Syntax:

  • Variables: {{ variable_name }}
  • Filters: {{ value | filter_name }}
  • Functions: {{ function('arg') }}

When Templates Are Interpolated

Automatic Interpolation (No Flags Required)

Templates are automatically resolved in these contexts:

1. FZF Fuzzy Finder Mode

bkmr search --fzf
bkmr search --fzf -t _snip_
bkmr search --fzf "deployment"

All fuzzy finder features show interpolated content:

  • Preview pane displays resolved values
  • CTRL-O copies interpolated content
  • Enhanced display renders dynamic values in real-time

2. Bookmark Actions (Open, Execute, Copy)

# All bookmark actions automatically interpolate
bkmr open <id>           # URLs: Opens with resolved variables
bkmr open <id>           # Snippets: Copies interpolated content
bkmr open <id>           # Shell: Executes with resolved values
bkmr open <id>           # Env: Sources interpolated variables

Action-Specific Behavior:

  • URI action: Templates resolved before opening browser
  • Snippet action: Content interpolated before clipboard copy
  • Shell action: Scripts interpolated before execution
  • Environment action: Variables interpolated before output

3. Clipboard Operations

# Yank always interpolates before copying
bkmr yank <id>

# FZF CTRL-O also interpolates
bkmr search --fzf
# Press CTRL-O to copy interpolated content

Manual Interpolation (Flag Required)

The --interpolate flag is only needed for regular search result display:

# Without flag - shows raw template syntax
bkmr search "deploy"
# Output: deploy-{{ current_date | strftime('%Y-%m-%d') }}.sh

# With flag - shows resolved values
bkmr search --interpolate "deploy"
# Output: deploy-2025-10-12.sh

When to use --interpolate:

  • ✅ Viewing resolved values in search results
  • ✅ Debugging template expressions before using bookmarks
  • ✅ Regular (non-FZF) search mode

When NOT to use --interpolate:

  • ❌ FZF mode (automatic)
  • ❌ Bookmark actions like open (automatic)
  • ❌ Clipboard operations (automatic)

Content Type Support

Content Type Supports Interpolation Notes
URLs ✅ Yes Always interpolated during actions
Snippets (_snip_) ✅ Yes Interpolated when copied
Shell Scripts (_shell_) ✅ Yes Interpolated before execution
Environment Variables (_env_) ✅ Yes Interpolated when sourced
Text Documents (_imported_) ✅ Yes Interpolated when copied
Markdown (_md_) ❌ No Disabled to avoid syntax conflicts

Note: Markdown content deliberately excludes interpolation to prevent conflicts with legitimate markdown syntax that may contain {%} patterns in code documentation.

Template Syntax Reference

Date and Time

# Current datetime object
{{ current_date }}

# Formatted dates
{{ current_date | strftime('%Y-%m-%d') }}          # 2025-10-12
{{ current_date | strftime('%B %d, %Y') }}         # October 12, 2025
{{ current_date | strftime('%Y%m%d_%H%M%S') }}     # 20251012_143015

# Time formatting
{{ current_date | strftime('%H:%M:%S') }}          # 14:30:15
{{ current_date | strftime('%I:%M %p') }}          # 02:30 PM

# Date arithmetic
{{ current_date | subtract_days(7) }}              # 7 days ago
{{ current_date | add_days(30) }}                  # 30 days from now
{{ current_date | subtract_days(7) | strftime('%Y-%m-%d') }}  # Date 7 days ago

Common strftime formats:

Format Output Description
%Y-%m-%d 2025-10-12 ISO date
%Y%m%d 20251012 Compact date
%B %d, %Y October 12, 2025 Full month name
%Y-%m-%d %H:%M:%S 2025-10-12 14:30:15 ISO datetime
%s 1728742215 Unix timestamp

Environment Variables

# Basic access
{{ env('HOME') }}                                  # Environment variable
{{ env('USER') }}                                  # Current username
{{ env('PATH') }}                                  # System PATH

# With fallback values
{{ env('API_KEY', 'default-key') }}                # Uses default if not set
{{ env('ENVIRONMENT', 'development') }}            # Safe defaults
{{ env('EDITOR', 'vim') }}                         # Tool defaults

Use Cases:

  • User-specific paths: {{ env('HOME') }}/projects
  • Configuration values: {{ env('API_ENDPOINT', 'localhost') }}
  • System context: {{ env('USER') }}@{{ env('HOSTNAME') }}

Shell Command Execution

# Basic commands
{{ "whoami" | shell }}                             # Current username
{{ "hostname" | shell }}                           # System hostname
{{ "pwd" | shell }}                                # Current directory

# Git integration
{{ "git branch --show-current" | shell }}          # Current git branch
{{ "git rev-parse --short HEAD" | shell }}         # Short commit hash
{{ "git describe --tags" | shell }}                # Latest git tag

# Date/time via shell
{{ "date '+%Y%m%d'" | shell }}                     # Date via shell command
{{ "date '+%s'" | shell }}                         # Unix timestamp

# System info
{{ "uname -s" | shell }}                           # Operating system
{{ "arch" | shell }}                               # Architecture

Shell Command Best Practices:

  • Use simple, fast commands
  • Avoid commands requiring user input
  • Test commands independently first
  • Use fallbacks with ||: {{ "git branch --show-current || echo 'no-git'" | shell }}

String Manipulation

# Case conversion
{{ "hello world" | upper }}                        # HELLO WORLD
{{ "HELLO WORLD" | lower }}                        # hello world
{{ "hello world" | title }}                        # Hello World

# Whitespace
{{ "  spaced  " | trim }}                          # spaced

# Common patterns
{{ env('USER') | upper }}                          # Uppercase username
{{ "hostname" | shell | lower }}                   # Lowercase hostname

Practical Examples

Dynamic URLs

Daily Reports:

# URL changes based on current date
bkmr add "https://reports.company.com/{{ current_date | strftime('%Y/%m/%d') }}/summary" reports

# Weekly reports (7 days ago to today)
bkmr add "https://reports.company.com/weekly?from={{ current_date | subtract_days(7) | strftime('%Y-%m-%d') }}&to={{ current_date | strftime('%Y-%m-%d') }}" reports-weekly

User-Specific Dashboards:

# Personalized dashboard URL
bkmr add "https://dashboard.company.com/users/{{ env('USER') }}" dashboard

# Team-specific pages
bkmr add "https://wiki.company.com/teams/{{ env('TEAM', 'engineering') }}" wiki

API Endpoints with Timestamps:

# API call with current timestamp
bkmr add "https://api.example.com/data?timestamp={{ current_date | strftime('%s') }}" api-data

# Time-range queries
bkmr add "https://api.example.com/logs?start={{ current_date | subtract_days(1) | strftime('%s') }}&end={{ current_date | strftime('%s') }}" api-logs

Dynamic Shell Scripts

Backup Scripts with Timestamps:

bkmr add "#!/bin/bash
echo 'Starting backup at {{ current_date | strftime('%Y-%m-%d %H:%M:%S') }}'
BACKUP_FILE=\"backup-{{ current_date | strftime('%Y%m%d_%H%M%S') }}.tar.gz\"
tar -czf \"\$BACKUP_FILE\" /data/
echo \"Backup completed on {{ \"hostname\" | shell }} as \$BACKUP_FILE\"" backup,_shell_ --title "database-backup"

Git Workflow Scripts:

bkmr add "#!/bin/bash
echo 'Current branch: {{ \"git branch --show-current\" | shell }}'
echo 'Current user: {{ env('USER') }}'
git commit -m 'Auto-commit {{ current_date | strftime('%Y-%m-%d') }}'" git,_shell_ --title "git-auto-commit"

Deployment Scripts:

bkmr add "#!/bin/bash
DEPLOY_ENV='{{ env('ENVIRONMENT', 'staging') }}'
DEPLOY_DATE='{{ current_date | strftime('%Y%m%d_%H%M%S') }}'
echo \"Deploying to \$DEPLOY_ENV at \$DEPLOY_DATE\"
kubectl apply -f deployment-\$DEPLOY_ENV.yaml" deploy,_shell_ --title "k8s-deploy"

Environment Variables with Context

Development Environment:

bkmr add "export PROJECT_ROOT={{ env('HOME') }}/projects
export BUILD_TIME={{ current_date | strftime('%Y%m%d_%H%M%S') }}
export GIT_BRANCH={{ \"git branch --show-current\" | shell }}
export HOSTNAME={{ \"hostname\" | shell }}
export USER={{ env('USER') }}" dev,_env_ --title "dev-environment"

# Usage
eval "$(bkmr open <id>)"

Deployment Environment:

bkmr add "export DEPLOY_DATE={{ current_date | strftime('%Y-%m-%d') }}
export DEPLOY_USER={{ env('USER') }}
export DEPLOY_HOST={{ \"hostname\" | shell }}
export DEPLOY_BRANCH={{ \"git branch --show-current\" | shell }}
export DEPLOY_COMMIT={{ \"git rev-parse --short HEAD\" | shell }}" deploy,_env_ --title "deploy-environment"

Code Snippets with Dynamic Values

SQL Queries:

# Recent logs (last 7 days)
bkmr add "SELECT * FROM logs
WHERE created_at >= '{{ current_date | subtract_days(7) | strftime('%Y-%m-%d') }}'
ORDER BY created_at DESC;" sql,_snip_ --title "recent-logs"

# Today's data
bkmr add "SELECT * FROM daily_metrics
WHERE date = '{{ current_date | strftime('%Y-%m-%d') }}';" sql,_snip_ --title "today-metrics"

JSON Configuration:

bkmr add "{
  \"user\": \"{{ env('USER') }}\",
  \"timestamp\": \"{{ current_date | strftime('%Y-%m-%dT%H:%M:%SZ') }}\",
  \"hostname\": \"{{ \"hostname\" | shell }}\",
  \"environment\": \"{{ env('ENVIRONMENT', 'development') }}\"
}" config,_snip_ --title "runtime-config"

Documentation Headers:

bkmr add "# Generated on {{ current_date | strftime('%B %d, %Y at %H:%M') }}
# By: {{ env('USER') }}
# Host: {{ \"hostname\" | shell }}
# Branch: {{ \"git branch --show-current\" | shell }}" doc-header,_snip_ --title "doc-header"

Workflow Examples

Search and Discovery

# Step 1: Search without interpolation (see template structure)
bkmr search "backup"
# Output: backup-{{ current_date | strftime('%Y%m%d') }}.sh

# Step 2: Search with interpolation (see resolved values)
bkmr search --interpolate "backup"
# Output: backup-20251012.sh

# Step 3: Use fuzzy finder (automatic interpolation)
bkmr search --fzf "backup"
# Preview shows: backup-20251012.sh
# Press Enter to execute

Development Workflow

# Store dynamic API endpoints
bkmr add "https://api.dev.company.com/{{ env('USER') }}/data" api,dev --title "dev-api"
bkmr add "https://api.prod.company.com/data?key={{ env('API_KEY') }}" api,prod --title "prod-api"

# Quick access with automatic interpolation
bkmr search --fzf "api"          # Shows resolved URLs
bkmr open <id>                   # Opens interpolated URL

Script Management

# Create parameterized script
bkmr add "#!/bin/bash
echo 'Deploying to {{ env('ENVIRONMENT', 'development') }} at {{ current_date }}'
kubectl apply -f deployment-{{ env('ENVIRONMENT', 'dev') }}.yaml
kubectl rollout status deployment/app-{{ env('ENVIRONMENT', 'dev') }}" deploy,k8s,_shell_ --title "k8s-deploy"

# Execute with automatic interpolation
bkmr open <id>  # Script runs with resolved variables

Environment Management

# Create context-aware environment
bkmr add "export WORKSPACE={{ env('HOME') }}/workspaces/{{ \"git branch --show-current\" | shell }}
export BUILD_DATE={{ current_date | strftime('%Y%m%d') }}
export HOSTNAME={{ \"hostname\" | shell }}
export GIT_COMMIT={{ \"git rev-parse --short HEAD\" | shell }}" workspace,_env_ --title "workspace-env"

# Source with automatic interpolation
eval "$(bkmr open <id>)"  # Variables resolved before sourcing

Implementation Details

Lazy Evaluation Pattern

bkmr uses render_if_needed() to optimize performance:

1. Check if content contains template markers: {{ or {%
   ├─ YES: Apply Jinja2 template rendering
   └─ NO: Return content unchanged (zero overhead)

Benefits:

  • Performance: No processing for non-template content
  • Safety: Regular content never accidentally modified
  • Consistency: Same logic across all content types

Error Handling

Template interpolation errors are handled gracefully:

  • Search display: Warnings logged, original content preserved
  • Action execution: Errors reported, action may fail safely
  • FZF mode: Fallback to original content if rendering fails

Troubleshooting

Template Syntax Conflicts

Problem: bkmr detects any {{ or {% patterns and attempts Jinja2 processing, which can conflict with other templating systems.

Common Tools with Conflicts:

  • GitHub CLI (gh) - Uses Go templates with {{ syntax
  • Docker Compose - Uses Go templates in some contexts
  • Helm charts - Uses Go templates extensively
  • Kubernetes manifests - May contain Go template syntax
  • CLIs using Go's text/template package

Solution: Dynamic Template Construction

# ❌ Problem: GitHub CLI Go templates conflict
gh run list --template '{{range .}}{{.name}}{{end}}'
# Error: Template syntax error: unexpected end of variable block

# ✅ Solution: Construct template dynamically to avoid {{ pattern
OPEN_BRACE='{'
CLOSE_BRACE='}'
TEMPLATE="${OPEN_BRACE}${OPEN_BRACE}range .${CLOSE_BRACE}${CLOSE_BRACE}..."
gh run list --template "$TEMPLATE"

Alternative Solutions:

  • Store command without template in bookmarks
  • Use shell variables to construct templates at runtime
  • Quote or escape template patterns differently

Template Not Rendering

Issue: Template shows raw syntax instead of resolved values

Debugging Steps:

  1. Check raw content:
bkmr search "template-name"
# Verify template syntax is present
  1. Verify template syntax:
# ✅ Correct: {{ current_date }}
# ❌ Wrong:   { current_date }          (single braces)
# ❌ Wrong:   {{current_date}}          (no spaces - actually works but not recommended)
  1. Test with interpolation flag:
bkmr search --interpolate "template-name"
# Should show resolved values
  1. Use FZF preview:
bkmr search --fzf "template-name"
# Preview should show resolved values

Environment Variable Not Found

Issue: Template references undefined environment variable

Solution 1: Use fallback values

# Always provide defaults for optional variables
{{ env('API_KEY', 'default-key') }}
{{ env('ENVIRONMENT', 'development') }}
{{ env('EDITOR', 'vim') }}

Solution 2: Check if variable exists

# Before using in template
echo $VARIABLE_NAME

# Set if missing
export VARIABLE_NAME="value"

Shell Command Failures

Issue: Shell command in template fails or returns empty

Solution 1: Test commands separately

# Test command outside bkmr first
git branch --show-current

# Then use in template
{{ "git branch --show-current" | shell }}

Solution 2: Add error handling

# Use safe commands with fallbacks
{{ "which git && git branch --show-current || echo 'no-git'" | shell }}
{{ "hostname 2>/dev/null || echo 'unknown'" | shell }}

Solution 3: Verify command is in PATH

# Check command availability
which command-name

# Use full paths if needed
{{ "/usr/bin/hostname" | shell }}

Date Format Issues

Issue: Date format varies or produces unexpected output

Solution: Use standard strftime formats

# ✅ Good: Explicit, consistent formats
{{ current_date | strftime('%Y-%m-%d') }}
{{ current_date | strftime('%Y%m%d_%H%M%S') }}

# ❌ Avoid: Locale-dependent formats
{{ current_date | strftime('%x') }}        # May vary by locale
{{ current_date | strftime('%c') }}        # Locale-specific

Reference: Common strftime formats

  • %Y - 4-digit year (2025)
  • %m - 2-digit month (01-12)
  • %d - 2-digit day (01-31)
  • %H - 24-hour (00-23)
  • %M - Minute (00-59)
  • %S - Second (00-59)

Debugging Templates

Method 1: Compare raw vs rendered

# See raw template
bkmr search "bookmark-name"

# See rendered result
bkmr search --interpolate "bookmark-name"

Method 2: Use FZF preview

# FZF shows resolved values in real-time
bkmr search --fzf "bookmark-name"

Method 3: Check logs

# Enable debug logging
RUST_LOG=debug bkmr open <id> 2>&1 | grep -i template

Method 4: Test incrementally

# Start with simple template
{{ current_date }}

# Add filters one at a time
{{ current_date | strftime('%Y-%m-%d') }}

# Add more complexity gradually
{{ current_date | strftime('%Y-%m-%d') }} on {{ "hostname" | shell }}

Security Considerations

Shell Command Injection

Be cautious with shell commands in templates:

# ✅ Safe: Static commands
{{ "hostname" | shell }}
{{ "date" | shell }}
{{ "git branch --show-current" | shell }}

# ⚠️  Risk: Dynamic input from environment
{{ env('USER_INPUT') | shell }}  # Could be dangerous if user controls this

# ✅ Better: Validate or use fallback
{{ env('USER_INPUT', 'safe-default') }}

# ✅ Best: Avoid user-controlled shell execution
# Use environment variables directly instead

Best Practices:

  • Only use trusted commands in templates
  • Avoid piping environment variables to shell
  • Use environment variables directly when possible
  • Validate or sanitize dynamic input

Environment Variable Exposure

# ✅ Safe: Non-sensitive variables
{{ env('USER') }}
{{ env('HOME') }}
{{ env('HOSTNAME') }}

# ⚠️  Risk: Sensitive data may appear in logs/output
{{ env('API_SECRET') }}
{{ env('DATABASE_PASSWORD') }}
{{ env('PRIVATE_KEY') }}

# ✅ Better: Use secure secret management
# Store sensitive data separately (e.g., password managers, vaults)
# Reference by ID or use configuration files with restricted permissions

Security Best Practices:

  • Never put secrets in templates that may be logged
  • Use secret management tools for sensitive data
  • Restrict file permissions on bookmarks database
  • Be cautious sharing templates with embedded secrets

Template Complexity

# ✅ Simple: Easy to understand and debug
{{ current_date | strftime('%Y-%m-%d') }}

# ⚠️  Complex: Harder to debug and maintain
{{ env('BASE_URL', 'https://default.com') }}/{{ env('API_VERSION', 'v1') }}/{{ env('USER') | lower }}/data?t={{ current_date | strftime('%s') }}

# ✅ Better: Break into multiple simpler bookmarks
# Or use shell scripts for complex logic

Maintainability Guidelines:

  • Keep templates simple and focused
  • Use multiple bookmarks for complex workflows
  • Document non-obvious template logic in bookmark descriptions
  • Test templates thoroughly before relying on them

Performance Notes

Optimization Strategy:

  • Lazy evaluation: Templates only processed when {{ or {% detected
  • Pattern detection: Fast string search before expensive rendering
  • No caching: Shell commands executed fresh each time for accurate values
  • Environment reads: Variables read from current environment each access

Performance Tips:

  • Simple templates (environment, dates) are very fast
  • Shell commands add execution overhead
  • Complex templates may slow interactive operations
  • For high-frequency usage, consider pre-rendering or caching externally

Related Pages

Clone this wiki locally