Files
Fabric/internal/plugins/template/README.md
Kayvan Sylvan 4004c51b9e refactor: restructure project to align with standard Go layout
### CHANGES

- Introduce `cmd` directory for all main application binaries.
- Move all Go packages into the `internal` directory.
- Rename the `restapi` package to `server` for clarity.
- Consolidate patterns and strategies into a new `data` directory.
- Group all auxiliary scripts into a new `scripts` directory.
- Move all documentation and images into a `docs` directory.
- Update all Go import paths to reflect the new structure.
- Adjust CI/CD workflows and build commands for new layout.
2025-07-08 22:47:17 -07:00

418 lines
9.4 KiB
Markdown

# 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:
```markdown
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
```markdown
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
```markdown
{{outer{{inner}}}}
Example:
Variables: {
"inner": "name",
"john": "John Doe"
}
{{{{inner}}}} -> {{name}} -> John Doe
```
#### Nested Plugin Calls
```markdown
{{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**
```markdown
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**
```markdown
{{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**
```markdown
# 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**
```go
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**
```markdown
{{plugin:text:{{operation}}:hello}}
With variables:
{
"operation": "upper"
}
Result: HELLO
```
2. **Dynamic Environment Variable Lookup**
```markdown
{{plugin:sys:env:{{env_var}}}}
With variables:
{
"env_var": "HOME"
}
Result: /home/user
```
3. **Nested Date Formatting**
```markdown
{{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:
```markdown
{{plugin:text:upper:hello}} -> HELLO
{{plugin:text:lower:HELLO}} -> hello
{{plugin:text:title:hello world}} -> Hello World
```
#### DateTime Plugin
Time and date operations:
```markdown
{{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:
```markdown
{{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:
```go
type Plugin interface {
Apply(operation string, value string) (string, error)
}
```
### Example Plugin Implementation
Here's a simple plugin that performs basic math operations:
```go
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:
```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
```go
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
}
```
3. **Security Considerations**
- Validate and sanitize inputs
- Avoid shell execution
- Be careful with file operations
- Limit resource usage
4. **Performance**
- Cache expensive computations
- Minimize allocations
- Consider concurrent access
### Testing Plugins
Create tests for your plugin in `plugin_test.go`:
```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