template

package
v1.4.124 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 21, 2024 License: MIT Imports: 16 Imported by: 0

README

Fabric Template System

Quick Start

echo "Hello {{name}}!" | fabric -v=name:World

Overview

The Fabric Template System provides a powerful and extensible way to handle variable substitution and dynamic content generation through a plugin architecture. It uses a double-brace syntax ({{}}) for variables and plugin operations, making it both readable and flexible.

Basic Usage

Variable Substitution

The template system supports basic variable substitution using double braces:

Hello {{name}}!
Current role: {{role}}

Variables can be provided via:

  • Command line arguments: -v=name:John -v=role:admin
  • YAML front matter in input files
  • Environment variables (when configured)
Special Variables
  • {{input}}: Represents the main input content
    Here is the analysis:
    {{input}}
    End of analysis.
    

Nested Tokens and Resolution

Basic Nesting

The template system supports nested tokens, where inner tokens are resolved before outer ones. This enables complex, dynamic template generation.

Simple Variable Nesting
{{outer{{inner}}}}

Example:
Variables: {
  "inner": "name",
  "john": "John Doe"
}
{{{{inner}}}} -> {{name}} -> John Doe
Nested Plugin Calls
{{plugin:text:upper:{{plugin:sys:env:USER}}}}
First resolves: {{plugin:sys:env:USER}} -> "john"
Then resolves: {{plugin:text:upper:john}} -> "JOHN"
How Nested Resolution Works
  1. Iterative Processing

    • The engine processes the template in multiple passes
    • Each pass identifies all {{...}} patterns
    • Processing continues until no more replacements are needed
  2. Resolution Order

    Original: {{plugin:text:upper:{{user}}}}
    Step 1: Found {{user}} -> "john"
    Step 2: Now have {{plugin:text:upper:john}}
    Step 3: Final result -> "JOHN"
    
  3. Complex Nesting Example

    {{plugin:text:{{case}}:{{plugin:sys:env:{{varname}}}}}}
    
    With variables:
    {
      "case": "upper",
      "varname": "USER"
    }
    
    Resolution steps:
    1. {{varname}} -> "USER"
    2. {{plugin:sys:env:USER}} -> "john"
    3. {{case}} -> "upper"
    4. {{plugin:text:upper:john}} -> "JOHN"
    
Important Considerations
  1. Depth Limitations

    • While nesting is supported, avoid excessive nesting for clarity
    • Complex nested structures can be hard to debug
    • Consider breaking very complex templates into smaller parts
  2. Variable Resolution

    • Inner variables must resolve to valid values for outer operations
    • Error messages will point to the innermost failed resolution
    • Debug logs show the step-by-step resolution process
  3. Plugin Nesting

    # Valid:
    {{plugin:text:upper:{{plugin:sys:env:USER}}}}
    
    # Also Valid:
    {{plugin:text:{{operation}}:{{value}}}}
    
    # Invalid (plugin namespace cannot be dynamic):
    {{plugin:{{namespace}}:operation:value}}
    
  4. Debugging Nested Templates

    Debug = true  // Enable debug logging
    
    Template: {{plugin:text:upper:{{user}}}}
    Debug output:
    > Processing variable: user
    > Replacing {{user}} with john
    > Plugin call:
    >   Namespace: text
    >   Operation: upper
    >   Value: john
    > Plugin result: JOHN
    
Examples
  1. Dynamic Operation Selection

    {{plugin:text:{{operation}}:hello}}
    
    With variables:
    {
      "operation": "upper"
    }
    
    Result: HELLO
    
  2. Dynamic Environment Variable Lookup

    {{plugin:sys:env:{{env_var}}}}
    
    With variables:
    {
      "env_var": "HOME"
    }
    
    Result: /home/user
    
  3. Nested Date Formatting

    {{plugin:datetime:{{format}}:{{plugin:datetime:now}}}}
    
    With variables:
    {
      "format": "full"
    }
    
    Result: Wednesday, November 20, 2024
    

Plugin System

Plugin Syntax

Plugins use the following syntax:

{{plugin:namespace:operation:value}}
  • namespace: The plugin category (e.g., text, datetime, sys)
  • operation: The specific operation to perform
  • value: Optional value for the operation
Built-in Plugins
Text Plugin

Text manipulation operations:

