mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-02-12 15:05:10 -05:00
### 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.
418 lines
9.4 KiB
Markdown
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 |