-
Couldn't load subscription status.
- Fork 9
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.
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') }}
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 variablesAction-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 contentThe --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.shWhen 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 | 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.
# 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 agoCommon 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 |
# 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 defaultsUse Cases:
- User-specific paths:
{{ env('HOME') }}/projects - Configuration values:
{{ env('API_ENDPOINT', 'localhost') }} - System context:
{{ env('USER') }}@{{ env('HOSTNAME') }}
# 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 }} # ArchitectureShell 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 }}
# 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 hostnameDaily 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-weeklyUser-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') }}" wikiAPI 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-logsBackup 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"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"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"# 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# 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# 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# 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 sourcingbkmr 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
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
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/templatepackage
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
Issue: Template shows raw syntax instead of resolved values
Debugging Steps:
- Check raw content:
bkmr search "template-name"
# Verify template syntax is present- Verify template syntax:
# ✅ Correct: {{ current_date }}
# ❌ Wrong: { current_date } (single braces)
# ❌ Wrong: {{current_date}} (no spaces - actually works but not recommended)- Test with interpolation flag:
bkmr search --interpolate "template-name"
# Should show resolved values- Use FZF preview:
bkmr search --fzf "template-name"
# Preview should show resolved valuesIssue: 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"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 }}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-specificReference: 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)
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 templateMethod 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 }}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 insteadBest Practices:
- Only use trusted commands in templates
- Avoid piping environment variables to shell
- Use environment variables directly when possible
- Validate or sanitize dynamic input
# ✅ 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 permissionsSecurity 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
# ✅ 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 logicMaintainability 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
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
- Content Types - How interpolation works with different content types
- Shell Scripts - Using templates in shell script bookmarks
- Search and Discovery - Searching with and without interpolation
- Configuration - Template-related configuration
- Basic Usage - Common template patterns in daily workflows