{{plugin:text:upper:hello}}  -> HELLO
{{plugin:text:lower:HELLO}}  -> hello
{{plugin:text:title:hello world}} -> Hello World
DateTime Plugin

Time and date operations:

{{plugin:datetime:now}}       -> 2024-11-20T15:04:05Z
{{plugin:datetime:today}}     -> 2024-11-20
{{plugin:datetime:rel:-1d}}   -> 2024-11-19
{{plugin:datetime:month}}     -> November
System Plugin

System information:

{{plugin:sys:hostname}}   -> server1
{{plugin:sys:user}}       -> currentuser
{{plugin:sys:os}}         -> linux
{{plugin:sys:env:HOME}}   -> /home/user

Developing Plugins

Plugin Interface

To create a new plugin, implement the following interface:

type Plugin interface {
    Apply(operation string, value string) (string, error)
}
Example Plugin Implementation

Here's a simple plugin that performs basic math operations:

package template

type MathPlugin struct{}

func (p *MathPlugin) Apply(operation string, value string) (string, error) {
    switch operation {
    case "add":
        // Parse value as "a,b" and return a+b
        nums := strings.Split(value, ",")
        if len(nums) != 2 {
            return "", fmt.Errorf("add requires two numbers")
        }
        a, err := strconv.Atoi(nums[0])
        if err != nil {
            return "", err
        }
        b, err := strconv.Atoi(nums[1])
        if err != nil {
            return "", err
        }
        return fmt.Sprintf("%d", a+b), nil
    
    default:
        return "", fmt.Errorf("unknown math operation: %s", operation)
    }
}
Registering a New Plugin
  1. Add your plugin struct to the template package
  2. Register it in template.go:
var (
    // Existing plugins
    textPlugin = &TextPlugin{}
    datetimePlugin = &DateTimePlugin{}
    
    // Add your new plugin
    mathPlugin = &MathPlugin{}
)

// Update the plugin handler in ApplyTemplate
switch namespace {
    case "text":
        result, err = textPlugin.Apply(operation, value)
    case "datetime":
        result, err = datetimePlugin.Apply(operation, value)
    // Add your namespace
    case "math":
        result, err = mathPlugin.Apply(operation, value)
    default:
        return "", fmt.Errorf("unknown plugin namespace: %s", namespace)
}
Plugin Development Guidelines
  1. Error Handling

    • Return clear error messages
    • Validate all inputs
    • Handle edge cases gracefully
  2. Debugging

    • Use the debugf function for logging
    • Log entry and exit points
    • Log intermediate calculations
func (p *MyPlugin) Apply(operation string, value string) (string, error) {
    debugf("MyPlugin operation: %s value: %s\n", operation, value)
    // ... plugin logic ...
    debugf("MyPlugin result: %s\n", result)
    return result, nil
}
  1. Security Considerations

    • Validate and sanitize inputs
    • Avoid shell execution
    • Be careful with file operations
    • Limit resource usage
  2. Performance

    • Cache expensive computations
    • Minimize allocations
    • Consider concurrent access
Testing Plugins

Create tests for your plugin in plugin_test.go:

func TestMathPlugin(t *testing.T) {
    plugin := &MathPlugin{}
    
    tests := []struct {
        operation string
        value     string
        expected  string
        wantErr   bool
    }{
        {"add", "5,3", "8", false},
        {"add", "bad,input", "", true},
        {"unknown", "value", "", true},
    }
    
    for _, tt := range tests {
        result, err := plugin.Apply(tt.operation, tt.value)
        if (err != nil) != tt.wantErr {
            t.Errorf("MathPlugin.Apply(%s, %s) error = %v, wantErr %v",
                tt.operation, tt.value, err, tt.wantErr)
            continue
        }
        if result != tt.expected {
            t.Errorf("MathPlugin.Apply(%s, %s) = %v, want %v",
                tt.operation, tt.value, result, tt.expected)
        }
    }
}

Best Practices

  1. Namespace Selection

    • Choose clear, descriptive names
    • Avoid conflicts with existing plugins
    • Group related operations together
  2. Operation Names

    • Use lowercase names
    • Keep names concise but clear
    • Be consistent with similar operations
  3. Value Format

    • Document expected formats
    • Use common separators consistently
    • Provide examples in comments
  4. Error Messages

    • Be specific about what went wrong
    • Include valid operation examples
    • Help users fix the problem

Common Issues and Solutions

  1. Missing Variables

    Error: missing required variables: [name]
    Solution: Provide all required variables using -v=name:value
    
  2. Invalid Plugin Operations

    Error: unknown operation 'invalid' for plugin 'text'
    Solution: Check plugin documentation for supported operations
    
  3. Plugin Value Format

    Error: invalid format for datetime:rel, expected -1d, -2w, etc.
    Solution: Follow the required format for plugin values
    

Contributing

  1. Fork the repository
  2. Create your plugin branch
  3. Implement your plugin following the guidelines
  4. Add comprehensive tests
  5. Submit a pull request

Support

For issues and questions:

  1. Check the debugging output (enable with Debug=true)
  2. Review the plugin documentation
  3. Open an issue with:
    • Template content
    • Variables used
    • Expected vs actual output
    • Debug logs

Documentation

Overview

Package template provides datetime operations for the template system

Package template provides URL fetching operations for the template system. Security Note: This plugin makes outbound HTTP requests. Use with caution and consider implementing URL allowlists in production.

Package template provides file system operations for the template system. Security Note: This plugin provides access to the local filesystem. Consider carefully which paths to allow access to in production.

Package template provides system information operations for the template system.

Package template provides text transformation operations for the template system.

Index

Constants

View Source
const (
	// MaxContentSize limits response size to 1MB to prevent memory issues
	MaxContentSize = 1024 * 1024

	// UserAgent identifies the client in HTTP requests
	UserAgent = "Fabric-Fetch/1.0"
)
View Source
const MaxFileSize = 1 * 1024 * 1024

MaxFileSize defines the maximum file size that can be read (1MB)

Variables

View Source
var (
	Debug = false // Debug flag
)

Functions

func ApplyTemplate

func ApplyTemplate(content string, variables map[string]string, input string) (string, error)

Types

type DateTimePlugin

type DateTimePlugin struct{}

DateTimePlugin handles time and date operations

func (*DateTimePlugin) Apply

func (p *DateTimePlugin) Apply(operation string, value string) (string, error)

Apply executes datetime operations with the following formats: Time: now (RFC3339), time (HH:MM:SS), unix (timestamp) Hour: startofhour, endofhour Date: today (YYYY-MM-DD), full (Monday, January 2, 2006) Period: startofweek, endofweek, startofmonth, endofmonth Relative: rel:-1h, rel:-2d, rel:1w, rel:3m, rel:1y

type FetchPlugin

type FetchPlugin struct{}

FetchPlugin provides HTTP fetching capabilities with safety constraints: - Only text content types allowed - Size limited to MaxContentSize - UTF-8 validation - Null byte checking

func (*FetchPlugin) Apply

func (p *FetchPlugin) Apply(operation string, value string) (string, error)

Apply executes fetch operations:

  • get:URL: Fetches content from URL, returns text content

type FilePlugin

type FilePlugin struct{}

FilePlugin provides filesystem operations with safety constraints: - No directory traversal - Size limits - Path sanitization

func (*FilePlugin) Apply

func (p *FilePlugin) Apply(operation string, value string) (string, error)

Apply executes file operations:

  • read:PATH - Read entire file content
  • tail:PATH|N - Read last N lines
  • exists:PATH - Check if file exists
  • size:PATH - Get file size in bytes
  • modified:PATH - Get last modified time

type SysPlugin

type SysPlugin struct{}

SysPlugin provides access to system-level information. Security Note: This plugin provides access to system information and environment variables. Be cautious with exposed variables in templates.

func (*SysPlugin) Apply

func (p *SysPlugin) Apply(operation string, value string) (string, error)

Apply executes system operations with the following options:

  • hostname: System hostname
  • user: Current username
  • os: Operating system (linux, darwin, windows)
  • arch: System architecture (amd64, arm64, etc)
  • env:VALUE: Environment variable lookup
  • pwd: Current working directory
  • home: User's home directory

type TextPlugin

type TextPlugin struct{}

TextPlugin provides string manipulation operations

func (*TextPlugin) Apply

func (p *TextPlugin) Apply(operation string, value string) (string, error)

Apply executes the requested text operation on the provided value

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL